首页
/ FastAPI 自定义响应(Custom Response)实战指南:HTML 页面、流式传输、文件下载与自定义 Response 类

FastAPI 自定义响应(Custom Response)实战指南:HTML 页面、流式传输、文件下载与自定义 Response 类

2026-09-06 19:13:16作者:苗圣禹Peter

本指南围绕 FastAPI 官方文档中「自定义响应」章节(对应仓库中的 docs/es/docs/advanced/custom-response.md)展开,系统讲解如何在默认 JSON 之外按需返回 HTML、纯文本、重定向、流式数据、文件下载等任意响应类型,以及两种自定义路径(直接返回 Response 与使用 response_class)的区别与组合用法。读完你不仅能在路由中灵活装配各类响应,还能基于 Response 基类自建响应类(例如格式化 JSON 的 orjson 响应),并掌握声明 default_response_class 让整套应用默认返回非 JSON 内容的方法。

两种覆盖默认 JSON 的方式:直接返回 Responseresponse_class

FastAPI 默认会把一切返回值包装成 JSON response。你可以在 path operation function 中直接 return 一个 Response 实例来覆盖它(参见 直接返回 Response),也可以在 path operation decorator 中使用 response_class 参数声明想要使用的 Response 子类,此时函数体内返回的内容会被装入该 Response 中。

两者存在重要区别:如果在函数内部直接返回 Response(或任意子类如 JSONResponse),那么——

  • 数据不会被自动转换(即使你声明了 response_model 也一样);
  • OpenAPI 文档不会自动生成对应信息(例如不会在生成的 OpenAPI 中记录 HTTP 头 Content-Type 的具体 media type)。

因此,若希望返回内容被自动处理、并在交互式文档中正确展示 media type,应优先在 decorator 上声明 response_class

[!NOTE] 如果使用的是没有 media type 的 response 类,FastAPI 会认为该响应没有实体内容,因此不会在生成的 OpenAPI 中记录响应的格式。

JSON responses 与性能取舍

默认情况下 FastAPI 返回 JSON:

  • 若声明了 Response Model,FastAPI 会用 Pydantic 将数据序列化为 JSON;
  • 若未声明 response model,FastAPI 会使用 JSON 兼容编码器jsonable_encoder)转换数据后放入 JSONResponse

当你在 decorator 上声明了 media type 为 application/jsonresponse_class(例如 JSONResponse)时,返回值会先经 response_model(Pydantic 模型)转换并过滤字段,但不会直接用 Pydantic 序列化成 JSON 字节,而是先经 jsonable_encoder 转换,再交给 JSONResponse 用 Python 标准库 json 序列化为字节——存在一次中间转换步骤。

[!IMPORTANT] 追求最大性能的做法:声明 Response Model(或返回类型注解),同时在 decorator 上不要显式传 response_class。这样 FastAPI 会让 Pydantic 直接生成 JSON 字节,省去 jsonable_encoder 与二次序列化。代码示例如下(源文件 docs_src/response_model/tutorial001_01_py310.py):

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

从源码层面看,FastAPI 的路由默认 response_class 就是 JSONResponse(见 fastapi/routing.pyresponse_class: type[Response] | DefaultPlaceholder = Default(JSONResponse)),而当你声明了返回类型时,FastAPI 会走“Pydantic 直接序列化字节”的快路径,这正是官方文档建议不要显式覆盖 JSON 响应类的原因。

返回 HTML:HTMLResponse

想要从 FastAPI 直接返回 HTML,使用 HTMLResponse 即可,只需两步:

  1. 导入 HTMLResponse
  2. path operation decorator 中把它传给 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>
    """

[!NOTE] response_class 还会被用来决定响应的 media type。在上例中,HTTP 头 Content-Type 会被设为 text/html,并会这样记录到 OpenAPI 中。

方式一:在函数内直接返回 Response

参照 直接返回 Response 的写法,也可以在 path operation function 里直接返回一个 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 不会被记录),在自动交互式文档中也不可见。

[!NOTE] 当然,真实响应中 Content-Type 头、状态码等都以你返回的那个 Response 对象为准。

方式二:既记录 OpenAPI,又在函数内覆盖 Response

如果想既在函数内部自由构造并返回 Response,又能在 OpenAPI 中正确声明 media type,可以同时使用 response_class 参数并返回 Response 对象:response_class 仅用于 OpenAPI 文档生成,实际生效的仍然是你返回的那个 Response。完整示例见 docs_src/custom_response/tutorial004_py310.py

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。由于 response_class=HTMLResponse 也同时传给了 decorator,FastAPI 会知道如何在 OpenAPI 中把它记录为 text/html 的 HTML 交互式文档——在 Swagger UI 的 /items/ 接口下,200 响应的 media type 会显示为 text/html

FastAPI 交互式文档中 /items/ 接口的 200 响应 media type 显示为 text/html(HTMLResponse 效果)

常用 Response 一览

下面列出 FastAPI 内置且最常用的 response 类。注意:你可以直接用 Response 返回任意内容,甚至创建自定义子类。

[!NOTE] 也可以使用 from starlette.responses import HTMLResponseFastAPIstarlette.responses 里的类以 fastapi.responses 的名字重新导出,纯粹是为了开发者方便;这些 response 类绝大部分源自 Starlette。仓库中的 fastapi/responses.py 即通过 from starlette.responses import ... as ... 逐类重导出(此外还额外导出了 FastAPI 的 EventSourceResponse)。

Response(基类)

主类 Response,其余所有 response 类都继承自它,也可以直接返回。它接受的参数:

  • content —— strbytes
  • status_code —— HTTP 状态码 int
  • headers —— 字符串键值对组成的 dict
  • media_type —— 描述 media type 的 str,例如 "text/html"

FastAPI(实际上由 Starlette 完成)会自动补上 Content-Length 头;也会依据 media_type 生成 Content-Type 头,并对文本类 media type 追加字符集。下面的 XML 示例可见,通过自定义 media_type 即可返回任意格式(完整代码见 docs_src/response_directly/tutorial002_py310.py):

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"

(对应仓库示例 docs_src/custom_response/tutorial005_py310.py。)

JSONResponse

接收数据并返回编码为 application/json 的响应——正是 FastAPI 的默认响应类。但注意:若声明了 response model 或返回类型注解,FastAPI 会直接用其把数据序列化成 JSON 字节并返回正确 media type 的响应,不再经过 JSONResponse 类;这也正是获取最佳性能的理想方式。

RedirectResponse

返回 HTTP 重定向,默认状态码为 307(临时重定向)。三种使用方式如下:

① 直接返回 RedirectResponsedocs_src/custom_response/tutorial006_py310.py):

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 使用,这样函数内直接返回目标 URL 字符串即可,状态码取 RedirectResponse 的默认值 307(docs_src/custom_response/tutorial006b_py310.py):

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"

③ 结合 status_code 修改重定向状态码(如改为 302,见 docs_src/custom_response/tutorial006c_py310.py):

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())

(完整代码见 docs_src/custom_response/tutorial007_py310.py。)

[!NOTE] 一个 async 任务只有执行到 await 时才能被取消。如果协程里没有任何 await,该生成器(带 yield 的函数)无法被正确取消,可能在客户端请求取消后仍继续运行。上面的小例子本身不需要任何 await 语句,因此特意加了 await anyio.sleep(0) 把控制权交还给事件循环以处理取消;对大型甚至无限流,这一点更加重要。

[!TIP] 相比直接返回 StreamingResponse,更推荐 流式数据(Stream Data) 一章的风格,它更便捷且由 FastAPI 在后台替你处理取消。若传输的是 JSON Lines,请参照 流式传输 JSON Lines(Stream JSON Lines) 教程。

FileResponse

以异步方式把文件流式传输给客户端。相比其他 response 类,它接收一组不同的构造参数:

  • path —— 待传输文件的路径;
  • headers —— 需要附带的自定义头(字典形式);
  • media_type —— media type 字符串;若未设置,会依据文件名或路径推断;
  • filename —— 若设置,会包含在响应的 Content-Disposition 中。

文件响应会自动带上合适的 Content-LengthLast-ModifiedETag 头。

① 直接返回 FileResponsedocs_src/custom_response/tutorial009_py310.py):

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 使用,此时函数内直接返回文件路径字符串即可(docs_src/custom_response/tutorial009b_py310.py):

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 类

你可以继承 Response 创建自己的 response 类再使用。核心只需要覆写 render(content) 方法,让它返回 bytes

例如希望使用 orjson 并启用其 orjson.OPT_INDENT_2 选项,输出带缩进、格式化后的 JSON,就可以这样写(完整代码见 docs_src/custom_response/tutorial009c_py310.py):

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

[!TIP] 仓库本身的历史响应类可作参考:在 fastapi/responses.py 中,UJSONResponseORJSONResponse 都是通过覆写 render() 返回字节实现的自定义 JSON 响应类——不过它们目前已标记为弃用(触发 FastAPIDeprecationWarning),因为新版本在声明返回类型时已能由 Pydantic 直接完成高性能序列化,不再需要这些类。对应的单元测试可参见仓库的 tests/test_orjson_response_class.py

orjson 还是 Response Model?

如果你的目标是性能,与其写一个 orjson 自定义响应,不如直接使用 Response Model:声明 response model 后,FastAPI 会让 Pydantic 直接把数据序列化为 JSON 字节,省去中间用 jsonable_encoder 转换后再交给其他序列化器的步骤。并且 Pydantic 底层与 orjson 使用相同的 Rust 序列化机制,用 response model 已经能获得最佳性能。

应用级默认响应类:default_response_class

创建 FastAPI 实例或 APIRouter 时,可以用 default_response_class 参数指定全局默认 response 类。例如下面的代码会让该应用所有 path operations 默认使用 HTMLResponse 而不是 JSON(docs_src/custom_response/tutorial010_py310.py):

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] 即便设置了 default_response_class,你依然可以在单个 path operation 中用 response_class 覆盖它。

该参数在路由层由 APIRoute/APIRouter 逐级继承:从源码看,路由解析时会用 get_value_or_default(...) 在「路由显式声明 → 父 Router 的 default_response_class → 默认 JSONResponse」之间取值(见 fastapi/routing.py)。仓库中对应行为亦有专门测试覆盖,例如 tests/test_default_response_class.pytests/test_default_response_class_router.py

扩展阅读

登录后查看全文
热门项目推荐
相关项目推荐