How do I write an ASGI compliant middleware (while staying framework-agnostic)?

Question:

We’re currently maintaining code written in several HTTP frameworks (Flask, aiohttp and FastAPI). Rewriting them so they all use the same framework is currently not feasible. There’s some code that I’d like to share across those applications and that would be very well suited for middleware (logging-config, monitoring, authentication, …).

The initial implementation was done by subclassing Flask and worked really well across all Flask-based apps. But it’s unusable in aiohttp or FastAPI.

Creating a framework-agnostic implementation is doable (theoretically) and this morning I’ve taken one of the simpler cases and successfully converted it to WSGI middleware and am able to integrate it in the Flask applications.

But ASGI is giving me some trouble because there’s not much documentation out there for pure ASGI middleware. All the examples show how middleware is written for their framework.

The official ASGI docs is also really really "skinny" on that topic. From what I can tell, it should look something like this (side-questions: "What’s the second argument passed into the constructor?"):

class MyMiddleware:
    def __init__(self, app, something) -> None:
        self.app = app
        self.something = something  # <- what is this second argument?

    async def __call__(self, scope, receive, send):
        print("Hello from the Middleware")
        await self.app(scope, receive, send)

I took Starlette TimingMiddleware as inspiration, but I am unable to integrate it with aiohttp. Probably because their implementation is slightly different.

Considering that there is a section of middleware in the ASGI specification, and that both aiohttp and Starlette implement that spec, shouldn’t there be a way to write a middleware that works in both?

If yes, what am I missing?

Asked By: exhuma

||

Answers:

There is no way to have the same middleware support all three frameworks.

Answered By: Aber Sheeran

If anyone would like to write ASGI complaint class based middleware here is a small example

class CustomASGIMiddleware:
    def __init__(self, app) -> None:
        self.app = app

    async def __call__(self, scope, receive, send):
        print("Hello from the Middleware")
        return await self.app(scope, receive, send)
        # ^^^^ OP forgot to return in the example from original post

You could use __init__ method to pass additional arguments, for example to adding custom headers on per-application basis and then set them in __call__ method to the scope

Usage

application = CustomASGIMiddleware(get_asgi_application())

Here is official documentation.

And couple examples

Answered By: Sardorbek Imomaliev
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.