You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在FastAPI中使用await request.form()同时保留Form参数校验功能?

FastAPI中间件中复用表单数据的解决方案

问题场景

在FastAPI中编写请求日志中间件时,调用await request.form()会消费掉请求体的所有数据,导致后续接口通过Form(...)获取参数时返回422 Unprocessable Entity错误。测试代码及现象如下:

测试代码

import uvicorn
from fastapi import FastAPI, Form

app = FastAPI()


@app.middleware('http')
async def log_request(request, call_next):
    print(await request.form())  # 此处消费了请求体
    return await call_next(request)


@app.post('/')
async def test(id: int = Form(...)):
    return {'id': id}


if __name__ == '__main__':
    uvicorn.run(app, host='0.0.0.0', port=80)

请求命令

curl --data id=1 http://localhost

错误现象

执行请求后返回422 Unprocessable Entity,注释掉print(await request.form())则正常返回200 OK。

解决方法

核心思路是先保存请求体原始数据,让后续流程可以重复读取,具体有两种实现方式:

方法一:手动复用请求体原始数据

先读取请求体的字节数据,解析为表单后,将原始字节数据重新存入请求对象的内部属性,供后续流程读取:

import uvicorn
from fastapi import FastAPI, Form
from starlette.requests import Request

app = FastAPI()


@app.middleware('http')
async def log_request(request: Request, call_next):
    # 读取原始请求体字节数据
    raw_body = await request.body()
    # 解析为表单数据用于日志
    form_data = await request.form()
    print(form_data)
    # 将原始字节数据回写,让后续流程能再次读取
    request._body = raw_body
    return await call_next(request)


@app.post('/')
async def test(id: int = Form(...)):
    return {'id': id}


if __name__ == '__main__':
    uvicorn.run(app, host='0.0.0.0', port=80)

方法二:使用依赖项替代中间件(更推荐)

如果仅需记录请求参数,用FastAPI的依赖项可以避免直接操作请求内部属性,更符合框架设计规范:

import uvicorn
from fastapi import FastAPI, Form, Depends
from starlette.requests import Request

app = FastAPI()


async def log_form_data(request: Request):
    # 依赖项中读取表单数据,框架会自动处理复用逻辑
    form_data = await request.form()
    print(form_data)
    return form_data


@app.post('/')
async def test(id: int = Form(...), _=Depends(log_form_data)):
    return {'id': id}


if __name__ == '__main__':
    uvicorn.run(app, host='0.0.0.0', port=80)

关键说明

  • request.body()读取的是原始字节数据,不会一次性消费请求流;而request.form()会消费请求体流,因此必须先保存原始数据才能复用。
  • 方法一中操作request._body是直接修改Starlette的内部属性,虽然可行,但方法二的依赖项方式更优雅,也更适配FastAPI的生命周期逻辑。

内容的提问来源于stack exchange,提问作者OrthoPole

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 02:56:29