Fastapi: [BUG] Unable to get request body inside an exception handler, got RuntimeError: Receive channel has not been made available

Created on 6 Apr 2020  路  4Comments  路  Source: tiangolo/fastapi

Hello

SAME HERE https://github.com/encode/starlette/issues/892 (I am using fastapi but it is a starlette issue I think)

I want to get the request body in an exception handler but I am getting an Error. Is this a bug?

Minimal example

from fastapi import FastAPI
from starlette.requests import Request
from starlette.responses import UJSONResponse

app = FastAPI()


async def http_exception_handler(request: Request, exc: Exception) -> UJSONResponse:
    print(await request.body())  # RuntimeError: Receive channel has not been made available
    return UJSONResponse(content={"msg": f"SERVER_ERROR, {exc.args[0]}", "code": 500}, status_code=500)


app.add_exception_handler(Exception, http_exception_handler)


@app.post("/")
async def index(request: Request):
    print(await request.body())  # b'{"a": "b"}'
    raise Exception("CATCH ME")


# Run http localhost:8000 some=data

Error:

b'{"a": "b"}'
ERROR:    Exception in ASGI application
Traceback (most recent call last):
  File "/home/sevaho/.local/share/virtualenvs/testje2-JmgYoeLF/lib/python3.8/site-packages/starlette/middleware/errors.py", line 159, in __call__
    await self.app(scope, receive, _send)
  File "/home/sevaho/.local/share/virtualenvs/testje2-JmgYoeLF/lib/python3.8/site-packages/starlette/exceptions.py", line 82, in __call__
    raise exc from None
  File "/home/sevaho/.local/share/virtualenvs/testje2-JmgYoeLF/lib/python3.8/site-packages/starlette/exceptions.py", line 71, in __call__
    await self.app(scope, receive, sender)
  File "/home/sevaho/.local/share/virtualenvs/testje2-JmgYoeLF/lib/python3.8/site-packages/starlette/routing.py", line 550, in __call__
    await route.handle(scope, receive, send)
  File "/home/sevaho/.local/share/virtualenvs/testje2-JmgYoeLF/lib/python3.8/site-packages/starlette/routing.py", line 227, in handle
    await self.app(scope, receive, send)
  File "/home/sevaho/.local/share/virtualenvs/testje2-JmgYoeLF/lib/python3.8/site-packages/starlette/routing.py", line 41, in app
    response = await func(request)
  File "/home/sevaho/.local/share/virtualenvs/testje2-JmgYoeLF/lib/python3.8/site-packages/fastapi/routing.py", line 196, in app
    raw_response = await run_endpoint_function(
  File "/home/sevaho/.local/share/virtualenvs/testje2-JmgYoeLF/lib/python3.8/site-packages/fastapi/routing.py", line 148, in run_endpoint_function
    return await dependant.call(**values)
  File "./app.py", line 19, in index
    raise Exception("CATCH ME")
Exception: CATCH ME

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/sevaho/.local/share/virtualenvs/testje2-JmgYoeLF/lib/python3.8/site-packages/uvicorn/protocols/http/httptools_impl.py", line 385, in run_asgi
    result = await app(self.scope, self.receive, self.send)
  File "/home/sevaho/.local/share/virtualenvs/testje2-JmgYoeLF/lib/python3.8/site-packages/uvicorn/middleware/proxy_headers.py", line 45, in __call__
    return await self.app(scope, receive, send)
  File "/home/sevaho/.local/share/virtualenvs/testje2-JmgYoeLF/lib/python3.8/site-packages/fastapi/applications.py", line 149, in __call__
    await super().__call__(scope, receive, send)
  File "/home/sevaho/.local/share/virtualenvs/testje2-JmgYoeLF/lib/python3.8/site-packages/starlette/applications.py", line 102, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/home/sevaho/.local/share/virtualenvs/testje2-JmgYoeLF/lib/python3.8/site-packages/starlette/middleware/errors.py", line 172, in __call__
    response = await self.handler(request, exc)
  File "./app.py", line 9, in http_exception_handler
    print(await request.body())
  File "/home/sevaho/.local/share/virtualenvs/testje2-JmgYoeLF/lib/python3.8/site-packages/starlette/requests.py", line 194, in body
    async for chunk in self.stream():
  File "/home/sevaho/.local/share/virtualenvs/testje2-JmgYoeLF/lib/python3.8/site-packages/starlette/requests.py", line 179, in stream
    message = await self._receive()
  File "/home/sevaho/.local/share/virtualenvs/testje2-JmgYoeLF/lib/python3.8/site-packages/starlette/requests.py", line 142, in empty_receive
    raise RuntimeError("Receive channel has not been made available")
RuntimeError: Receive channel has not been made available
INFO:     127.0.0.1:38798 - "POST / HTTP/1.1" 500 Internal Server Error

versions used:

```pipenv graph
fastapi==0.54.0

  • pydantic [required: >=0.32.2,<2.0.0, installed: 1.4]
  • starlette [required: ==0.13.2, installed: 0.13.2]
    uvicorn==0.11.3
  • click [required: ==7.*, installed: 7.1.1]
  • h11 [required: >=0.8,<0.10, installed: 0.9.0]
  • httptools [required: ==0.1.*, installed: 0.1.1]
  • uvloop [required: >=0.14.0, installed: 0.14.0]
  • websockets [required: ==8.*, installed: 8.1]
    ```
bug

Most helpful comment

I'm no expert as I'm learning and use fastapi for a few weeks only.
In my learning project spec, I stated that every exceptions should be partially returned to the client. So I had the similar expectation. Turns out that it is documented.

Your code should probably look like this:

import logging
from typing import Callable, List, Union

from fastapi import Body, FastAPI, HTTPException, Request, Response
from fastapi.exceptions import RequestValidationError
from fastapi.routing import APIRoute
from starlette.responses import UJSONResponse


class ErrorLoggingRoute(APIRoute):
    def get_route_handler(self) -> Callable:
        original_route_handler = super().get_route_handler()

        async def custom_route_handler(request: Request) -> Union[Response, UJSONResponse]:
            try:
                return await original_route_handler(request)
            except Exception as exc:
                logging.exception("Exception:")  # Keep it somewhere.

                return UJSONResponse(
                    {
                        "msg": f"SERVER_ERROR, {exc.args[0]}",
                        "code": 500,
                        "body": await request.body(),
                    },
                    status_code=500,
                )

        return custom_route_handler


app = FastAPI()
app.router.route_class = ErrorLoggingRoute


@app.post("/")
async def index(request: Request):
    raise Exception("CATCH ME")

I hope that it may help.

All 4 comments

I'm no expert as I'm learning and use fastapi for a few weeks only.
In my learning project spec, I stated that every exceptions should be partially returned to the client. So I had the similar expectation. Turns out that it is documented.

Your code should probably look like this:

import logging
from typing import Callable, List, Union

from fastapi import Body, FastAPI, HTTPException, Request, Response
from fastapi.exceptions import RequestValidationError
from fastapi.routing import APIRoute
from starlette.responses import UJSONResponse


class ErrorLoggingRoute(APIRoute):
    def get_route_handler(self) -> Callable:
        original_route_handler = super().get_route_handler()

        async def custom_route_handler(request: Request) -> Union[Response, UJSONResponse]:
            try:
                return await original_route_handler(request)
            except Exception as exc:
                logging.exception("Exception:")  # Keep it somewhere.

                return UJSONResponse(
                    {
                        "msg": f"SERVER_ERROR, {exc.args[0]}",
                        "code": 500,
                        "body": await request.body(),
                    },
                    status_code=500,
                )

        return custom_route_handler


app = FastAPI()
app.router.route_class = ErrorLoggingRoute


@app.post("/")
async def index(request: Request):
    raise Exception("CATCH ME")

I hope that it may help.

Body is consumed already by your index function.
You can raise an exception with the body data from there:

@app.post("/")
async def index(request: Request):
    body = await request.body())  # b'{"a": "b"}'
    raise Exception(body)

Thank you all, fixed now :D

Thanks for the help here everyone! :clap: :cake:

And thanks @sevaho for reporting back and closing the issue :+1:

Was this page helpful?
0 / 5 - 0 ratings