首页
/ FastAPI 自定义响应详解:用 response_class 返回 HTML、流式、文件等响应并控制 OpenAPI 文档

FastAPI 自定义响应详解:用 response_class 返回 HTML、流式、文件等响应并控制 OpenAPI 文档

2026-09-06 14:17:54作者:裴锟轩Denise

FastAPI 默认返回 JSON,但真实项目中常常需要返回 HTML 页面、纯文本、文件、重定向或流式数据。本文基于 FastAPI 官方文档《Custom Response》(docs/en/docs/advanced/custom-response.md),系统讲解如何通过 response_classdefault_response_class 以及直接返回 Response 对象这三种方式自定义响应类型,并结合源码说明 OpenAPI 文档中 Content-Type 媒体类型的生成机制、各内置响应类的参数与适用场景,以及自定义响应类的实现原理。读完本文,你可以为任意接口精确控制响应的媒体类型、状态码与文档呈现。

JSON 响应:默认行为与序列化路径

FastAPI 默认返回 JSON 响应。不同声明方式下,数据序列化的路径不同,这直接影响性能:

  • 声明了 Response Model(即返回类型注解或 response_model):FastAPI 使用 Pydantic 将数据序列化为 JSON 字节,无需中间转换步骤,这是性能最优的方式。
  • 未声明 response model:FastAPI 使用 jsonable_encoder(见 JSON Compatible Encoder)转换数据后放入 JSONResponse
  • 显式声明 JSON 媒体类型的 response_class(如 JSONResponse):返回数据仍会经过 Pydantic response_model 的转换与过滤,但不会用 Pydantic 序列化成 JSON 字节,而是先经 jsonable_encoder 转换,再交给 JSONResponse 用 Python 标准 JSON 库序列化为字节。

JSON 性能结论(原文档核心建议):想要最高性能,声明 Response Model不要在路径操作装饰器中声明 response_class。对应示例:

# 摘自 docs_src/response_model/tutorial001_01_py310.py
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),
    ]

从源码结构看,默认的响应类就是 JSONResponse:在 fastapi/routing.py 中,路由注册方法的签名是 response_class: type[Response] | DefaultPlaceholder = Default(JSONResponse)(L379),若传入 DefaultPlaceholder 则取其 .value 作为实际响应类(L396-L399),这解释了为什么"不声明 response_class"时行为与 JSON 媒体类型完全一致。

返回 HTML:用 response_class 声明 HTMLResponse

要让 FastAPI 直接返回 HTML,使用 HTMLResponse,只需两步:导入它,并作为 response_class 参数传给路径操作装饰器:

# 摘自 docs_src/custom_response/tutorial002_py310.py
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 文档会按此记录。

关于媒体类型的技术细节:如果使用了没有媒体类型(media_typeNone)的响应类,FastAPI 会认为该响应无内容,因而在生成的 OpenAPI 文档中不记录响应格式。

从源码看,OpenAPI 生成时正是直接取响应类的 media_type 属性:fastapi/openapi/utils.py 中先解包 DefaultPlaceholder 得到 current_response_class,再执行 route_response_media_type: str | None = current_response_class.media_type,随后该媒体类型被写入 OpenAPI 的 responses 结构(L456-L502 区域)。这解释了为什么 response_class=HTMLResponse 能让 Swagger UI 的 "Try it out" 显示 text/html

方式二:直接返回一个 Response 对象

Return a Response directly 所述,你也可以不声明 response_class,而在路径操作函数中直接返回 Response 实例:

# 摘自 docs_src/custom_response/tutorial003_py310.py
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 不会被文档化),在自动生成的交互式文档中也不会可见。当然,实际的 Content-Type 头、状态码等仍来自你返回的 Response 对象本身。

方式三:既文档化 OpenAPI,又在函数内覆盖 Response

如果希望在函数内部覆盖响应,同时又在 OpenAPI 中文档化媒体类型,可以同时使用 response_class 参数并返回 Response 对象:response_class 只用于文档化 OpenAPI 路径操作,而你的 Response 会被原样使用。

例如让一个辅助函数生成 HTMLResponse

# 摘自 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()

这里 read_items() 返回的是 generate_html_response() 的结果,本身就是一个会覆盖 FastAPI 默认行为的 Response;但因为同时传了 response_class=HTMLResponse,FastAPI 知道如何在 OpenAPI 与交互式文档中把它记录为 text/html 的 HTML 响应:

交互式文档中 /items/ 接口的响应媒体类型显示为 text/html

可用响应类一览:Response、HTMLResponse、PlainTextResponse、JSONResponse

fastapi.responses 提供的响应类大多直接来自 Starlette——fastapi/responses.py 中可以看到 FileResponseHTMLResponseJSONResponsePlainTextResponseRedirectResponseResponseStreamingResponse 都是从 starlette.responses 重新导出的,因此你也可以写 from starlette.responses import HTMLResponse,效果相同。

Response

所有响应类的基类,可直接返回。构造参数:

  • content - strbytes
  • status_code - int 类型 HTTP 状态码;
  • headers - 字符串字典;
  • media_type - 媒体类型字符串,如 "text/html"

FastAPI(实际由 Starlette 完成)会自动附加 Content-Length 头,并根据 media_type 附加 Content-Type 头(文本类型会自动追加 charset)。示例:

# 摘自 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 / PlainTextResponse

HTMLResponse 接收文本或字节返回 HTML 响应(上文已演示);PlainTextResponse 接收文本或字节返回纯文本响应:

# 摘自 docs_src/custom_response/tutorial005_py310.py
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 的默认响应。

技术细节:如果你声明了 response model 或返回类型注解,FastAPI 会直接用它把数据序列化为 JSON 并返回带正确 JSON 媒体类型的响应,而不经过 JSONResponse 类。这就是获得最佳性能的理想方式。

RedirectResponse:HTTP 重定向

RedirectResponse 返回 HTTP 重定向,默认使用 307(Temporary Redirect)状态码。

用法一:直接返回:

# 摘自 docs_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 字符串:

# 摘自 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_codeRedirectResponse 的默认值 307。

用法三:response_classstatus_code 参数组合,自定义重定向状态码:

# 摘自 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:注意取消机制

StreamingResponse 接收一个 async 生成器或普通生成器/迭代器(带 yield 的函数),流式传输响应体:

# 摘自 docs_src/custom_response/tutorial007_py310.py
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,它无法被正确取消,可能在取消请求后继续运行。上例中 await anyio.sleep(0) 就是为了让事件循环有机会处理取消信号;对于大型或无限流这一点尤其重要。

建议:与其直接返回 StreamingResponse,更推荐采用 Stream Data 教程的风格,它更方便且会在后台替你处理取消逻辑;如果流式传输 JSON Lines,可参考 Stream JSON Lines 教程。

文件响应 FileResponse:异步流式传输文件

FileResponse 将文件异步流式传输为响应。它的构造参数与其他响应类不同:

  • path - 要流式传输的文件路径;
  • headers - 要包含的自定义头字典;
  • media_type - 媒体类型字符串;若未设置,会根据文件名或路径推断;
  • filename - 若设置,会包含在响应 Content-Disposition 头中。

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

用法一:直接返回:

# 摘自 docs_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

自定义响应类:重写 render() 控制序列化

你可以继承 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"
}

从源码印证这一模式:fastapi/responses.py 中的 UJSONResponseORJSONResponse 正是通过重写 render(self, content: Any) -> bytes 来切换序列化器的(L64-L66、L94-L98)。值得注意的是,这两个类在当前版本中已被标记为 @deprecated(L39-L47、L69-L77)——官方弃用理由与本节结论一致:当设置了返回类型或 response model 时,FastAPI 现在直接通过 Pydantic 把数据序列化为 JSON 字节,更快且无需自定义响应类。

追求性能:Response Model 优先于 orjson

如果你追求性能,用 Response Model 通常比 orjson 响应类更好。使用 response model 时,FastAPI 会用 Pydantic 直接把数据序列化为 JSON,不经过 jsonable_encoder 等中间步骤;而 Pydantic 底层使用的 Rust 序列化机制与 orjson 相同,因此 response model 已经能带来最佳性能。

默认响应类 default_response_class

创建 FastAPI 实例或 APIRouter 时,可以用 default_response_class 参数指定默认响应类(参数定义见 fastapi/applications.py,默认值为 Default(JSONResponse))。例如让所有路径操作默认使用 HTMLResponse

# 摘自 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>"

在单个路径操作上声明的 response_class 仍然会覆盖这个默认值,两者优先级从高到低为:直接返回的 Response 对象 > 路由级 response_class > 应用/路由器级 default_response_class

补充:在 OpenAPI 中声明更多响应细节

除了媒体类型,你还可以用 responses 参数在 OpenAPI 中声明额外的状态码与响应格式,详见 Additional Responses in OpenAPI

小结

  • 需要自定义响应类型时,优先在路径操作装饰器传 response_class(如 HTMLResponsePlainTextResponseRedirectResponseFileResponse),它同时控制实际响应与 OpenAPI 中的 Content-Type 文档;
  • 需要在函数内部完全控制响应时,直接返回 Response 实例,但会牺牲 OpenAPI 文档化;两者兼得的做法是同时传 response_class 并返回 Response
  • 追求最高 JSON 性能时,声明 response model 或不声明 response_class,让 Pydantic 直接序列化,不必借助 JSONResponse 或自定义 orjson 响应类;
  • 应用级统一响应类型用 FastAPI(default_response_class=...)
  • 所有可用响应类都来自 Starlette 并经由 fastapi.responses 再导出,自定义响应类只需实现 render(content) -> bytes 即可接入整套机制。
登录后查看全文
热门项目推荐
相关项目推荐