10 KiB
自訂回應——HTML、串流、檔案與其他
預設情況下,FastAPI 會回傳 JSON 回應。
你可以像在直接回傳 Response中所示,直接回傳一個 Response 來覆寫它。
但如果你直接回傳一個 Response(或其子類別,例如 JSONResponse),資料將不會被自動轉換(即使你宣告了 response_model),而且文件也不會自動產生(例如,在產生的 OpenAPI 中包含 HTTP 標頭 Content-Type 的特定「media type」)。
你也可以在「路徑操作裝飾器」中使用 response_class 參數,宣告要使用的 Response(例如任意 Response 子類別)。
你從「路徑操作函式」回傳的內容,會被放進該 Response 中。
/// note
若你使用的回應類別沒有 media type,FastAPI 會假設你的回應沒有內容,因此不會在產生的 OpenAPI 文件中記錄回應格式。
///
JSON 回應
FastAPI 預設回傳 JSON 回應。
如果你宣告了回應模型,FastAPI 會使用 Pydantic 將資料序列化為 JSON。
如果你沒有宣告回應模型,FastAPI 會使用在JSON 相容編碼器中解釋的 jsonable_encoder,並將結果放進 JSONResponse。
如果你宣告的 response_class 具有 JSON 的 media type(application/json),像 JSONResponse,你回傳的資料會自動以你在「路徑操作裝飾器」中宣告的任何 Pydantic response_model 進行轉換(與過濾)。但資料不會由 Pydantic 直接序列化成 JSON 位元組;取而代之,會先經由 jsonable_encoder 轉換,然後交給 JSONResponse 類別,該類別會使用 Python 標準的 JSON 函式庫將其序列化為位元組。
JSON 效能
簡而言之,若你想要最佳效能,請使用回應模型,並且不要在「路徑操作裝飾器」中宣告 response_class。
{* ../../docs_src/response_model/tutorial001_01_py310.py ln[15:17] hl[16] *}
HTML 回應
要直接從 FastAPI 回傳 HTML,使用 HTMLResponse。
- 匯入
HTMLResponse。 - 在「路徑操作裝飾器」中,將
HTMLResponse傳給response_class參數。
{* ../../docs_src/custom_response/tutorial002_py310.py hl[2,7] *}
/// info
參數 response_class 也會用來定義回應的「media type」。
在此情況下,HTTP 標頭 Content-Type 會被設為 text/html。
而且它會以此形式被記錄到 OpenAPI 中。
///
回傳 Response
如直接回傳 Response所示,你也可以在「路徑操作」中直接回傳以覆寫回應。
上面的相同範例,回傳 HTMLResponse,可以像這樣:
{* ../../docs_src/custom_response/tutorial003_py310.py hl[2,7,19] *}
/// warning
由你的「路徑操作函式」直接回傳的 Response 不會被記錄進 OpenAPI(例如不會記錄 Content-Type),也不會出現在自動產生的互動式文件中。
///
/// info
當然,實際的 Content-Type 標頭、狀態碼等,會來自你回傳的 Response 物件。
///
在 OpenAPI 中文件化並覆寫 Response
如果你想在函式內覆寫回應,同時又要在 OpenAPI 中記錄「media type」,你可以同時使用 response_class 參數並回傳一個 Response 物件。
此時,response_class 只會用於記錄該 OpenAPI「路徑操作」,而你回傳的 Response 將會如實使用。
直接回傳 HTMLResponse
例如,可能會像這樣:
{* ../../docs_src/custom_response/tutorial004_py310.py hl[7,21,23] *}
在這個例子中,函式 generate_html_response() 已經產生並回傳了一個 Response,而不是把 HTML 當作 str 回傳。
透過回傳 generate_html_response() 的結果,你其實已經回傳了一個 Response,這會覆寫 FastAPI 的預設行為。
但因為你同時也在 response_class 中傳入了 HTMLResponse,FastAPI 便能在 OpenAPI 與互動式文件中,將其以 text/html 的 HTML 形式記錄:
可用的回應
以下是一些可用的回應類別。
記得你可以用 Response 回傳其他任何東西,甚至建立自訂的子類別。
/// note | 技術細節
你也可以使用 from starlette.responses import HTMLResponse。
FastAPI 將 starlette.responses 以 fastapi.responses 提供給你(開發者)做為方便之用。但大多數可用的回應其實直接來自 Starlette。
///
Response
主要的 Response 類別,其他回應皆繼承自它。
你也可以直接回傳它。
它接受以下參數:
content-str或bytes。status_code-int類型的 HTTP 狀態碼。headers- 由字串組成的dict。media_type- 描述 media type 的str。例如"text/html"。
FastAPI(實際上是 Starlette)會自動包含 Content-Length 標頭。也會根據 media_type(並為文字型別附加 charset)包含 Content-Type 標頭。
{* ../../docs_src/response_directly/tutorial002_py310.py hl[1,18] *}
HTMLResponse
接收文字或位元組並回傳 HTML 回應,如上所述。
PlainTextResponse
接收文字或位元組並回傳純文字回應。
{* ../../docs_src/custom_response/tutorial005_py310.py hl[2,7,9] *}
JSONResponse
接收資料並回傳 application/json 編碼的回應。
這是 FastAPI 的預設回應,如上所述。
/// note | 技術細節
但如果你宣告了回應模型或回傳型別,將會直接用它來把資料序列化為 JSON,並直接回傳具有正確 JSON media type 的回應,而不會使用 JSONResponse 類別。
這是取得最佳效能的理想方式。
///
RedirectResponse
回傳一個 HTTP 重新導向。預設使用 307 狀態碼(Temporary Redirect)。
你可以直接回傳 RedirectResponse:
{* ../../docs_src/custom_response/tutorial006_py310.py hl[2,9] *}
或者你可以在 response_class 參數中使用它:
{* ../../docs_src/custom_response/tutorial006b_py310.py hl[2,7,9] *}
若這麼做,你就可以在「路徑操作函式」中直接回傳 URL。
在此情況下,所使用的 status_code 會是 RedirectResponse 的預設值 307。
你也可以同時搭配 status_code 與 response_class 參數:
{* ../../docs_src/custom_response/tutorial006c_py310.py hl[2,7,9] *}
StreamingResponse
接收一個 async 產生器或一般的產生器/疊代器(帶有 yield 的函式),並以串流方式傳送回應本文。
{* ../../docs_src/custom_response/tutorial007_py310.py hl[3,16] *}
/// note | 技術細節
一個 async 任務只能在抵達某個 await 時才能被取消。如果沒有 await,該產生器(帶有 yield 的函式)將無法被正確取消,甚至在請求取消後仍可能持續執行。
因為這個小範例不需要任何 await 陳述式,我們加入 await anyio.sleep(0),讓事件迴圈有機會處理取消。
對於大型或無限的串流來說,這點更為重要。
///
/// tip
與其直接回傳 StreamingResponse,你大概會想遵循資料串流中的作法,這樣更方便,並且會在底層幫你處理取消。
如果你要串流 JSON Lines,請參考教學:串流 JSON Lines。
///
FileResponse
以非同步串流方式將檔案作為回應。
它在初始化時所需的參數與其他回應型別不同:
path- 要串流的檔案路徑。headers- 要包含的自訂標頭,字典形式。media_type- 描述 media type 的字串。若未設定,將根據檔名或路徑推斷 media type。filename- 若設定,會包含在回應的Content-Disposition中。
檔案回應會包含適當的 Content-Length、Last-Modified 與 ETag 標頭。
{* ../../docs_src/custom_response/tutorial009_py310.py hl[2,10] *}
你也可以使用 response_class 參數:
{* ../../docs_src/custom_response/tutorial009b_py310.py hl[2,8,10] *}
在此情況下,你可以在「路徑操作函式」中直接回傳檔案路徑。
自訂回應類別
你可以建立自己的自訂回應類別,繼承自 Response 並加以使用。
例如,假設你要使用 orjson 並套用一些設定。
假設你想回傳縮排且格式化的 JSON,因此要使用 orjson 選項 orjson.OPT_INDENT_2。
你可以建立 CustomORJSONResponse。你主要需要做的是建立一個 Response.render(content) 方法,將內容以 bytes 回傳:
{* ../../docs_src/custom_response/tutorial009c_py310.py hl[9:14,17] *}
現在,不再是回傳:
{"message": "Hello World"}
...這個回應會回傳:
{
"message": "Hello World"
}
當然,你大概能找到比格式化 JSON 更好的方式來利用這個能力。😉
orjson 或回應模型
如果你追求效能,使用回應模型 大概會比使用 orjson 回應更好。
有了回應模型,FastAPI 會使用 Pydantic 直接將資料序列化為 JSON,而不需要像其他情況那樣先經過 jsonable_encoder 之類的中介步驟。
而且在底層,Pydantic 用來序列化為 JSON 的 Rust 機制和 orjson 相同,因此用回應模型已經能獲得最佳效能。
預設回應類別
在建立 FastAPI 類別實例或 APIRouter 時,你可以指定預設要使用哪個回應類別。
用來設定的是 default_response_class 參數。
在下面的例子中,FastAPI 會在所有「路徑操作」中預設使用 HTMLResponse,而不是 JSON。
{* ../../docs_src/custom_response/tutorial010_py310.py hl[2,4] *}
/// tip
你仍然可以在「路徑操作」中像以前一樣覆寫 response_class。
///
其他文件化選項
你也可以在 OpenAPI 中使用 responses 宣告 media type 與其他許多細節:在 OpenAPI 中的額外回應。