Middlewares on FastAPI
Tags: python, fastapi, asgi, starlette
Published: 2021-02-07 23:06:26

**Originally published on my** [blog](https://www.gealber.com/middlewares-on-fastapi)

Now is time for middlewares on FastAPI. Let me tell you in advance that middlewares on FastAPI are not very different to middlewares on starlette. Given that FastAPI is built on top of starlette, it uses the same approach to middlewares. That is just an ASGI middleware, so to summarize, middlewares in FastAPI are simple ASGI middleware classes.

# Following the breadcrumbs

Regard to this subject the documentation provided with several examples, including the next one, related to a Gzip middleware.

```python
from fastapi import FastAPI
from fastapi.middleware.gzip import GZipMiddleware

app = FastAPI()

app.add_middleware(GZipMiddleware, minimum_size=1000)

@app.get("/")
async def main():
    return "somebigcontent"

```

Let's go for parts here, 
* What does this middleware?

Well if you don't know Gzip is a compression algorithm. Used very often to compress the content passed through the web, given that is supported by the browsers and well is able to reduce the size of JavaScript, CSS and HTML files in good percent. No pretty sure about the benchmark related to 
Gzip, so I'll let that as a homework.

The action of the middleware, in this case, would be to compress the response sent to the client by the server, only of course if the client is able to handle this kind of compression.

In this piece of code the tricky part is the `app.add_middleware(GZipMiddleware, minimum_size=1000)`.
Here, what's supposed to take the function `add_middleware` as arguments? Well let's go to its definition and see if we can figure it out, right?

Here we are:

```python
class Starlette:
    .......

    def add_middleware(self, middleware_class: type, **options: typing.Any) -> None:
        self.user_middleware.insert(0, Middleware(middleware_class, **options))
        self.middleware_stack = self.build_middleware_stack()

    ......
```

So I followed the definition, and as a result guess what. Well, yes is on the `Starlette` class of the `starlette` framework, so what happened here? Given the previous code, we can see that `add_middleware` is a method of `FastAPI` class, but `FastAPI` inherits it directly from the `Starlette` class.

I hope this was clear, the flow is as follows:

```
     +-----------------+
     | Starlette       |
     +-----------------+
     |  ...            |
     |* add_middleware |
     | ...             |
     +-----------------+
            |
            |
            |
            |
     +-----------------+
     | FastAPI         |
     +-----------------+

```

So back into the business, from these snippets:

```python
def add_middleware(self, middleware_class: type, **options: typing.Any) -> None:
    # Body...
```

We can see that `add_middleware` take as an argument a `middleware_class` and other `options` as keyword arguments. Again, what they are referring to, when they say `middleware_class`? Well reading the documentation of `starlette` I could check that:

> Starlette includes several middleware classes for adding behavior that is applied across
> your entire application. These are all implemented as standard ASGI
> middleware classes, and can be applied either to Starlette or to any other ASGI application.
> 
> The Starlette application class allows you to include the ASGI middleware
> in a way that ensures that it remains wrapped by the exception handler.

Pretty tracking business we had made here, right? Now let's take a pause before we move on.

Go and take a coffee, don't worry I'll be here

# Looking at an ASGI middleware class.

Are you ready? Well, let's take a look at the interface that must hold
a class in order to be an ASGI middleware class. According to the ASGI specs:

> It is possible to have ASGI “middleware” - code that plays the role of both server and application, 
> taking in a **scope** and the **send/receive** awaitable **callables**, potentially modifying them, and then calling 
> an inner application.

Not very explicit you may say, right? But looking another examples on these posts:

* [Hello ASGI](https://encode.io/articles/hello-asgi)
* [Introduction to ASGI](https://florimond.dev/blog/articles/2019/08/introduction-to-asgi-async-python-web)
* [ASGI Documentation](https://asgi.readthedocs.io)

You certainly can get the general idea. We'll need to define it similar to this:

```python
class SimpleASGIMiddleware:
    def __init__(self, app):
        self.app = app

    async def __call__(self, scope, receive, send):
        # do something here with the scope, receive and send
        # parameters passed. 
```

Again the ASGI specs come to rescue:

> It takes a **scope**, which is a dict containing details about the specific connection, **send**, 
> an asynchronous callable, that lets the application send event messages to the client, and
> **receive**, an asynchronous callable which lets the application receive event messages from the client.

Now they are pretty explicit on what these parameters are. I think this is enough information to build our
pretty simple middleware.

## Now is time to code

Our simple and almost ridiculous middleware, will just display on the console the info regarded to the client connected. On the next article I'll try to implement a more serious middleware, for now let's keep it simple.

```python

from fastapi import FastAPI
from starlette.types import ASGIApp, Scope, Receive, Send


class SimpleASGIMiddleware:
    def __init__(self, app: ASGIApp):
        self.app = app

    async def __call__(self, scope: Scope, receive: Receive, send: Send):
        await self.app(scope, receive, send)
        client = scope["client"]
        print(f"[CLIENT]: {client}")


app = FastAPI()

app.add_middleware(SimpleASGIMiddleware)


@app.get("/")
async def main():
    return "Hello client"

```

If you take a look at the class there's nothing weird on its definition, I just follow the recipe previously explained.

# Run it, now!

```console
uvicorn main:app --reload
```

The `--reload` flag is to be able to recover on failure, very useful.

Now open another console and test the application with [cURL](https://curl.se/) or any other HTTP client available, or just simple use the browser. If you visit <http://localhost:8000> on the browser, you should get the text "Hello client", but take a look at the logs of the server running on the console. I'm pretty sure you will see something like this:

```console
[CLIENT]: ('127.0.0.1', 48514)
```

If you look closely to the logs, you'll find out how useless this middleware is. Given that this same information is provided by default.

# Further learning

As an advice, I think that you should check out the available middlewares provided by `FastAPi` and `starlette`. Also, there's other third party middlewares, that made a pretty good job too. Here I'm listing you some of them:

* [SentryMiddleware](https://github.com/encode/sentry-asgi)
* [ProxyHeadersMiddleware](https://github.com/encode/uvicorn/blob/master/uvicorn/middleware/proxy_headers.py)
* [datasette-auth-github](https://github.com/simonw/datasette-auth-github)
* [EarlyDataMiddleware](https://github.com/HarrySky/starlette-early-data)

Take a look at this simple projects and give it a try, tinkering is the best way to learn. Bye!