跳至內容

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

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

您可以透過直接返回 Response 來覆蓋它,如直接返回回應中所示。

但如果您直接返回 Response(或任何子類別,例如 JSONResponse),資料將不會自動轉換(即使您宣告了 response_model),文件也不會自動產生(例如,在產生的 OpenAPI 中包含特定的「媒體類型」,作為 HTTP 標頭 Content-Type 的一部分)。

但您也可以在路徑操作裝飾器中使用 response_class 參數宣告您想要使用的 Response(例如任何 Response 子類別)。

您從路徑操作函式返回的內容將會放入該 Response 中。

如果該 Response 具有 JSON 媒體類型 (application/json),就像 JSONResponseUJSONResponse 的情況一樣,您返回的資料將會使用您在路徑操作裝飾器中宣告的任何 Pydantic response_model 自動轉換(和過濾)。

注意事項

如果您使用沒有媒體類型的回應類別,FastAPI 將預期您的回應沒有內容,因此它不會在其產生的 OpenAPI 文件中記錄回應格式。

使用 ORJSONResponse

例如,如果您正在追求效能,您可以安裝並使用 orjson 並將回應設定為 ORJSONResponse

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

對於大型回應,直接返回 Response 比返回字典要快得多。

這是因為預設情況下,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"}])

資訊

參數 response_class 也將用於定義回應的「媒體類型」。

在這種情況下,HTTP 標頭 Content-Type 將設定為 application/json

並且它將在 OpenAPI 中如此記錄。

提示

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

資訊

參數 response_class 也將用於定義回應的「媒體類型」。

在這種情況下,HTTP 標頭 Content-Type 將設定為 text/html

並且它將在 OpenAPI 中如此記錄。

返回 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)

警告

由您的 *路徑操作函式* 直接返回的 Response 將不會記錄在 OpenAPI 中(例如,Content-Type 不會被記錄),並且不會顯示在自動互動式文件中。

資訊

當然,實際的 Content-Type 標頭、狀態碼等將來自您返回的 Response 物件。

在 OpenAPI 中記錄並覆寫 Response

如果您想要從函式內部覆寫響應,但同時在 OpenAPI 中記錄「媒體類型」,您可以使用 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,而不是以 str 返回 HTML。

透過返回呼叫 generate_html_response() 的結果,您已經返回了一個 Response,它將覆寫預設的 FastAPI 行為。

但由於您也在 response_class 中傳遞了 HTMLResponseFastAPI 將知道如何在 OpenAPI 和互動式文件中將其記錄為帶有 text/html 的 HTML。

可用的響應

以下是一些可用的響應。

請記住,您可以使用 Response 返回任何其他內容,甚至建立自定義子類別。

「技術細節」

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

為了方便開發者,FastAPI 提供了與 starlette.responses 相同的 fastapi.responses。但大多數可用的響應都直接來自 Starlette。

Response

主要的 Response 類別,所有其他響應都繼承自它。

您可以直接返回它。

它接受以下參數:

  • content - 一個 strbytes
  • status_code - 一個 int HTTP 狀態碼。
  • headers - 一個字串的 dict
  • media_type - 一個表示媒體類型的 str。例如 "text/html"

FastAPI(實際上是 Starlette)會自動包含一個 Content-Length 標頭。它還會根據 media_type 包含一個 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 響應,如您上面所讀。

資訊

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

UJSONResponse

使用 ujson 的另一種 JSON 回應方式。

資訊

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

警告

在處理一些邊緣情況時,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"}]

提示

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

RedirectResponse

返回 HTTP 重定向。預設使用 307 狀態碼(暫時重定向)。

您可以直接返回 RedirectResponse

from fastapi import FastAPI
from fastapi.responses import RedirectResponse

app = FastAPI()


@app.get("/typer")
async def redirect_typer():
    return RedirectResponse("https://typer.dev.org.tw")

或者您可以在 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.dev.org.tw"

如果您這樣做,則可以直接從您的*路徑操作*函數返回 URL。

在這種情況下,使用的 status_code 將是 RedirectResponse 的預設值,即 307


您也可以將 status_code 參數與 response_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

接收一個異步產生器或一般的產生器/迭代器,並串流傳輸回應主體。

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 與類檔案物件一起使用

如果您有一個類檔案物件(例如 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) 的值產生。

    因此,它是一個將「產生」工作轉移到內部其他物件的產生器函數。

    透過這種方式,我們可以將其放入 with 區塊中,從而確保在完成後關閉類檔案物件。

提示

請注意,由於我們在此處使用不支援 asyncawait 的標準 open(),因此我們使用普通的 def 宣告路徑操作。

FileResponse

以非同步方式串流傳輸檔案作為回應。

使用與其他回應類型不同的參數集來實例化

  • path - 要串流傳輸的檔案的路徑。
  • headers - 要包含的任何自訂標頭,以字典形式提供。
  • 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 ORJSONResponse

app = FastAPI(default_response_class=ORJSONResponse)


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

提示

您仍然可以像以前一樣在*路徑操作*中覆寫 response_class

其他文件

您也可以使用 responses 在 OpenAPI 中宣告媒體類型和許多其他細節:OpenAPI 中的其他回應