Problem with ASGI lifespan when i use custom middleware

Viewed 128

I came across an interesting behavior. For the application, I wrote a custom middleware for processing the client, which did not wait for a response and disconnected

class NoResponseReturnMiddleware(BaseHTTPMiddleware):
    _ERROR_MESSAGE: str = "No response returned."

    def __init__(self, app: ASGIApp, logger: Logger):
        super().__init__(app)
        self.logger = logger

    async def __call__(self, scope, receive, send) -> None:
        request = Request(scope, receive, send)
        try:
            await super().__call__(scope, receive, send)
        except RuntimeError as exc:
            if await request.is_disconnected() and str(exc) == self._ERROR_MESSAGE:
                self.logger.debug(
                    "Remote client disconnected",
                    extra={
                        "url": request.url,
                        "method": request.method,
                        "path": request.url.path,
                        "query": request.url.query,
                    },
                )
            else:
                raise exc

    async def dispatch(self, request, call_next):
        return await call_next(request)

but when I launch the application, it rises, although I receive a message:

ASGI 'lifespan' protocol appears unsupported.

enter image description here

Locally, the application works fine, but as soon as it is assembled and sent to the register, and then used in the kubernetes deployment, it behaves strangely, and then "dies" altogether (goes into a permanent restart) Inside the pod I get an error

Warning Unhealthy 5m5s (x3 over 5m25s)  kubelet      Liveness probe failed: Get "http://11.11.11.11.1:8000/": dial tcp 11.11.11.11.1:8000: connect: connection refused

Could it be related somehow? I've read that this can cause server crashes

Upd:

Try it out

uvicorn main:app --reload --lifespan on

And you will get an error:

assert scope["type"] in ("http", "websocket") AssertionError

Fix it:

    async def __call__(self, scope, receive, send) -> None:
        if scope["type"] in ["http", "websocket"]:
            request = Request(scope, receive, send)
            ...
        return
1 Answers

You shouldn't implement the __call__ method if you are inheriting BaseHTTPMiddleware. When using it, you should only implement the dispatch method, as mentioned in the documentation: https://www.starlette.io/middleware/#basehttpmiddleware.

ASGI works in blocks. Each middleware (or application) calls the next middleware (or application), and the types of those are either "websocket", "http" or "lifespan".

If the middleware you've created is for "http", then you should have a fallback to the next middleware, see:

if scope["type"] != "http":
    await self.app(scope, send, receive)
# Your middleware code should be here!

We've updated the Starlette documentation to give more information about ASGI middlewares in general. It will be deployed on the next Starlette release, but you can already read it on https://github.com/encode/starlette/blob/master/docs/middleware.md#pure-asgi-middleware.

Related