Awaiting request.json() in FastAPI hangs forever

803 views Asked by At

I added the exception handling as given here (https://github.com/tiangolo/fastapi/discussions/6678) to my code but I want to print the complete request body to see the complete content. However, when I await the request.json() it never terminates. request.json() returns a coroutine, so I need to wait for the coroutine to complete before printing the result. How can I print the content of the request in case an invalid request was sent to the endpoint?

Code example from github with 2 changes by me in the error handler and a simple endpoint.

import logging
from fastapi import FastAPI, Request, status
from fastapi.exceptions import RequestValidationError
from fastapi.responses import JSONResponse
from pydantic import BaseModel

app = FastAPI()


@app.exception_handler(RequestValidationError)
async def validation_exception_handler(
    request: Request, exc: RequestValidationError
) -> JSONResponse:
    exc_str = f"{exc}".replace("\n", " ").replace("   ", " ")
    logging.error(f"{request}: {exc_str}")
    body = await request.json()  # This line was added by me and never completes
    logging.error(body)  # This line was added by me
    content = {"status_code": 10422, "message": exc_str, "data": None}
    return JSONResponse(
        content=content, status_code=status.HTTP_422_UNPROCESSABLE_ENTITY
    )


class User(BaseModel):
    name: str


@app.post("/")
async def test(body: User) -> User:
    return body

1

There are 1 answers

0
Chris On BEST ANSWER

When awaiting request.json, you are trying to read the request body (not the response body, as you may have assumed) out from stream; however, this operation has already taken place in your API endpoint (behind the scenes, in your case). Hence, calling await request.json() is allowed only once in the API's request life-cycle, and attempting reading it again will keep on waiting for it indefinitely (since reading the request body got exhausted), which causes the hanging issue. You may find this answer helpful as well, regarding reading/logging the request body (and/or response body) in a FastAPI/Starlette middleware, before passing the request to the endpoint.

As for getting the error body from the response in the RequestValidationError exception handler, you would not need to read the request body, but the response body and you can do that by calling exc.body, instead of request.json(), as demonstrated here and here. Please refer to those answers for more details.

Example

from fastapi.exceptions import RequestValidationError

@app.exception_handler(RequestValidationError)
async def validation_exception_handler(request: Request, exc: RequestValidationError):
    return JSONResponse(
        status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
        content=jsonable_encoder({"detail": exc.errors(),  # optionally include the errors
                "body": exc.body,
                 "custom msg": {"Your error message"}}),
    )