【问题标题】:FASTAPI custom middleware getting body of request insideFASTAPI 自定义中间件在内部获取请求主体
【发布时间】:2021-10-21 23:23:40
【问题描述】:

一直在尝试使用 FASTAPI 中间件获取请求的正文,但似乎我只能获取 request.headers 而不能获取正文。我需要 body 才能获得用于检查数据库中某些内容的密钥。想想中间件的日志记录或身份验证用法。

@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 的访问权限或拒绝它。

【问题讨论】:

    标签: python python-3.x middleware router fastapi


    【解决方案1】:

    您需要在请求上await,以便请求对象实际上可以被读取。我就是这样实现的。

    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
    

    【讨论】:

      【解决方案2】:

      获取请求体引用FastAPI - Custom APIRoute class in a router的解决方案

      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
      

      【讨论】:

        【解决方案3】:

        我建议改用路由器。参考这个github issue

        这是一个例子

        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)])
        
        

        【讨论】:

          【解决方案4】:

          引用关于“Details about the Request object”的 FastAPI 文档:

          由于FastAPI其实是Starlette下面,上面有几个工具层,你可以在需要的时候直接使用Starlette的Request对象。

          starlette doc about the request body object 说:

          有几种不同的接口用于返回请求的正文:

          • 请求正文为字节: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 测试:

          curl -X POST \
            -H "Content-Type: application/json" \
            -d '{"test": "Also works!"}' \
              http://localhost:8000/foo
          

          【讨论】:

          • 我用 POSTMAN 对此进行了测试,但它只是挂起,我曾尝试按照文档中的说明对 request.json() 和 request.body() 进行等待,但 POST 端点没有返回什么都挂了。我还需要能够在中间件而不是端点上获取请求的 BODY。我需要这个来检查数据库的身份验证,如果他们通过了身份验证,那么我可以继续进行终点。
          猜你喜欢
          • 2018-12-21
          • 1970-01-01
          • 2022-11-27
          • 2018-07-04
          • 2011-06-20
          • 2019-06-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多