Starlette中间件读取请求体致504阻塞,如何安全读取请求体?
解决FastAPI中间件读取请求体后请求冻结的问题
这个问题我之前也碰到过——原因很简单:FastAPI的Request.body()方法是一次性消耗的,你读取完请求体之后,原始的请求流就被耗尽了,后面的路由函数再尝试解析请求体(比如绑定Pydantic模型)的时候,就会一直等待数据,最终超时返回504。
要安全读取请求体同时不影响后续处理,核心思路是:读取请求体后,把内容备份回Request对象,让后续流程还能获取到。这里有两种常用的实现方式:
方法一:直接设置_body属性(简单高效)
FastAPI的Request内部会优先检查_body私有属性是否存在,如果存在就直接返回该值,不用再从流中读取。所以我们只需要在读取后把body赋值给这个属性即可:
class RequestContext(BaseHTTPMiddleware): async def dispatch(self, request: Request, call_next: RequestResponseEndpoint): request_id = request_ctx.set(str(uuid4())) # 为请求生成UUID # 读取请求体 body = await request.body() # 关键:把读取后的请求体备份回Request对象 request._body = body # 记录请求日志 if body: # 解码成字符串(根据实际请求编码调整,比如表单可能用gbk) logger.info(f"Request [{request_id}] | Path: {request.url.path} | Body: {body.decode('utf-8')}") else: logger.info(f"Request [{request_id}] | Path: {request.url.path} | No body") # 继续处理请求 response = await call_next(request) response.headers['X-Request-ID'] = request_ctx.get() logger.info(f"Request [{request_id}] | Status: {response.status_code}") request_ctx.reset(request_id) return response
方法二:替换receive方法(更规范的私有属性兼容方案)
如果你担心直接操作私有属性_body在未来FastAPI版本中出现兼容性问题,可以通过替换Request的_receive方法来模拟请求体的重新发送:
class RequestContext(BaseHTTPMiddleware): async def dispatch(self, request: Request, call_next: RequestResponseEndpoint): request_id = request_ctx.set(str(uuid4())) # 为请求生成UUID # 读取原始请求体 body = await request.body() # 定义一个伪造的receive方法,返回已读取的body async def fake_receive(): return {"type": "http.request", "body": body} # 替换request的receive方法 original_receive = request._receive request._receive = fake_receive try: # 记录请求日志 if body: logger.info(f"Request [{request_id}] | Path: {request.url.path} | Body: {body.decode('utf-8')}") else: logger.info(f"Request [{request_id}] | Path: {request.url.path} | No body") # 继续处理请求 response = await call_next(request) response.headers['X-Request-ID'] = request_ctx.get() logger.info(f"Request [{request_id}] | Status: {response.status_code}") finally: # 恢复原始的receive方法 request._receive = original_receive request_ctx.reset(request_id) return response
注意事项
- 编码问题:如果你的请求体不是UTF-8编码(比如表单提交的gbk编码数据),需要调整
body.decode()的参数,避免乱码。如果不确定编码,也可以直接记录原始字节(但可读性差)。 - 大请求体:如果请求体特别大(比如上传文件),这种方式会把整个请求体加载到内存中,可能影响性能。这种场景下建议只记录请求元数据(比如路径、方法),而不是完整请求体。
内容的提问来源于stack exchange,提问作者sashaaero
相关产品推荐
相关产品推荐

