Originally published on my blog
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.
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:
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:
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:
You certainly can get the general idea. We'll need to define it similar to this:
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.
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!
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 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:
[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:
Take a look at this simple projects and give it a try, tinkering is the best way to learn. Bye!
Top comments (0)