Skip to content

自訂回應——HTML、串流、檔案與其他

🌐 AI 與人類共同完成的翻譯

此翻譯由人類指導的 AI 完成。🤝

可能會有對原意的誤解,或讀起來不自然等問題。🤖

你可以透過協助我們更好地引導 AI LLM來改進此翻譯。

英文版

預設情況下,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

# Code above omitted 👆

@app.post("/items/")
async def create_item(item: Item) -> Item:
    return item

# Code below omitted 👇
👀 Full file preview
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str
    description: str | None = None
    price: float
    tax: float | None = None
    tags: list[str] = []


@app.post("/items/")
async def create_item(item: Item) -> Item:
    return item


@app.get("/items/")
async def read_items() -> list[Item]:
    return [
        Item(name="Portal Gun", price=42.0),
        Item(name="Plumbus", price=32.0),
    ]

HTML 回應

要直接從 FastAPI 回傳 HTML,使用 HTMLResponse

  • 匯入 HTMLResponse
  • 在「路徑操作裝飾器」中,將 HTMLResponse 傳給 response_class 參數。
from fastapi import FastAPI
from fastapi.responses import HTMLResponse

app = FastAPI()


@app.get("/items/", response_class=HTMLResponse)
async def read_items():
    return """
    <html>
        <head>
            <title>Some HTML in here</title>
        </head>
        <body>
            <h1>Look ma! HTML!</h1>
        </body>
    </html>
    """

Info

參數 response_class 也會用來定義回應的「media type」。

在此情況下,HTTP 標頭 Content-Type 會被設為 text/html

而且它會以此形式被記錄到 OpenAPI 中。

回傳 Response

直接回傳 Response所示,你也可以在「路徑操作」中直接回傳以覆寫回應。

上面的相同範例,回傳 HTMLResponse,可以像這樣:

from fastapi import FastAPI
from fastapi.responses import HTMLResponse

app = FastAPI()


@app.get("/items/")
async def read_items():
    html_content = """
    <html>
        <head>
            <title>Some HTML in here</title>
        </head>
        <body>
            <h1>Look ma! HTML!</h1>
        </body>
    </html>
    """
    return HTMLResponse(content=html_content, status_code=200)

Warning

由你的「路徑操作函式」直接回傳的 Response 不會被記錄進 OpenAPI(例如不會記錄 Content-Type),也不會出現在自動產生的互動式文件中。

Info

當然,實際的 Content-Type 標頭、狀態碼等,會來自你回傳的 Response 物件。

在 OpenAPI 中文件化並覆寫 Response

如果你想在函式內覆寫回應,同時又要在 OpenAPI 中記錄「media type」,你可以同時使用 response_class 參數並回傳一個 Response 物件。

此時,response_class 只會用於記錄該 OpenAPI「路徑操作」,而你回傳的 Response 將會如實使用。

直接回傳 HTMLResponse

例如,可能會像這樣:

from fastapi import FastAPI
from fastapi.responses import HTMLResponse

app = FastAPI()


def generate_html_response():
    html_content = """
    <html>
        <head>
            <title>Some HTML in here</title>
        </head>
        <body>
            <h1>Look ma! HTML!</h1>
        </body>
    </html>
    """
    return HTMLResponse(content=html_content, status_code=200)


@app.get("/items/", response_class=HTMLResponse)
async def read_items():
    return generate_html_response()

在這個例子中,函式 generate_html_response() 已經產生並回傳了一個 Response,而不是把 HTML 當作 str 回傳。

透過回傳 generate_html_response() 的結果,你其實已經回傳了一個 Response,這會覆寫 FastAPI 的預設行為。

但因為你同時也在 response_class 中傳入了 HTMLResponseFastAPI 便能在 OpenAPI 與互動式文件中,將其以 text/html 的 HTML 形式記錄:

可用的回應

以下是一些可用的回應類別。

記得你可以用 Response 回傳其他任何東西,甚至建立自訂的子類別。

技術細節

你也可以使用 from starlette.responses import HTMLResponse

FastAPIstarlette.responsesfastapi.responses 提供給你(開發者)做為方便之用。但大多數可用的回應其實直接來自 Starlette。

Response

主要的 Response 類別,其他回應皆繼承自它。

你也可以直接回傳它。

它接受以下參數:

  • content - strbytes
  • status_code - int 類型的 HTTP 狀態碼。
  • headers - 由字串組成的 dict
  • media_type - 描述 media type 的 str。例如 "text/html"

FastAPI(實際上是 Starlette)會自動包含 Content-Length 標頭。也會根據 media_type(並為文字型別附加 charset)包含 Content-Type 標頭。

from fastapi import FastAPI, Response

app = FastAPI()


@app.get("/legacy/")
def get_legacy_data():
    data = """<?xml version="1.0"?>
    <shampoo>
    <Header>
        Apply shampoo here.
    </Header>
    <Body>
        You'll have to use soap here.
    </Body>
    </shampoo>
    """
    return Response(content=data, media_type="application/xml")

HTMLResponse

接收文字或位元組並回傳 HTML 回應,如上所述。

PlainTextResponse

接收文字或位元組並回傳純文字回應。

from fastapi import FastAPI
from fastapi.responses import PlainTextResponse

app = FastAPI()


@app.get("/", response_class=PlainTextResponse)
async def main():
    return "Hello World"

JSONResponse

接收資料並回傳 application/json 編碼的回應。

這是 FastAPI 的預設回應,如上所述。

技術細節

但如果你宣告了回應模型或回傳型別,將會直接用它來把資料序列化為 JSON,並直接回傳具有正確 JSON media type 的回應,而不會使用 JSONResponse 類別。

這是取得最佳效能的理想方式。

RedirectResponse

回傳一個 HTTP 重新導向。預設使用 307 狀態碼(Temporary Redirect)。

你可以直接回傳 RedirectResponse

from fastapi import FastAPI
from fastapi.responses import RedirectResponse

app = FastAPI()


@app.get("/typer")
async def redirect_typer():
    return RedirectResponse("https://typer.tiangolo.com")

或者你可以在 response_class 參數中使用它:

from fastapi import FastAPI
from fastapi.responses import RedirectResponse

app = FastAPI()


@app.get("/fastapi", response_class=RedirectResponse)
async def redirect_fastapi():
    return "https://fastapi.tiangolo.com"

若這麼做,你就可以在「路徑操作函式」中直接回傳 URL。

在此情況下,所使用的 status_code 會是 RedirectResponse 的預設值 307


你也可以同時搭配 status_coderesponse_class 參數:

from fastapi import FastAPI
from fastapi.responses import RedirectResponse

app = FastAPI()


@app.get("/pydantic", response_class=RedirectResponse, status_code=302)
async def redirect_pydantic():
    return "https://docs.pydantic.dev/"

StreamingResponse

接收一個 async 產生器或一般的產生器/疊代器(帶有 yield 的函式),並以串流方式傳送回應本文。

import anyio
from fastapi import FastAPI
from fastapi.responses import StreamingResponse

app = FastAPI()


async def fake_video_streamer():
    for i in range(10):
        yield b"some fake video bytes"
        await anyio.sleep(0)


@app.get("/")
async def main():
    return StreamingResponse(fake_video_streamer())

技術細節

一個 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-LengthLast-ModifiedETag 標頭。

from fastapi import FastAPI
from fastapi.responses import FileResponse

some_file_path = "large-video-file.mp4"
app = FastAPI()


@app.get("/")
async def main():
    return FileResponse(some_file_path)

你也可以使用 response_class 參數:

from fastapi import FastAPI
from fastapi.responses import FileResponse

some_file_path = "large-video-file.mp4"
app = FastAPI()


@app.get("/", response_class=FileResponse)
async def main():
    return some_file_path

在此情況下,你可以在「路徑操作函式」中直接回傳檔案路徑。

自訂回應類別

你可以建立自己的自訂回應類別,繼承自 Response 並加以使用。

例如,假設你要使用 orjson 並套用一些設定。

假設你想回傳縮排且格式化的 JSON,因此要使用 orjson 選項 orjson.OPT_INDENT_2

你可以建立 CustomORJSONResponse。你主要需要做的是建立一個 Response.render(content) 方法,將內容以 bytes 回傳:

from typing import Any

import orjson
from fastapi import FastAPI, Response

app = FastAPI()


class CustomORJSONResponse(Response):
    media_type = "application/json"

    def render(self, content: Any) -> bytes:
        assert orjson is not None, "orjson must be installed"
        return orjson.dumps(content, option=orjson.OPT_INDENT_2)


@app.get("/", response_class=CustomORJSONResponse)
async def main():
    return {"message": "Hello World"}

現在,不再是回傳:

{"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。

from fastapi import FastAPI
from fastapi.responses import HTMLResponse

app = FastAPI(default_response_class=HTMLResponse)


@app.get("/items/")
async def read_items():
    return "<h1>Items</h1><p>This is a list of items.</p>"

Tip

你仍然可以在「路徑操作」中像以前一樣覆寫 response_class

其他文件化選項

你也可以在 OpenAPI 中使用 responses 宣告 media type 與其他許多細節:在 OpenAPI 中的額外回應