跳到内容

自定义响应 - HTML、流式、文件等

默认情况下,FastAPI 将使用 JSONResponse 返回响应。

你可以通过直接返回一个 Response 来覆盖它,正如 直接返回响应 中所示。

但是,如果你直接返回一个 Response(或任何子类,如 JSONResponse),数据将不会自动转换(即使你声明了 response_model),并且文档也不会自动生成(例如,将特定的“媒体类型”包含在 HTTP 头 Content-Type 中作为生成的 OpenAPI 的一部分)。

但是你也可以在**路径操作装饰器**中使用 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() 的调用结果,你已经返回了一个将覆盖默认 FastAPI 行为的 Response

但由于你也在 response_class 中传递了 HTMLResponse,**FastAPI** 将知道如何在 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.fastapi.org.cn")

或者你可以在 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.org.cn"

如果你这样做,那么你可以直接从你的**路径操作**函数返回 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.org.cn/"

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 中的附加响应