microdot --- 極簡 HTTP 框架

Microdot 是一個小巧、受 Flask 啟發的 MicroPython HTTP 框架。它建構於 asyncio 之上,無需執行緒即可處理多個並行用戶端,並提供熟悉的路由裝飾器 API。一個最簡的應用程式看起來像這樣::

from microdot import Microdot

app = Microdot()

@app.route('/')
async def index(request):
    return 'Hello, world!'

app.run(host='0.0.0.0', port=80)

class Microdot

class microdot.Microdot

一個 HTTP 應用程式實例。通常在指令碼開頭附近建構一個實例,並以路由處理常式加以裝飾;呼叫 run() 或 await start_server() 即開始提供服務。

路由註冊

route(url_pattern: str, methods: list | None = None) Callable

用於為 url_pattern 在所列 HTTP 方法(預設 ['GET'])下註冊處理常式的裝飾器。回傳該裝飾器;當套用於某函式時,會將其註冊為處理常式並原樣回傳該函式。

url_pattern

一個路徑模式。支援靜態區段(/users)以及以 < / > 括起的動態區段。動態區段可接受以 : 分隔的選用型別前綴 —— <int:id><path:rest><re:[0-9a-f]+:hex>。預設型別為 string

methods

HTTP 方法名稱清單。若省略,則只比對 GET

呼叫處理常式時,第一個參數為請求物件,接著以關鍵字引數傳入所有擷取到的動態區段。處理常式的回傳值即成為 HTTP 回應:可以是 Response、字串、元組 (body, status_code[, headers]),或字典 / 清單(以 JSON 傳送)。

get(url_pattern: str) Callable

route(url_pattern, methods=['GET']) 的便利別名 —— 將被裝飾的函式註冊為 url_patternGET 處理常式。回傳該裝飾器。

post(url_pattern: str) Callable

route(url_pattern, methods=['POST']) 的便利別名 —— 將被裝飾的函式註冊為 url_patternPOST 處理常式。回傳該裝飾器。

put(url_pattern: str) Callable

route(url_pattern, methods=['PUT']) 的便利別名 —— 將被裝飾的函式註冊為 url_patternPUT 處理常式。回傳該裝飾器。

patch(url_pattern: str) Callable

route(url_pattern, methods=['PATCH']) 的便利別名 —— 將被裝飾的函式註冊為 url_patternPATCH 處理常式。回傳該裝飾器。

delete(url_pattern: str) Callable

route(url_pattern, methods=['DELETE']) 的便利別名 —— 將被裝飾的函式註冊為 url_patternDELETE 處理常式。回傳該裝飾器。

生命週期掛鉤

before_request(f: Callable) Callable

用於註冊 f 在每個請求之前執行的裝飾器。f 接收請求物件;其回傳值通常會被忽略。若要使請求短路,可回傳一個 Response(或可轉換為其的值)—— 此時管線的其餘部分會被略過,並送出該回應。回傳原樣的 f

after_request(f: Callable) Callable

用於註冊 f 在每個成功請求之後執行的裝飾器。f 接收 (request, response),並必須回傳(可能已修改的)回應物件。回傳原樣的 f

after_error_request(f: Callable) Callable

用於註冊 f 在 Microdot 產生錯誤回應(404、500、引發的例外等)之後執行的裝飾器。f 接收 (request, response),並必須回傳(可能已修改的)回應物件。回傳原樣的 f

errorhandler(status_code_or_exception_class) Callable

用於為某個 HTTP 狀態碼或某個 Python 例外類別註冊自訂處理常式的裝飾器。對於狀態碼,處理常式只接收請求;對於例外類別,則接收 (request, exception)。回傳該裝飾器。

掛載與中止

mount(subapp: Microdot, url_prefix: str = '', local: bool = False) None

將另一個 Microdot 實例的路由附掛到 url_prefix 之下。當 localFalse(預設)時,子應用程式的 before / after / error 處理常式也會附掛到父應用程式。當為 True 時,那些處理常式只會對子應用程式的路由執行。回傳 None

static abort(status_code: int, reason: str | None = None) None

引發 HTTPException 以指定狀態碼中止目前的請求。永遠不會正常回傳 —— 框架會捕捉該例外並將其轉換為對應的錯誤回應。在路由主體內使用很方便::

from microdot import abort

@app.get('/users/<int:id>')
def get_user(request, id):
    user = lookup(id)
    if user is None:
        abort(404)
    return user.to_dict()

執行伺服器

run(host: str = '0.0.0.0', port: int = 5000, debug: bool = False, ssl=None) None

阻塞呼叫端的執行緒,對 start_server() 執行 asyncio.run()。只有在 shutdown() 被呼叫且監聽迴圈結束之後才會回傳 None。這是啟動獨立應用程式最簡單的方式::

app.run(host='0.0.0.0', port=80)
async start_server(host: str = '0.0.0.0', port: int = 5000, debug: bool = False, ssl=None, start_serving: bool = True) None

以協程方式啟動伺服器。當需要與其他任務一起整合進既有的 asyncio 事件迴圈時使用此方法。在 shutdown() 被呼叫之前,此協程不會回傳::

async def main():
    await asyncio.gather(
        app.start_server(port=80),
        capture_loop(),
    )
host

要監聽的網路介面。'0.0.0.0'(預設)表示所有介面;'127.0.0.1' 表示僅限回送位址。

port

要監聽的 TCP 連接埠。預設 5000 —— 純 HTTP 請選 80,HTTPS 則選 443

debug

若為 True,則記錄每個請求並將追蹤回溯傾印到 stdout。

ssl

一個 ssl.SSLContext,用於以 TLS 包裝傳入的連線。None(預設)表示純 HTTP。

start_serving

僅在 CPython 上有意義;在 MicroPython 上會被忽略。

shutdown() None

請求伺服器優雅地關閉。可安全地從路由處理常式中呼叫 —— 目前的請求會在迴圈結束前完成。會立即回傳;實際的關閉會在進行中的請求結束後發生。

屬性

url_map: list

已註冊路由的清單,以 (methods, URLPattern, handler, url_prefix, subapp) 元組表示。

before_request_handlers: list

before_request() 註冊的可呼叫物件清單,依註冊順序排列。每一個都會在路由處理常式之前以請求物件執行。直接修改此清單雖可行但很少需要 —— 建議改用裝飾器。

after_request_handlers: list

after_request() 註冊的可呼叫物件清單,依註冊順序排列。每一個都會在成功的請求之後以 (request, response) 執行,並必須回傳(可能已修改的)回應。

after_error_request_handlers: list

after_error_request() 註冊的可呼叫物件清單,依註冊順序排列。每一個都會在錯誤回應產生後(由框架或由應用程式錯誤處理常式產生)以 (request, response) 執行,並必須回傳該回應。

error_handlers: dict

錯誤鍵到處理常式可呼叫物件的對應,由 errorhandler() 填入。鍵可以是 HTTP 狀態碼(int)或 Python 例外類別;值則是已註冊的處理常式。狀態碼處理常式接收 (request);例外類別處理常式接收 (request, exception)

debug: bool

當伺服器以 debug=True 執行時為 True

class Request

class microdot.Request

一個傳入的 HTTP 請求。實例會作為第一個位置引數傳給路由處理常式。應用程式不會直接建構 Request

類別屬性

max_content_length: int

對於 Content-Length 超過此位元組數的請求,以 413 回應加以拒絕。預設 16 KB。

max_body_length: int

會被緩衝到記憶體中並透過 body 公開的最大主體。較大的主體(最多達 max_content_length)會保留在 socket 上,必須透過 stream 讀取。預設 16 KB。

max_readline: int

單一請求行 / 標頭行的最大長度(以位元組計)。預設 2 KB。

實例屬性

app: Microdot

處理此請求的 Microdot 實例。

client_addr: tuple

用戶端的位址,以 (host, port) 表示。

method: str

HTTP 方法字串('GET''POST' ...)。

scheme: str

'http''https'

url: str

完整的請求 URL 路徑與查詢字串(主機之後的全部內容)。

path: str

僅路徑部分。

query_string: str | None

原始的查詢字串部分,或 None

args: MultiDict

解析後的查詢字串,以 MultiDict 表示。

headers: NoCaseDict

請求標頭,以 NoCaseDict 表示(不區分大小寫查詢)。

cookies: dict

解析後的 Cookie 標頭,以 dict 表示。

content_length: int

整數的 Content-Length 值,若不存在則為 0。

content_type: str | None

Content-Type 標頭值,或 None

g: object

一個自由格式的每請求容器(一個裸物件)。可在其上指派屬性,以在掛鉤與處理常式之間傳遞值(request.g.user = ...)。

route: Callable

比對到此請求的處理常式函式。

url_prefix: str

路由被掛載時所用的前綴,或 ''

subapp: Microdot | None

掛載的子應用程式實例,或 None

主體存取

body: bytes

完整的請求主體,以 bytes 表示。當主體正以串流方式處理時為空(參見 stream)。

stream: object

一個非同步串流物件,透過其在主體上提供 read()。對於大於 max_body_length 的主體請使用此屬性。

json: dict | list | str | int | float | bool | None

解析為 JSON 的主體(dictliststrintfloatbool —— 視酬載編碼而定),若 Content-Type 不是 application/json 則為 None

form: MultiDict | None

URL 編碼的表單欄位,以 MultiDict 表示,或 None。對於 multipart/form-data,請以 microdot.multipart.with_form_data() 裝飾該路由。

files: dict | None

上傳的檔案,以 {name: FileUpload} 表示,由 microdot.multipart.with_form_data() 填入。在該裝飾器執行之前為 None

after_request(f: Callable) Callable

註冊一個請求本地的 after-request 掛鉤 —— 在應用程式層級的 after-request 處理常式之後執行,且僅在成功時執行。f 接收 (request, response),並必須回傳(可能已修改的)回應物件。回傳原樣的 f

class Response

class microdot.Response(body=b'', status_code: int = 200, headers: dict | None = None, reason: str | None = None)

一個 HTTP 回應。多數處理常式會回傳 Microdot 自動轉換為 Response 的值;當需要自訂標頭或特定狀態碼時,可直接建構一個。

body

回應主體。str 會以 UTF-8 編碼;dict / list 會以 JSON 編碼並相應設定 Content-Typebytes 會原樣傳送;類檔案物件或非同步產生器則以串流方式傳送。

status_code

數值 HTTP 狀態。預設 200。

headers

回應標頭的字典(不區分大小寫)。

reason

自訂原因短語。對 200 預設為 "OK",否則預設為 "N/A"

類別屬性

default_content_type: str

未明確設定時所使用的 Content-Type。預設 'text/plain'

default_send_file_max_age: int | None

當未提供 max_age 時,send_file() 所用的 Cache-Control: max-age 值。None(預設)表示不加 Cache-Control 標頭。

send_file_buffer_size: int

send_file() 串流時的區塊大小。預設 1024。

already_handled: None

由已直接寫出回應的處理常式所回傳的哨兵值(供 WebSocket 升級使用)。永遠為 None;重點在於其身分識別。

types_map: dict

send_file() 用於內容型別推斷的副檔名對 mime-type 對應。對應 cssgifhtmljpgjsjsonpngtxtsvg

方法

新增一個 Set-Cookie 標頭。expires 可以是預先格式化的字串,或具有 timetuple() 的類 datetime 物件。回傳 None;該標頭會就地附加到此回應上。

設定一個使指定 cookie 立即過期的 Set-Cookiekwargs 接受與 set_cookie() 相同的選項(pathdomainsecurehttp_onlypartitioned);expiresmax_age 會被忽略。回傳 None;該標頭會就地附加到此回應上。

classmethod redirect(location: str, status_code: int = 302) Response

回傳一個重新導向回應::

@app.get('/old')
def old(request):
    return Response.redirect('/new')
classmethod send_file(filename: str, status_code: int = 200, content_type: str | None = None, stream=None, max_age: int | None = None, compressed: bool | str = False, file_extension: str = '') Response

將檔案系統中的某個檔案以串流方式作為回應主體傳送。若未提供 content_type,會透過 types_map 由副檔名推斷。compressed=True 會設定 Content-Encoding: gzip(該檔案必須已是壓縮過的)。

警告

filename 會被直接開啟。切勿傳入未經淨化的使用者提供路徑 —— 這麼做會允許任意檔案外洩。

例外

exception microdot.HTTPException

abort() 引發,以指定狀態碼使請求短路。Microdot 會捕捉此例外並將其轉換為對應的錯誤回應。

status_code: int

要回傳的數值 HTTP 狀態碼 —— 即傳給 abort() 的值。

reason: str

要包含在錯誤回應中的原因短語。若未傳給 abort(),則預設為 "<status_code> error"(例如 "404 error")。

class URLPattern

class microdot.URLPattern(url_pattern: str)

路由 URL 模式的編譯形式。由 Microdot.route() 自動建構;應用程式很少會直接實例化它。

classmethod register_type(type_name: str, pattern: str = '[^/]+', parser: Callable | None = None) None

註冊一個新的動態區段型別,以供路由模式中使用。回傳 None;該型別會被加入類別層級的型別註冊表。例如要新增一個 <uuid:...> 型別::

URLPattern.register_type('uuid', '[0-9a-f-]{36}')

parser 是一個選用的可呼叫物件,會在擷取到的字串送達處理常式之前對其進行轉換。

match(path: str) dict | None

path 與該模式比對,並回傳擷取群組的字典,若無符合則回傳 None

輔助類別

class microdot.MultiDict(initial_dict: dict | None = None)

一個可為每個鍵儲存多個值的 dict 子類別。用於查詢字串以及 URL 編碼的表單主體,這些情況下同一個鍵可能出現多次(?tag=a&tag=b)。

get(key, default=None, type: Callable | None = None)

回傳 key 的第一個值,並可選擇以 type(一個可呼叫物件)轉換。若鍵不存在或轉換失敗,則回傳 default;否則回傳(可選擇已轉換的)值。

getlist(key, type: Callable | None = None) list

以清單回傳 key 的所有值,並可選擇對每個值以 type 進行轉換。若 key 不存在,則回傳空清單。

class microdot.NoCaseDict

一個具有不區分大小寫字串鍵的 dict 子類別。用於 Request.headersResponse.headers

模組層級函式

microdot.abort(status_code: int, reason: str | None = None) None

Microdot.abort() 的捷徑。永遠不會正常回傳 —— 會引發 HTTPException。可透過 from microdot import abort 匯入。

microdot.redirect(location: str, status_code: int = 302) Response

Response.redirect() 的捷徑。可透過 from microdot import redirect 匯入。

microdot.send_file(filename: str, **kwargs) Response

Response.send_file() 的捷徑。

microdot.urlencode(s: str) str

對 URL 元件進行百分比編碼。將在 URL 中具有保留意義的字元(/?&=#、空格 ...)替換為其 %xx 十六進位逸出,使結果能安全地置於路徑區段或查詢值之中。回傳編碼後的 str

microdot.urldecode(s: str) str

對 URL 元件進行百分比解碼 —— 即 urlencode() 的逆運算。%xx 逸出會被替換為其所編碼的位元組,而 + 會被轉換為空格(歷史上的查詢字串慣例)。回傳解碼後的 str

子模組