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

FastAPI中间件读取表单后后续路由无法处理数据的解决方案咨询

解决FastAPI中间件读取multipart/form-data后后续无法处理数据的问题

FastAPI基于Starlette实现,Request对象的请求体是流式结构,调用await request.form()会一次性消费掉请求体的所有内容。后续路由或中间件再尝试读取请求体时,会因为流已耗尽而无法获取数据。直接给request._form赋值无效,因为内部的请求体流已经空了,Starlette依然会尝试从流中读取数据而非直接使用_form。

方案一:复制请求体并复用解析后的表单

通过复制原始请求体内容,重新构建可读取的流,同时将解析后的表单存入request._form,让后续流程既能读取请求体,也能直接复用已解析的结果:

from fastapi import FastAPI, Request, UploadFile, Form, File
from starlette.datastructures import UploadFile as StarletteUploadFile
import io

app = FastAPI()

@app.middleware("http")
async def middleware_log(request: Request, call_next):
    content_type = request.headers.get("Content-Type")
    if content_type and "multipart/form-data" in content_type:
        # 复制原始请求体内容
        raw_body = await request.body()
        # 重新设置请求体的字节流,让后续可重复读取
        request._body = raw_body
        request._stream = io.BytesIO(raw_body)
        
        # 解析表单并生成日志数据
        form = await request.form()
        log_data = {
            "files": [
                {
                    "filename": file.filename, 
                    "file_size": str(round(file.size / 1024, 2)) + 'KB', 
                    "content_type": file.content_type
                } for file in form.values() if isinstance(file, StarletteUploadFile)
            ],
            "form_fields": {k: v for k, v in form.items() if not isinstance(v, StarletteUploadFile)}
        }
        print(log_data)
        
        # 将解析后的表单存入request._form,避免后续重复解析
        request._form = form
    
    response = await call_next(request)
    return response

方案二:使用依赖项处理日志(更简洁)

利用FastAPI的依赖项机制,在路由处理前自动解析表单并记录日志,FastAPI内部会自动缓存解析后的表单,路由中的File/Form参数可直接获取数据:

from fastapi import FastAPI, Request, UploadFile, Form, File, Depends
from starlette.datastructures import UploadFile as StarletteUploadFile

app = FastAPI()

async def log_form_data(request: Request):
    content_type = request.headers.get("Content-Type")
    if content_type and "multipart/form-data" in content_type:
        form = await request.form()
        log_data = {
            "files": [
                {
                    "filename": file.filename, 
                    "file_size": str(round(file.size / 1024, 2)) + 'KB', 
                    "content_type": file.content_type
                } for file in form.values() if isinstance(file, StarletteUploadFile)
            ],
            "form_fields": {k: v for k, v in form.items() if not isinstance(v, StarletteUploadFile)}
        }
        print(log_data)

# 全局添加依赖,所有路由都会执行日志逻辑
app.dependency_overrides[None] = [log_form_data]

# 测试路由示例
@app.post("/upload")
async def upload_file(file: UploadFile = File(...), username: str = Form(...)):
    return {"uploaded_file": file.filename, "username": username}

注意事项

  • 若处理超大文件,方案一中的内存复制可能导致性能问题,此时可考虑将请求体写入临时文件而非内存。
  • 方案二更符合FastAPI的设计规范,无需手动操作请求体的底层属性,代码更简洁易维护。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 11:23:30