[英]FASTAPI custom middleware getting body of request inside
一直在尝试使用 FASTAPI 中间件获取请求的正文,但似乎我只能获取 request.headers 而不是正文。 我需要身体来获得一把钥匙,我将用它来检查数据库中的某些东西。 想想中间件的日志记录或身份验证使用。
@app.middleware("http")
async def TestCustomMiddleware(request: Request, call_next):
print("Middleware works!", request.headers)
response = await call_next(request)
resp_body = [section async for section in response.__dict__['body_iterator']]
print("BODY:", resp_body)
return response
我能够得到这个但是有一个错误会破坏 POST 请求:
INFO: Started server process [37160]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit)
Middleware works! Headers({'content-type': 'application/json', 'user-agent': 'PostmanRuntime/7.26.8', 'accept': '*/*', 'cache-control': 'no-cache', 'postman-token': 'ca6839ec-833d-45c0-9b52-8f904db13966', 'host': 'localhost:8000', 'accept-encoding': 'gzip, deflate, br', 'connection': 'keep-alive', 'content-length': '12'})
BODY: [b'{"test":"1"}']
INFO: 127.0.0.1:60761 - "POST /jctest HTTP/1.1" 200 OK
ERROR: Exception in ASGI application
Traceback (most recent call last):
File "C:\Python\Python38\lib\site-packages\uvicorn\protocols\http\httptools_impl.py", line 386, in run_asgi
result = await app(self.scope, self.receive, self.send)
File "C:\Python\Python38\lib\site-packages\uvicorn\middleware\proxy_headers.py", line 45, in __call__
return await self.app(scope, receive, send)
File "C:\Python\Python38\lib\site-packages\fastapi\applications.py", line 181, in __call__
await super().__call__(scope, receive, send)
File "C:\Python\Python38\lib\site-packages\starlette\applications.py", line 111, in __call__
await self.middleware_stack(scope, receive, send)
File "C:\Python\Python38\lib\site-packages\starlette\middleware\errors.py", line 181, in __call__
raise exc from None
File "C:\Python\Python38\lib\site-packages\starlette\middleware\errors.py", line 159, in __call__
await self.app(scope, receive, _send)
File "C:\Python\Python38\lib\site-packages\starlette\middleware\base.py", line 26, in __call__
await response(scope, receive, send)
File "C:\Python\Python38\lib\site-packages\starlette\responses.py", line 228, in __call__
await run_until_first_complete(
File "C:\Python\Python38\lib\site-packages\starlette\concurrency.py", line 18, in run_until_first_complete
[task.result() for task in done]
File "C:\Python\Python38\lib\site-packages\starlette\concurrency.py", line 18, in <listcomp>
[task.result() for task in done]
File "C:\Python\Python38\lib\site-packages\starlette\responses.py", line 225, in stream_response
await send({"type": "http.response.body", "body": b"", "more_body": False})
File "C:\Python\Python38\lib\site-packages\starlette\middleware\errors.py", line 156, in _send
await send(message)
File "C:\Python\Python38\lib\site-packages\uvicorn\protocols\http\httptools_impl.py", line 516, in send
raise RuntimeError("Response content shorter than Content-Length")
RuntimeError: Response content shorter than Content-Length
我该如何解决这个问题,以便获得{"test":"1"}
的请求正文?
尝试让正文找到一个密钥,该密钥将用于检查数据库并根据凭据授予对 API 的访问权限或拒绝它。
引用 FastAPI Doc 关于“ 关于请求对象的详细信息”:
由于FastAPI其实底层是Starlette,上面有一层几个工具,你可以在需要的时候直接使用Starlette的Request对象。
有几个不同的接口用于返回请求的正文:
- 请求正文为字节:await request.body()
- 请求体,解析为表单数据或多部分:await request.form()
- 请求正文,解析为 JSON:await request.json()
@app.middleware("http")
async def TestCustomMiddleware(request: Request, call_next):
the_headers = request.headers
the_body = await request.json()
print(the_headers)
print(the_body)
response = await call_next(request)
return response
用卷曲测试:
curl -X POST \
-H "Content-Type: application/json" \
-d '{"test": "Also works!"}' \
http://localhost:8000/foo
获取请求正文引用的解决方法FastAPI - 路由器中的自定义 APIRoute 类
class CustomRoute(APIRoute):
def get_route_handler(self) -> Callable:
original_route_handler = super().get_route_handler()
async def custom_route_handler(request: Request) -> Response:
body = await request.body()
response: Response = await original_route_handler(request)
return response
return custom_route_handler
我建议改用路由器。 参考这个github 问题
这是一个例子
app = FastAPI()
api_router = APIRouter()
async def log_request_info(request: Request):
request_body = await request.json()
logger.info(
f"{request.method} request to {request.url} metadata\n"
f"\tHeaders: {request.headers}\n"
f"\tBody: {request_body}\n"
f"\tPath Params: {request.path_params}\n"
f"\tQuery Params: {request.query_params}\n"
f"\tCookies: {request.cookies}\n"
)
@api_router.get("/", summary="Status")
async def status_get():
logger.debug('Status requested')
return {'status': 'OK'}
@api_router.post("/", )
def status_post(urls: Optional[List[str]] = None):
logger.debug('Status requested')
return {'status': 'OK'}
app.include_router(api_router, dependencies=[Depends(log_request_info)])
您需要
await<\/code>请求,以便请求对象实际上已准备好被读取。
这就是我实现它的方式。
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 的中间件内尝试request.body()
或request.json()
将会挂起。
这是 Starlette 中的一个已知问题,记录如下: https ://github.com/tiangolo/fastapi/issues/394#issuecomment-883524819
解决方法是放入set_body
和get_body
并await set_body(request, await request.body())
代码。 下面的例子:
async def set_body(request: Request, body: bytes):
async def receive() -> Message:
return {"type": "http.request", "body": body}
request._receive = receive
async def get_body(request: Request) -> bytes:
body = await request.body()
await set_body(request, body)
return body
@app.middleware("http")
async def app_entry(request: Request, call_next):
await set_body(request, await request.body())
print(await get_body(request))
response = await call_next(request)
return response
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.