Search code examples
pythonhttpmiddlewarefastapistarlette

Get starlette request body in the middleware context


I have such middleware

class RequestContext(BaseHTTPMiddleware):
    async def dispatch(self, request: Request, call_next: RequestResponseEndpoint):
        request_id = request_ctx.set(str(uuid4()))  # generate uuid to request
        body = await request.body()
        if body:
            logger.info(...)  # log request with body
        else:
            logger.info(...)  # log request without body
 
        response = await call_next(request)
        response.headers['X-Request-ID'] = request_ctx.get()
        logger.info("%s" % (response.status_code))
        request_ctx.reset(request_id)

        return response

So the line body = await request.body() freezes all requests that have body and I have 504 from all of them. How can I safely read the request body in this context? I just want to log request parameters.


Solution

  • I would not create a Middleware that inherits from BaseHTTPMiddleware since it has some issues, FastAPI gives you a opportunity to create your own routers, in my experience this approach is way better.

    from fastapi import APIRouter, FastAPI, Request, Response, Body
    from fastapi.routing import APIRoute
    
    from typing import Callable, List
    from uuid import uuid4
    
    
    class ContextIncludedRoute(APIRoute):
        def get_route_handler(self) -> Callable:
            original_route_handler = super().get_route_handler()
    
            async def custom_route_handler(request: Request) -> Response:
                request_id = str(uuid4())
                response: Response = await original_route_handler(request)
    
                if await request.body():
                    print(await request.body())
    
                response.headers["Request-ID"] = request_id
                return response
    
            return custom_route_handler
    
    
    app = FastAPI()
    router = APIRouter(route_class=ContextIncludedRoute)
    
    
    @router.post("/context")
    async def non_default_router(bod: List[str] = Body(...)):
        return bod
    
    
    app.include_router(router)
    

    Works as expected.

    b'["string"]'
    INFO:     127.0.0.1:49784 - "POST /context HTTP/1.1" 200 OK