Python(九)Tornado web 框架 其實很簡單、深度應用

来源:http://www.cnblogs.com/suoning/archive/2016/08/08/5721225.html
-Advertisement-
Play Games

一、簡介 Tornado 是 FriendFeed 使用的可擴展的非阻塞式 web 伺服器及其相關工具的開源版本。這個 Web 框架看起來有些像web.py 或者 Google 的 webapp,不過為了能有效利用非阻塞式伺服器環境,這個 Web 框架還包含了一些相關有用工具及優化。 Tornado ...


一、簡介

  Tornado 是 FriendFeed 使用的可擴展的非阻塞式 web 伺服器及其相關工具的開源版本。這個 Web 框架看起來有些像web.py 或者 Google 的 webapp,不過為了能有效利用非阻塞式伺服器環境,這個 Web 框架還包含了一些相關有用工具及優化。

  Tornado 和現在的主流 Web 伺服器框架(包括大多數 Python 的框架)有著明顯的區別:它是非阻塞式伺服器,而且速度相當快。得利於其非阻塞的方式和對 epoll 的運用,Tornado 每秒可以處理數以千計的連接,這就意味著對於實時的 Web 服務來說,Tornado 是一個理想的 Web 框架。開發這個 Web 伺服器的主要目的就是為了處理 FriendFeed 的實時功能 ——在 FriendFeed 的應用里每一個活動用戶都會保持著一個伺服器連接。(關於如何擴容伺服器,以處理數以千計的客戶端的連接的問題,請參閱 C10K problem。)

  請參見 Tornado 文檔 或 Tornado 原文文檔(鏡像)以詳細瞭解該 Web 框架。

下載和安裝

pip安裝
pip3 install tornado
 
源碼安裝
tar xvzf tornado-4.4.1.tar.gz
cd tornado-4.4.1
python setup.py build
sudo python setup.py install

源碼下載:tornado-1.2.1.tar.gz、 tornado-4.4.1.tar.gz

主要模塊
web - FriendFeed 使用的基礎 Web 框架,包含了 Tornado 的大多數重要的功能
escape - XHTML, JSON, URL 的編碼/解碼方法
database - 對 MySQLdb 的簡單封裝,使其更容易使用
template - 基於 Python 的 web 模板系統
httpclient - 非阻塞式 HTTP 客戶端,它被設計用來和 web 及 httpserver 協同工作
auth - 第三方認證的實現(包括 Google OpenID/OAuth、Facebook Platform、Yahoo BBAuth、FriendFeed OpenID/OAuth、Twitter OAuth)
locale - 針對本地化和翻譯的支持
options - 命令行和配置文件解析工具,針對伺服器環境做了優化

底層模塊
httpserver - 服務於 web 模塊的一個非常簡單的 HTTP 伺服器的實現
iostream - 對非阻塞式的 socket 的簡單封裝,以方便常用讀寫操作
ioloop - 核心的 I/O 迴圈
Tornado 各模塊

 

二、Hello, world

 "Hello, world" 及 Application settings 基本配置:

import tornado.ioloop
import tornado.web
# import uimodules as md
# import uimethods as mt

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.write("Hello, world")

settings = {
    'template_path': 'views',        # html文件
    'static_path': 'statics',        # 靜態文件(css,js,img)
    'static_url_prefix': '/statics/',# 靜態文件首碼
    'cookie_secret': 'suoning',      # cookie自定義字元串加鹽
    # 'xsrf_cookies': True,          # 防止跨站偽造
    # 'ui_methods': mt,              # 自定義UIMethod函數
    # 'ui_modules': md,              # 自定義UIModule類
}

application = tornado.web.Application([
    (r"/", MainHandler),
], **settings)

if __name__ == "__main__":
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()

 

三、方法

 1、處理程式和參數

  請求來時,程式會用正則匹配相應路由地址,並交付於 tornado.web.RequestHandler 的子類處理;子類會根據請求方式(post / get / delete ...)的不同調用並執行相應的方法,方法返回字元串內容併發送到瀏覽器。

self.write("<h1>Hello, World</h1>")    # html代碼直接寫在瀏覽器客戶端
self.render("index.html")  # 返回html文件,調用render_string(),內部其實是打開並讀取文件,返回內容
self.redirect("http://www.cnblogs.com/suoning",permanent=False) # 跳轉重定向,參數代表是否永久重定向

name = self.get_argument("name")       # 獲取客戶端傳入的參數值
name = self.get_arguments("name")      # 獲取多個值,類別形式
file = self.request.files["filename"]  # 獲取客戶端上傳的文件

raise tornado.web.HTTPError(403)       # 返回錯誤信息給客戶端

2、重寫 RequestHandler 的方法函數

對於一個請求的處理過程代碼調用次序如下:

  1. 程式為每一個請求創建一個 RequestHandler 對象;
  2. 程式調用 initialize() 函數,這個函數的參數是 Application 配置中的關鍵字參數定義。(initialize 方法是 Tornado 1.1 中新添加的,舊版本中你需要重寫 __init__ 以達到同樣的目的) initialize 方法一般只是把傳入的參數存到成員變數中,而不會產生一些輸出或者調用像 send_error 之類的方法。
  3. 程式調用 prepare()。無論使用了哪種 HTTP 方法,prepare 都會被調用到,因此這個方法通常會被定義在一個基類中,然後在子類中重用。prepare可以產生輸出信息。如果它調用了finish(或send_error` 等函數),那麼整個處理流程就此結束。
  4. 程式調用某個 HTTP 方法:例如 get()post()put() 等。如果 URL 的正則表達式模式中有分組匹配,那麼相關匹配會作為參數傳入方法。

 重寫 initialize() 函數(會在創建RequestHandler對象後調用):

class ProfileHandler(tornado.web.RequestHandler):

    def initialize(self,database):
        self.database = database

    def get(self):
        self.write("result:" + self.database)

application = tornado.web.Application([
    (r"/init", ProfileHandler, dict(database="database"))
])

 

四、模板引擎

Tornao中的模板語言和django中類似,模板引擎將模板文件載入記憶體,然後將數據嵌入其中,最終獲取到一個完整的字元串,再將字元串返回給請求者。

Tornado 的模板支持“控制語句”和“表達語句”,控制語句是使用 {% 和 %} 包起來的 例如 {% if len(items) > 2 %}。表達語句是使用 {{ 和 }} 包起來的,例如 {{ items[0] }}。

控制語句和對應的 Python 語句的格式基本完全相同。我們支持 ifforwhile 和 try,這些語句邏輯結束的位置需要用 {% end %} 做標記。還通過 extends 和 block 語句實現了模板繼承。這些在 template 模塊 的代碼文檔中有著詳細的描述。

註:在使用模板前需要在setting中設置模板路徑:"template_path" : "views"

 1、基本使用

#!/usr/bin/env python
# -*- coding:utf-8 -*-
  
import tornado.ioloop
import tornado.web
  
  
class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.render("index.html", list_info = [11,22,33])
  
application = tornado.web.Application([
    (r"/index", MainHandler),
])
  
  
if __name__ == "__main__":
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()
app.py
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <title>Nick</title>
</head>
<body>

    <div>
        <ul>
            {% for item in list_info %}
                <li>{{item}}</li>
            {% end %}
        </ul>
    </div>
    
</body>
</html>
index.html
在模板中預設提供了一些函數、欄位、類以供模板使用:

escape: tornado.escape.xhtml_escape 的別名
xhtml_escape: tornado.escape.xhtml_escape 的別名
url_escape: tornado.escape.url_escape 的別名
json_encode: tornado.escape.json_encode 的別名
squeeze: tornado.escape.squeeze 的別名
linkify: tornado.escape.linkify 的別名
datetime: Python 的 datetime 模組
handler: 當前的 RequestHandler 對象
request: handler.request 的別名
current_user: handler.current_user 的別名
locale: handler.locale 的別名
_: handler.locale.translate 的別名
static_url: for handler.static_url 的別名
xsrf_form_html: handler.xsrf_form_html 的別名
其他方法

2、母版

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <title>Nick</title>
    <link href="{{static_url("css/common.css")}}" rel="stylesheet" />
    {% block CSS %}{% end %}
</head>
<body>

    <div class="pg-header">

    </div>
    
    {% block RenderBody %}{% end %}
   
    <script src="{{static_url("js/jquery-1.8.2.min.js")}}"></script>
    
    {% block JavaScript %}{% end %}
</body>
</html>
layout.html
{% extends 'layout.html'%}
{% block CSS %}
    <link href="{{static_url("css/index.css")}}" rel="stylesheet" />
{% end %}

{% block RenderBody %}
    <h1>Index</h1>

    <ul>
    {%  for item in li %}
        <li>{{item}}</li>
    {% end %}
    </ul>

{% end %}

{% block JavaScript %}
    
{% end %}
index.html

3、導入

<div>
    <ul>
        <li>1024</li>
        <li>42區</li>
    </ul>
</div>
header.html
<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <title>Nick</title>
    <link href="{{static_url("css/common.css")}}" rel="stylesheet" />
</head>
<body>

    <div class="pg-header">
        {% include 'header.html' %}
    </div>
    
    <script src="{{static_url("js/jquery-1.8.2.min.js")}}"></script>
    
</body>
</html>
index.html

4、自定義UIMethod以UIModule

a.定義

# uimethods.py
 
def tab(self):
    return 'UIMethod'
uimethods.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from tornado.web import UIModule
from tornado import escape

class custom(UIModule):

    def render(self, *args, **kwargs):
        return escape.xhtml_escape('<h1>nick</h1>')
        #return escape.xhtml_escape('<h1>suoning</h1>')
uimodules.py

b.註冊

#!/usr/bin/env python
# -*- coding:utf-8 -*-

import tornado.ioloop
import tornado.web
from tornado.escape import linkify
import uimodules as md
import uimethods as mt

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        self.render('index.html')

settings = {
    'template_path': 'template',
    'static_path': 'static',
    'static_url_prefix': '/static/',
    'ui_methods': mt,
    'ui_modules': md,
}

application = tornado.web.Application([
    (r"/index", MainHandler),
], **settings)


if __name__ == "__main__":
    application.listen(8009)
    tornado.ioloop.IOLoop.instance().start()
View Code

c.使用

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title>Nick</title>
    <link href="{{static_url("commons.css")}}" rel="stylesheet" />
</head>
<body>
    <h1>hello</h1>
    {% module custom(123) %}
    {{ tab() }}
</body>
View Code

 

 五、靜態文件和主動式文件緩存

 在應用配置 settings 中指定 static_path 選項來提供靜態文件服務;

 在應用配置 settings 中指定 static_url_prefix 選項來提供靜態文件首碼服務;

 在導入靜態文件時用 {{static_url('XX.css')}} 方式實現主動緩存靜態文件

settings = {
    'template_path': 'views',
    'static_path': 'static',
    'static_url_prefix': '/static/',
}
<head lang="en">
    <title>Nick</title>
    <link href="{{static_url("commons.css")}}" rel="stylesheet" />
</head>

 

六、 Cookie

1、基本Cookie

set_cookie 方法在用戶的瀏覽中設置 cookie;

get_cookie 方法在用戶的瀏覽中獲取 cookie。

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        if not self.get_cookie("mycookie"):
            self.set_cookie("mycookie", "myvalue")
            self.write("Your cookie was not set yet!")
        else:
            self.write("Your cookie was set!")

2、加密Cookie(簽名)

 Cookie 很容易被惡意的客戶端偽造。加入你想在 cookie 中保存當前登陸用戶的 id 之類的信息,你需要對 cookie 作簽名以防止偽造。Tornado 通過 set_secure_cookie 和 get_secure_cookie 方法直接支持了這種功能。 要使用這些方法,你需要在創建應用時提供一個密鑰,名字為 cookie_secret。 你可以把它作為一個關鍵詞參數傳入應用的設置中:

class MainHandler(tornado.web.RequestHandler):
    def get(self):
        if not self.get_secure_cookie("mycookie"):
            self.set_secure_cookie("mycookie", "myvalue")
            self.write("Your cookie was not set yet!")
        else:
            self.write("Your cookie was set!")
             
application = tornado.web.Application([
    (r"/", MainHandler),
], cookie_secret="61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=")
def _create_signature_v1(secret, *parts):
    hash = hmac.new(utf8(secret), digestmod=hashlib.sha1)
    for part in parts:
        hash.update(utf8(part))
    return utf8(hash.hexdigest())

# 加密
def _create_signature_v2(secret, s):
    hash = hmac.new(utf8(secret), digestmod=hashlib.sha256)
    hash.update(utf8(s))
    return utf8(hash.hexdigest())

def create_signed_value(secret, name, value, version=None, clock=None,
                        key_version=None):
    if version is None:
        version = DEFAULT_SIGNED_VALUE_VERSION
    if clock is None:
        clock = time.time

    timestamp = utf8(str(int(clock())))
    value = base64.b64encode(utf8(value))
    if version == 1:
        signature = _create_signature_v1(secret, name, value, timestamp)
        value = b"|".join([value, timestamp, signature])
        return value
    elif version == 2:
        # The v2 format consists of a version number and a series of
        # length-prefixed fields "%d:%s", the last of which is a
        # signature, all separated by pipes.  All numbers are in
        # decimal format with no leading zeros.  The signature is an
        # HMAC-SHA256 of the whole string up to that point, including
        # the final pipe.
        #
        # The fields are:
        # - format version (i.e. 2; no length prefix)
        # - key version (integer, default is 0)
        # - timestamp (integer seconds since epoch)
        # - name (not encoded; assumed to be ~alphanumeric)
        # - value (base64-encoded)
        # - signature (hex-encoded; no length prefix)
        def format_field(s):
            return utf8("%d:" % len(s)) + utf8(s)
        to_sign = b"|".join([
            b"2",
            format_field(str(key_version or 0)),
            format_field(timestamp),
            format_field(name),
            format_field(value),
            b''])

        if isinstance(secret, dict):
            assert key_version is not None, 'Key version must be set when sign key dict is used'
            assert version >= 2, 'Version must be at least 2 for key version support'
            secret = secret[key_version]

        signature = _create_signature_v2(secret, to_sign)
        return to_sign + signature
    else:
        raise ValueError("Unsupported version %d" % version)

# 解密
def _decode_signed_value_v1(secret, name, value, max_age_days, clock):
    parts = utf8(value).split(b"|")
    if len(parts) != 3:
        return None
    signature = _create_signature_v1(secret, name, parts[0], parts[1])
    if not _time_independent_equals(parts[2], signature):
        gen_log.warning("Invalid cookie signature %r", value)
        return None
    timestamp = int(parts[1])
    if timestamp < clock() - max_age_days * 86400:
        gen_log.warning("Expired cookie %r", value)
        return None
    if timestamp > clock() + 31 * 86400:
        # _cookie_signature does not hash a delimiter between the
        # parts of the cookie, so an attacker could transfer trailing
        # digits from the payload to the timestamp without altering the
        # signature.  For backwards compatibility, sanity-check timestamp
        # here instead of modifying _cookie_signature.
        gen_log.warning("Cookie timestamp in future; possible tampering %r",
                        value)
        return None
    if parts[1].startswith(b"0"):
        gen_log.warning("Tampered cookie %r", value)
        return None
    try:
        return base64.b64decode(parts[0])
    except Exception:
        return None


def _decode_fields_v2(value):
    def _consume_field(s):
        length, _, rest = s.partition(b':')
        n = int(length)
        field_value = rest[:n]
        # In python 3, indexing bytes returns small integers; we must
        # use a slice to get a byte string as in python 2.
        if rest[n:n + 1] != b'|':
            raise ValueError("malformed v2 signed value field")
        rest = rest[n + 1:]
        return field_value, rest

    rest = value[2:]  # remove version number
    key_version, rest = _consume_field(rest)
    timestamp, rest = _consume_field(rest)
    name_field, rest = _consume_field(rest)
    value_field, passed_sig = _consume_field(rest)
    return int(key_version), timestamp, name_field, value_field, passed_sig


def _decode_signed_value_v2(secret, name, value, max_age_days, clock):
    try:
        key_version, timestamp, name_field, value_field, passed_sig = _decode_fields_v2(value)
    except ValueError:
        return None
    signed_string = value[:-len(passed_sig)]

    if isinstance(secret, dict):
        try:
            secret = secret[key_version]
        except KeyError:
            return None

    expected_sig = _create_signature_v2(secret, signed_string)
    if not _time_independent_equals(passed_sig, expected_sig):
        return None
    if name_field != utf8(name):
        return None
    timestamp = int(timestamp)
    if timestamp < clock() - max_age_days * 86400:
        # The signature has expired.
        return None
    try:
        return base64.b64decode(value_field)
    except Exception:
        return None


def get_signature_key_version(value):
    value = utf8(value)
    version = _get_version(value)
    if version < 2:
        return None
    try:
        key_version, _, _, _, _ = _decode_fields_v2(value)
    except ValueError:
        return None

    return key_version
內部演算法

加密Cookice的本質:

寫cookie過程:

  • 將值進行base64加密
  • 對除值以外的內容進行簽名,哈希演算法(無法逆向解析)
  • 拼接 簽名 + 加密值

讀cookie過程:

  • 讀取 簽名 + 加密值
  • 對簽名進行驗證
  • base64解密,獲取值內容

註:許多API驗證機制和安全cookie的實現機制相同。

#!/usr/bin/env python
# -*- coding:utf-8 -*-
 
import tornado.ioloop
import tornado.web
 
 
class MainHandler(tornado.web.RequestHandler):
 
    def get(self):
        login_user = self.get_secure_cookie("login_user", None)
        if login_user:
            self.write(login_user)
        else:
            self.redirect('/login')
 
 
class LoginHandler(tornado.web.RequestHandler):
    def get(self):
        self.current_user()
 
        self.render('login.html', **{'status': ''})
 
    def post(self, *args, **kwargs):
 
        username = self.get_argument('name')
        password = self.get_argument('pwd')
        if username == 'nick' and password == 'nicknick':
            self.set_secure_cookie('login_user', 'nick')
            self.redirect('/')
        else:
            self.render('login.html', **{'status': '用戶名或密碼錯誤'})
 
settings = {
    'template_path': 'template',
    'static_path': 'static',
    'static_url_prefix': '/static/',
    'cookie_secret': 'aiuasdhflashjdfoiuashdfiuh'
}
 
application = tornado.web.Application([
    (r"/index", MainHandler),
    (r"/login", LoginHandler),
], **settings)
 
 
if __name__ == "__main__":
    application.listen(8888)
    tornado.ioloop.IOLoop.instance().start()
基於Cookie實現用戶驗證-Demo
#!/usr/bin/env python
# -*- coding:utf-8 -*-
 
import tornado.ioloop


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 實例運行結果如下 實例運行結果如下 實例運行結果如下 ...
  • 詳細的見 https://github.com/linux-wang/DRF_tutorial/blob/master/README.md DRF中有一個serializer的概念,實現的功能是將各種Django Queryset和model instance轉換成Python原生格式,這裡就省去了 ...
  • 實力運行效果如下 ...
  • String類和StringBuffer類主要用來處理字元串,這兩個類提供了很多字元串的使用處理方法。String類是不可變類,表示對象所包含的字元串類不能改變。StringBuffer類是可變類,其對象所包含的字元串內容可以被添加或修改。 關於這兩個類處理字元串的常用方法請參考:http://ww ...
  • SpEL簡介與功能特性 Spring表達式語言(簡稱SpEL)是一個支持查詢併在運行時操縱一個對象圖的功能強大的表達式語言。SpEL語言的語法類似於統一EL,但提供了更多的功能,最主要的是顯式方法調用和基本字元串模板函數。 同很多可用的Java 表達式語言相比,例如OGNL,MVEL和JBoss E ...
  • 一、本來想說的是返回值處理問題,但在 SpringMVC 中,返回值處理問題的核心就是視圖渲染。所以這裡標題叫視圖渲染問題。 本來想在上一篇文章中對視圖解析進行說明的,但是通過源碼發現,它應該算到視圖渲染中,所以在這篇文章中進行說明。 org.springframework.web.servlet. ...
  • java API(Java Application Interface)是java的應用編程介面。它提供給java編程人員使用的程式介面,是java語言提供的已經實現的標準類的集合。Java API類庫與win32 中的dll文件有點像,封裝了很多函數,不提供具體的實體,只提供了方法名和參數等信息。 ...
  • 數組是程式的基本數據結構,數組操作是常用操作,Arrays類封裝了數組的一些常見操作,它有哪些方法?是怎麼實現的?多維數組是怎麼回事?排序方法的介面體現了怎樣的設計思維?Java是如何實現排序的?Arrays中的方法不夠用怎麼辦?... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...