拜托,帮我查一下封锁代码的问题。我正在使用FastAPI。
我已经完成了一个中间件来记录传入的请求和它们的服务器响应。
@app.middleware("http")
async def log_request(request: Request, call_next):
# Code to log incoming request
response = await call_next(request)
# Code to log response
return response
我有一个没有依赖注入的端点:
@app.post("/without-dependency-injection", tags=["Test"])
async def check_response_without_using_dependency():
# ... Any code
return {"token":"token123"}
下面是一个控制台,其中包含一个由中间件编写的日志:
[17.08.2021 13:08:25.08S]: {"event_type": "Incoming request", "id": "3fb33dc0-cb86-49e9-9a0f-c48596e58061"}
[17.08.2021 13:08:25.08S]: {"event_type": "Outgoing response", "id": "3fb33dc0-cb86-49e9-9a0f-c48596e58061"}
INFO: 127.0.0.1:50285 - "POST /without-dependency-injection HTTP/1.1" 200 OK
一切都很顺利!中间件捕获请求,用请求细节写入日志,执行请求,用服务器响应写入日志,并向客户端返回响应。
当我添加依赖项时,行为将发生变化。
这是一个具有依赖项注入的端点:
@app.post("/generate-token/", tags=["Test"])
async def create_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
# ...
# Code to generate token
return {"token":f"{token}"}
下面是一个控制台日志:
[17.08.2021 13:13:08.13S]: {"event_type": "Incoming request", "id": "3b6398de-5b20-40ad-820e-24733425e6c7"}
正如您所看到的,没有“传出响应”事件。中间件捕获请求,记录传入的请求数据,并将请求发送到执行。密码被封锁了..。直到服务器收到新请求。只有在该客户端从服务器获得响应之后。
如果我删除了中间件或依赖项注入,一切都会正常工作。
谁知道为什么代码会被阻塞,直到服务器从客户端获得一个新的请求?如何纠正这种行为?
发布于 2022-04-09 19:32:08
基本信息、根本原因与咆哮
嘿,我花了相当多的时间在这个悬而未决的问题上(在我的组织中有多个自定义MDW的关键应用程序)。这基本上是因为基于@app.middleware("http")
的中间件是从Starlette的BaseHTTPMiddleware
继承而来的后端产品。因此,通过显式继承BaseHTTPMiddleware
编写的MDW也存在这个问题。其原因相当复杂,我一直未能完全理解这些理由,但这是我迄今所理解的:
StreamingResponse
,这有一些问题request.json()
一次,而BaseHTTPMiddleware
也会自己创建一个请求对象(这会导致挂起问题,因为这是另一个请求)。最后一个链接还提到,也会导致挂起问题的原因是,由于StreamingResponse
;响应的读取在第一次读取时不知怎么地被耗尽了,当涉及到第二次读取时,它一直在无限期地等待它,从而导致挂起。(这里第一次和第二次阅读意味着:在ASGI应用程序中,消息通过各种类型的客户端和应用程序(如http.response.start
、http.response.body
等)发送。)
解决方案(部分,记录响应,但我将更新时,我也了解到请求)
所以,不要使用任何与BaseHTTPMiddleware
有关的东西。为了解决这一问题,我使用这里提供的ASGI规范编写了我的所有定制中间件
您可以使您的自定义中间件如下所示:
from starlette.types import ASGIApp, Receive, Send, Message
class LogResponseMDW:
def __init__(self, app: ASGIApp) -> None:
self.app = app
async def __call__(self, scope: Scope, receive: Receive, send: Send):
async def send_wrapper(message: Message):
# This will capture response coming from APP Layer
# You will want to check for message["type"] first
# response body will be in message where the type is
# "http.response.body"
print(f"message: {message}") # log here, after checking the message type
await send(message)
await self.app(scope, receive, send_wrapper)
# you can add this to your app this way:
app.add_middleware(LogResponseMDW)
https://stackoverflow.com/questions/68830274
复制相似问题