Mariusz Felisiak, a Django and Python contributor and a Django Fellow, explores the world of ASGI servers. Django on Fly.io is pretty sweet! Check it out: you can be up and running on Fly.io in just minutes.
Asynchronous support in Django is constantly improving and each version introduces new features. With already implemented:
- asynchronous views and middlewares,
- asynchronous ORM interface,
- asynchronous support for decorators and signal dispatchers,
and growing support in contrib
packages (like auth
or sessions
), it’s now possible
to create a fully-asynchronous real world application in Django. You can find more
details on this topic in one of my
previous blog posts.
To efficiently serve an asynchronous app and avoid unnecessary context-switching that
causes a performance penalty,
we need an ASGI server (Asynchronous
Server Gateway Interface). This article explores the most
popular ASGI web servers and how to integrate them in our development and deployment
processes.
Let’s start with a brief introduction of the ASGI application
.
ASGI application
The application
object is used by the ASGI-compatible web servers to communicate with
your project. ASGI application
is defined in the asgi.py
file generated by the
startproject
management command in the project root directory. This works analogously
to the WSGI application
defined for WSGI-compatible web servers in the wsgi.py
. This
is what the hello_django
project structure looks like with two entry-points for web
servers:
hello-django/
manage.py
hello_django/
__init__.py
settings.py
urls.py
asgi.py ← Entry-point for ASGI-compatible web servers.
wsgi.py ← Entry-point for WSGI-compatible web servers.
Most ASGI web servers accept path to the application
callable, so we will pass it as:
<project name>.asgi:application
, e.g. hello_django.asgi:application
.
Now let’s dive into the most popular ASGI web servers 🤿
Daphne
Daphne is a pure-Python ASGI server that supports HTTP/1, HTTP/2, and WebSockets. It’s written and maintained under the wing of the Django organization which is a big plus from a long-term support perspective. It should be maintained as long as Django exists.
Another advantage is that it provides integration with the built-in runserver
command,
which allows you to run your project
under ASGI during development
and benefit from all runserver
functionalities, such as system checks, auto-reloader,
or readable logs.
To enable runserver
integration, add daphne
to the INSTALLED_APPS
and define
ASGI_APPLICATION
with the path to your ASGI application
in your settings file.
For example:
# hello_world/settings.py
INSTALLED_APPS = [
"daphne", # ← Added.
...,
]
ASGI_APPLICATION = "hello_django.asgi.application" # ← Added.
Now runserver
will use Daphne development server:
python manage.py runserver
Watching for file changes with StatReloader
Performing system checks...
System check identified no issues (0 silenced).
March 19, 2024 - 10:18:47
Django version 5.0.3, using settings 'hello_django.settings'
Starting ASGI/Daphne version 4.1.0 development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
To deploy on ASGI using Daphne, first make sure that it’s installed and listed in
requirements.txt
:
python -m pip install Daphne
pip freeze > requirements.txt
Second, update Dockerfile
generated by fly launch
to use Daphne instead of Gunicorn:
# Dockerfile
...
EXPOSE 8000
# ↓↓ Update to Daphne ↓↓
CMD ["daphne", "-b", "0.0.0.0", "-p", "8000", "hello_django.asgi:application"]
Hypercorn
Hypercorn is a pure-Python hybrid ASGI/WSGI
server that supports HTTP/1, HTTP/2, HTTP/3 (optionally using
aioquic
library), and WebSockets. It’s really
flexible and can be used both during development and as a production web server. It also
has a medley of configuration options, including:
--worker-class
: the type of worker to use, with support for various worker classes:asyncio
- defaultuvloop
- available when installed withhypercorn[uvloop]
trio
- available when installed withhypercorn[trio]
--reload
: enables auto-reload on code changes, which is very handy for local development.
To deploy on ASGI using Hypercorn, first make sure that it’s installed and listed in
requirements.txt
:
python -m pip install hypercorn
pip freeze > requirements.txt
Second, update Dockerfile
generated by fly launch
to use Hypercorn instead of
Gunicorn:
# Dockerfile
...
EXPOSE 8000
# ↓↓ Update to Hypercorn ↓↓
CMD ["hypercorn", "--bind", ":8000", "--workers", "2", "hello_django.asgi:application"]
Uvicorn
Uvicorn is a pure-Python ASGI server based on uvloop
that
supports HTTP/1.1 and WebSockets. Use the uvicorn
command to run your project under
ASGI during development:
uvicorn hello_django.asgi:application
INFO: Started server process [6591]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
Passing --reload
causes the server to reload when Python files are changed. We can
pass --reload-include
to extend this behavior for other files, e.g. HTML templates:
uvicorn --reload --reload-include *.py --reload-include *.html hello_django.asgi:application
INFO: Will watch for changes in these directories: ['/examples/hello_django']
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO: Started reloader process [6682] using WatchFiles
INFO: Started server process [6684]
INFO: Waiting for application startup.
INFO: Application startup complete.
To deploy on ASGI using Uvicorn, first make sure that both Uvicorn and Gunicorn are
installed and listed in requirements.txt
:
python -m pip install uvicorn gunicorn
pip freeze > requirements.txt
Second, update Dockerfile
generated by fly launch
to start Gunicorn using the
Uvicorn worker class:
# Dockerfile
...
EXPOSE 8000
# ↓↓ Update to the Uvicorn worker class ↓↓
CMD ["gunicorn", "--bind", ":8000", "--workers", "2", "-k", "uvicorn.workers.UvicornWorker", "hello_django.asgi:application"]
Gunicorn is a combat-proven web server that implements many essential features, that’s why it’s recommended for running Uvicorn workers in a production environment.
Granian
Granian is a newish option in ASGI
servers world. It is a Rust 🦀 hybrid ASGI/WSGI/RSGI
server that supports HTTP/1, HTTP/2, and WebSockets. It can be used both during
development and as a production web server. It has a myriad of configuration options and
provides an auto-reloader (when installed with granian[reload]
) that is useful for
development. Use the granian
command to develop your project under ASGI:
granian --reload hello_django.asgi:application
[INFO] Starting granian (main PID: 6049)
[INFO] Listening at: 127.0.0.1:8000
[INFO] Spawning worker-1 with pid: 6050
[INFO] Started worker-1
[INFO] Started worker-1 runtime-1
The only thing I personally miss is the lack of access logs, but this may change in the future.
To deploy on ASGI using Granian, first make sure that it’s installed and listed in
requirements.txt
:
python -m pip install granian
pip freeze > requirements.txt
Second, update Dockerfile
generated by fly launch
to use Granian instead of
Gunicorn:
# Dockerfile
...
EXPOSE 8000
# ↓↓ Update to Granian ↓↓
CMD ["granian", "--interface", "asgi", "--host", "0.0.0.0", "--port", "8000", "--workers", "2", "hello_django.asgi:application"]
Closing thoughts
The world of ASGI servers is truly rich and everyone should find something that suits their personal preferences. This article mentions just some of the most popular and solid options. Choosing an ASGI server for a production environment is like picking an ice cream flavor - everyone has to find their favorite type based on personal taste.
Try them and share!