granian: AssertionError with FastAPI and Granian when using Middleware

Hey,

Firstly, I would like to express my gratitude for developing this exceptional package and your effort in it. I am fairly new to this area, but I am considering leveraging Granian’s power for my FastAPI project. A little background: I am integrating various middleware (SlowAPIMiddelware & GZipMiddleware) into my FastAPI application, but I have encountered an issue that I hope to get some guidance on.

When running my FastAPI with Granian, I am facing an “AssertionError” linked to middleware processing when the application asserts the message type as “http.response.body”. Check Error Traceback .

  AssertionError:
  [ERROR] Application callable raised an exception
  Traceback (most recent call last):
    File "C:\Users\Geo\AppData\Local\pypoetry\Cache\virtualenvs\website-v-for-vets-QDVzixt4-py3.11\Lib\site-packages\granian\_futures.py", line 4, in future_watcher
      await inner(watcher.scope, watcher.proto)
    File "C:\Users\Geo\AppData\Local\pypoetry\Cache\virtualenvs\website-v-for-vets-QDVzixt4-py3.11\Lib\site-packages\fastapi\applications.py", line 1054, in __call__
      await super().__call__(scope, receive, send)
    File "C:\Users\Geo\AppData\Local\pypoetry\Cache\virtualenvs\website-v-for-vets-QDVzixt4-py3.11\Lib\site-packages\starlette\applications.py", line 123, in __call__
      await self.middleware_stack(scope, receive, send)
    File "C:\Users\Geo\AppData\Local\pypoetry\Cache\virtualenvs\website-v-for-vets-QDVzixt4-py3.11\Lib\site-packages\starlette\middleware\errors.py", line 186, in __call__
      raise exc
    File "C:\Users\Geo\AppData\Local\pypoetry\Cache\virtualenvs\website-v-for-vets-QDVzixt4-py3.11\Lib\site-packages\starlette\middleware\errors.py", line 164, in __call__
      await self.app(scope, receive, _send)
    File "C:\Users\Geo\AppData\Local\pypoetry\Cache\virtualenvs\website-v-for-vets-QDVzixt4-py3.11\Lib\site-packages\starlette\middleware\base.py", line 189, in __call__
      with collapse_excgroups():
    File "C:\Users\Geo\AppData\Local\Programs\Python\Python311\Lib\contextlib.py", line 155, in __exit__
      self.gen.throw(typ, value, traceback)
    File "C:\Users\Geo\AppData\Local\pypoetry\Cache\virtualenvs\website-v-for-vets-QDVzixt4-py3.11\Lib\site-packages\starlette\_utils.py", line 93, in collapse_excgroups
      raise exc
    File "C:\Users\Geo\AppData\Local\pypoetry\Cache\virtualenvs\website-v-for-vets-QDVzixt4-py3.11\Lib\site-packages\starlette\responses.py", line 260, in wrap
      await func()
    File "C:\Users\Geo\AppData\Local\pypoetry\Cache\virtualenvs\website-v-for-vets-QDVzixt4-py3.11\Lib\site-packages\starlette\middleware\base.py", line 217, in stream_response
      return await super().stream_response(send)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    File "C:\Users\Geo\AppData\Local\pypoetry\Cache\virtualenvs\website-v-for-vets-QDVzixt4-py3.11\Lib\site-packages\starlette\responses.py", line 249, in stream_response
      async for chunk in self.body_iterator:
    File "C:\Users\Geo\AppData\Local\pypoetry\Cache\virtualenvs\website-v-for-vets-QDVzixt4-py3.11\Lib\site-packages\starlette\middleware\base.py", line 173, in body_stream
      assert message["type"] == "http.response.body"

Here are the dependencies of my environment:

  • Python version: 3.11
  • FastAPI version: 0.109.2
  • Granian version: 1.1.0 (with reload extra)
  • Uvicorn version: 0.27.1
  • Other relevant packages:
    • jinja2-fragments: 1.2.1
    • loguru: 0.7.2
    • slowapi: 0.1.9
    • gunicorn: 21.2.0
    • pydantic-settings: 2.2.0
    • supabase: 2.3.5

Thanks in advance!

Funding

  • You can sponsor this specific effort via a Polar.sh pledge below
  • We receive the pledge once the issue is completed & verified
<picture> <source media="(prefers-color-scheme: dark)" srcset="https://polar.sh/api/github/emmett-framework/granian/issues/216/pledge.svg?darkmode=1"> Fund with Polar </picture>

About this issue

  • Original URL
  • State: closed
  • Created 4 months ago
  • Comments: 24 (13 by maintainers)

Commits related to this issue

Most upvoted comments

Hello @gi0baro,

I haven’t had a chance to review version 1.2 in the Linux environment yet. I plan to do so later this week when I have some free time.

The following code can reproduce this problem. I don’t care whether it is caused by Starlette or Granian, but if this problem persists, I can only continue to use Uvicorn or Gunicorn and cannot migrate to Granian. I did not encounter this problem when using Uvicorn and Gunicorn

from fastapi import FastAPI
from collections.abc import Callable
from typing import Any

from fastapi import Request
from starlette.middleware.base import BaseHTTPMiddleware


class AccessMiddleware(BaseHTTPMiddleware):
    """
    记录请求日志
    """

    async def dispatch(self, request: Request, call_next: Callable[[Any], Any]) -> Any:
        # start_time = datetime.now()
        response = await call_next(request)
        # end_time = datetime.now()
        # 用时 单位秒
        # use_time = (end_time - start_time).total_seconds()
        # logger.info(
        #     f"{response.headers.get('X-Request-ID')} {response.status_code} "
        #     f"{request.client.host if request.client else ''} "
        #     f"{request.method} {request.url} {use_time}"
        # )
        return response


app = FastAPI()
app.add_middleware(AccessMiddleware)


@app.get("/")
def hello():
    return "hello"
 granian --interface asgi 1:app --host=0.0.0.0 --port=9000  --workers=2

[INFO] Starting granian (main PID: 81160)
[INFO] Listening at: 0.0.0.0:9000
[INFO] Spawning worker-1 with pid: 81162
[INFO] Spawning worker-2 with pid: 81163
[INFO] Started worker-1
[INFO] Started worker-1 runtime-1
[INFO] Started worker-2
[INFO] Started worker-2 runtime-1
[ERROR] Application callable raised an exception
Traceback (most recent call last):
  File "/Users/wangf/Desktop/work_shuan/web/后端/GaoKao/.venv/lib/python3.10/site-packages/granian/_futures.py", line 4, in future_watcher
    await inner(watcher.scope, watcher.proto)
  File "/Users/wangf/Desktop/work_shuan/web/后端/GaoKao/.venv/lib/python3.10/site-packages/fastapi/applications.py", line 1054, in __call__
    await super().__call__(scope, receive, send)
  File "/Users/wangf/Desktop/work_shuan/web/后端/GaoKao/.venv/lib/python3.10/site-packages/starlette/applications.py", line 116, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/Users/wangf/Desktop/work_shuan/web/后端/GaoKao/.venv/lib/python3.10/site-packages/starlette/middleware/errors.py", line 164, in __call__
    await self.app(scope, receive, _send)
  File "/Users/wangf/Desktop/work_shuan/web/后端/GaoKao/.venv/lib/python3.10/site-packages/starlette/middleware/base.py", line 192, in __call__
    await response(scope, wrapped_receive, send)
  File "/Users/wangf/Desktop/work_shuan/web/后端/GaoKao/.venv/lib/python3.10/site-packages/starlette/responses.py", line 259, in __call__
    await wrap(partial(self.listen_for_disconnect, receive))
  File "/Users/wangf/Desktop/work_shuan/web/后端/GaoKao/.venv/lib/python3.10/site-packages/starlette/responses.py", line 255, in wrap
    await func()
  File "/Users/wangf/Desktop/work_shuan/web/后端/GaoKao/.venv/lib/python3.10/site-packages/starlette/responses.py", line 232, in listen_for_disconnect
    message = await receive()
  File "/Users/wangf/Desktop/work_shuan/web/后端/GaoKao/.venv/lib/python3.10/site-packages/starlette/middleware/base.py", line 52, in wrapped_receive
    msg = await self.receive()
CancelledError: Future cancelled.
[ERROR] Application callable raised an exception
Traceback (most recent call last):
  File "/Users/wangf/Desktop/work_shuan/web/后端/GaoKao/.venv/lib/python3.10/site-packages/granian/_futures.py", line 4, in future_watcher
    await inner(watcher.scope, watcher.proto)
  File "/Users/wangf/Desktop/work_shuan/web/后端/GaoKao/.venv/lib/python3.10/site-packages/fastapi/applications.py", line 1054, in __call__
    await super().__call__(scope, receive, send)
  File "/Users/wangf/Desktop/work_shuan/web/后端/GaoKao/.venv/lib/python3.10/site-packages/starlette/applications.py", line 116, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/Users/wangf/Desktop/work_shuan/web/后端/GaoKao/.venv/lib/python3.10/site-packages/starlette/middleware/errors.py", line 164, in __call__
    await self.app(scope, receive, _send)
  File "/Users/wangf/Desktop/work_shuan/web/后端/GaoKao/.venv/lib/python3.10/site-packages/starlette/middleware/base.py", line 192, in __call__
    await response(scope, wrapped_receive, send)
  File "/Users/wangf/Desktop/work_shuan/web/后端/GaoKao/.venv/lib/python3.10/site-packages/starlette/responses.py", line 259, in __call__
    await wrap(partial(self.listen_for_disconnect, receive))
  File "/Users/wangf/Desktop/work_shuan/web/后端/GaoKao/.venv/lib/python3.10/site-packages/starlette/responses.py", line 255, in wrap
    await func()
  File "/Users/wangf/Desktop/work_shuan/web/后端/GaoKao/.venv/lib/python3.10/site-packages/starlette/responses.py", line 232, in listen_for_disconnect
    message = await receive()
  File "/Users/wangf/Desktop/work_shuan/web/后端/GaoKao/.venv/lib/python3.10/site-packages/starlette/middleware/base.py", line 52, in wrapped_receive
    msg = await self.receive()
CancelledError: Future cancelled.

@Kludex thank you for the explanation. I think that makes sense, it probably happens with Granian only 'cause the concurrency flow might be a bit different from uvicorn in dealing with recv/send messages.

@GeoKafkias please open a discussion on Starlette repo as @Kludex suggested, you can mention this issue as a starting point. I’m closing this, feel free to mention me in Starlette repo if you need anything from my side.