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

FastAPI自定义中间件call_next处理大JSON负载时无限阻塞问题

问题现象

部署基于BaseHTTPMiddleware实现的自定义请求日志中间件时,小体积JSON请求可以正常处理,传入大体积JSON负载时,response = await call_next(request)语句会无限期阻塞,涉及的实现代码如下:

from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
from starlette.types import Message, RequestResponseEndpoint

class RequestContextLogMiddleware(BaseHTTPMiddleware):

    async def set_body(self, request: Request):
        receive_ = await request._receive()

        async def receive() -> Message:
            return receive_

        request._receive = receive

    async def dispatch(self, request: Request, call_next: RequestResponseEndpoint):
        await self.set_body(request)
        body = await request.body()
        jsonbody = await request.json()
        id_ = jsonbody['external_id']
        response = await call_next(request)    
        return response

核心疑问:

  • 大请求场景下阻塞的根本诱因是什么?
  • 上述写法是否是FastAPI中读取/修改JSON请求体的正确实现?
阻塞原因

问题出在set_body方法的自定义receive实现逻辑:

  • Starlette的Request对象读取请求体时,会反复调用内部的_receive方法逐块拉取HTTP传输的分片数据,直到收到标识请求体结束的消息,才会判定请求体接收完成。小体积请求的所有数据可以在第一次_receive调用时全部返回,所以这套逻辑在小请求场景下不会暴露问题。
  • 大体积JSON负载会被拆成多个TCP分片传输,示例代码里的自定义receive函数永远只返回第一次调用_receive拿到的第一个分片,后续路由处理逻辑调用receive拉取剩余分片时,永远拿不到后续数据,也等不到请求结束的标识,就会一直挂起阻塞。
正确实现建议

上述写法不是读取/修改请求体的推荐实现,可根据场景选择对应方案:

  • 若必须使用BaseHTTPMiddleware缓存请求体,需要把所有读取到的分片按顺序存入缓存队列,自定义receive方法要按读取顺序依次返回缓存的分片,不能只存储第一次读取的单块数据。
  • 优先使用原生ASGI中间件实现请求体读取/修改逻辑,避免BaseHTTPMiddleware自带的请求上下文拷贝带来的异常;如果只是要提取请求体字段做日志、鉴权等操作,直接用FastAPI的依赖注入在路由层获取请求体即可,不需要在中间件层处理,实现更简单也不容易出问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 18:09:37