Skip to content

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

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

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

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

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

英文版

預設情況下,FastAPI 會使用 JSONResponse 傳回回應。

你可以像在直接回傳 Response 中所示,直接回傳一個 Response 來覆寫它。

但如果你直接回傳一個 Response(或其子類別,如 JSONResponse),資料將不會被自動轉換(即使你宣告了 response_model),而且文件也不會自動產生(例如,在產生的 OpenAPI 中包含 HTTP 標頭 Content-Type 的特定「media type」)。

你也可以在「路徑操作裝飾器」中使用 response_class 參數,宣告要使用的 Response(例如任意 Response 子類別)。

你從「路徑操作函式」回傳的內容,會被放進該 Response 中。

若該 Response 的 media type 是 JSON(application/json),像 JSONResponseUJSONResponse,則你回傳的資料會自動以你在「路徑操作裝飾器」中宣告的 Pydantic response_model 進行轉換(與過濾)。

Note

若你使用的回應類別沒有 media type,FastAPI 會假設你的回應沒有內容,因此不會在產生的 OpenAPI 文件中記錄回應格式。

使用 ORJSONResponse

例如,若你在追求效能,你可以安裝並使用 orjson,並將回應設為 ORJSONResponse

匯入你想使用的 Response 類別(子類),並在「路徑操作裝飾器」中宣告它。

對於大型回應,直接回傳 Response 會比回傳 dict 快得多。

這是因為預設情況下,FastAPI 會檢查每個項目並確認它能被序列化為 JSON,使用與教學中說明的相同JSON 相容編碼器。這使你可以回傳「任意物件」,例如資料庫模型。

但如果你確定你回傳的內容「可以用 JSON 序列化」,你可以直接將它傳給回應類別,避免 FastAPI 在把你的回傳內容交給回應類別之前,先經過 jsonable_encoder 所帶來的額外開銷。

from fastapi import FastAPI
from fastapi.responses import ORJSONResponse

app = FastAPI()


@app.get("/items/", response_class=ORJSONResponse)
async def read_items():
    return ORJSONResponse([{"item_id": "Foo"}])

Info

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

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

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

Tip

ORJSONResponse 只在 FastAPI 中可用,在 Starlette 中不可用。

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 的預設回應,如上所述。

ORJSONResponse

使用 orjson 的快速替代 JSON 回應,如上所述。

Info

這需要安裝 orjson,例如使用 pip install orjson

UJSONResponse

使用 ujson 的替代 JSON 回應。

Info

這需要安裝 ujson,例如使用 pip install ujson

Warning

ujson 在處理某些邊界情況時,沒那麼嚴謹,較 Python 內建實作更「隨意」。

from fastapi import FastAPI
from fastapi.responses import UJSONResponse

app = FastAPI()


@app.get("/items/", response_class=UJSONResponse)
async def read_items():
    return [{"item_id": "Foo"}]

Tip

ORJSONResponse 可能是更快的替代方案。

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 產生器或一般的產生器/疊代器,並以串流方式傳送回應本文。

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"


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

對「類檔案物件」使用 StreamingResponse

如果你有一個類檔案(file-like)物件(例如 open() 回傳的物件),你可以建立一個產生器函式來疊代該類檔案物件。

如此一來,你不必先把它全部讀進記憶體,就能將那個產生器函式傳給 StreamingResponse 並回傳。

這也包含許多用於雲端儲存、影像/影音處理等的函式庫。

from fastapi import FastAPI
from fastapi.responses import StreamingResponse

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


@app.get("/")
def main():
    def iterfile():  # (1)
        with open(some_file_path, mode="rb") as file_like:  # (2)
            yield from file_like  # (3)

    return StreamingResponse(iterfile(), media_type="video/mp4")
  1. 這是產生器函式。因為它內含 yield 陳述式,所以是「產生器函式」。
  2. 透過 with 區塊,我們確保在產生器函式結束後關閉類檔案物件。因此,在完成傳送回應後就會關閉。
  3. 這個 yield from 告訴函式去疊代名為 file_like 的東西。對於每個被疊代到的部分,就把該部分當作此產生器函式(iterfile)的輸出進行 yield

    因此,這是一個把「生成」工作在內部轉交給其他東西的產生器函式。

    透過這樣做,我們可以把它放進 with 區塊,藉此確保在完成後關閉類檔案物件。

Tip

注意,這裡我們使用的是標準的 open(),它不支援 asyncawait,因此我們用一般的 def 來宣告路徑操作。

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,但想套用一些未包含在 ORJSONResponse 類別中的自訂設定。

假設你想回傳縮排且格式化的 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 更好的方式來利用這個能力。😉

預設回應類別

在建立 FastAPI 類別實例或 APIRouter 時,你可以指定預設要使用哪個回應類別。

用來設定的是 default_response_class 參數。

在下面的例子中,FastAPI 會在所有「路徑操作」中預設使用 ORJSONResponse,而不是 JSONResponse

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 中的額外回應