Gunicorn on wsgi-django
Marcelino Veloso III,Server
What is a web server? Testdriven.io makes a colorful introduction to the concept. To paraphrase: it accepts requests from clients (like a browser) and serves a response, that's all it does. The responsibility of understanding what was requested and how to respond to the request belongs to the application. But there needs to be a connection between the server and this application. And this connection, this interface, is where wsgi (or asgi) come in.
Interface
wsgi - web server gateway interface - is a python standard described in PEP 3333. Lately there's been a surge in asgi - asynchronous server gateway interface - implementations. For now though I'd like to focus on the traditional wsgi, specifically using the popular gunicorn.
Interface - gunicorn
Gunicorn is the bridge (the interface), if you will, for python-based apps like Django (application) to communicate with the actual web server (e.g. nginx / caddy)
When a runserver
management command is invoked in Django it mimics the interface just described locally. We don't need an elaborate setup of servers in this context since, at this juncture, we're more interested in understanding business logic and application design. Once the context shifts to the realm of containers and the cloud, runserver
becomes ill-advised and gunicorn
becomes a popular choice. Django's warning is explicit:
runserver: ... DO NOT USE THIS SERVER IN A PRODUCTION SETTING. It has not gone through security audits or performance tests. (And that’s how it’s gonna stay. We’re in the business of making web frameworks, not web servers, so improving this server to be able to handle a production environment is outside the scope of Django.)
Relating Interface, Server, and Application
This is how I presently understand the relationship:
---
title: How a click is processed
---
flowchart LR
subgraph server-side
server(((nginx/caddy)))<--->python
subgraph python
gunicorn---django
end
end
subgraph client-side
browser<--http request-response--->server
end
Gunicorn receives requests and processes it through workers. Its docs elaborate:
Gunicorn is based on the pre-fork worker model. This means that there is a central master process that manages a set of worker processes. The master never knows anything about individual clients. All requests and responses are handled completely by worker processes.
Concretizing this description, I have this visual, mental model:
---
title: Visualizing worker processes
---
flowchart LR
subgraph gunicorn-master-process
subgraph django-app-worker-process-1
d1req(request: get endpoint '/about/')
d1res(response: render html template)
d1req--route request url to response view-->d1res
end
subgraph django-app-worker-process-2
d2req(request: search 'hello world')
d2res(response: query db, show results)
d2req--route request url to response view-->d2res
end
subgraph django-app-worker-process-3
d3req(request: user signup)
d3res(response: send email, request confirmation)
d3req--route request url to response view-->d3res
end
end
nginx(((web server)))<--request-response managed by gunicorn--->django-app-worker-process-1
nginx(((web server)))<--request-response managed by gunicorn--->django-app-worker-process-2
nginx(((web server)))<--request-response managed by gunicorn--->django-app-worker-process-3
Default Configuration
According to Django's docs: [runserver] uses the WSGI application object specified by the WSGI_APPLICATION
setting. And in the default settings.py
, we see that
WSGI_APPLICATION = "config.wsgi.application"
So in essence, when we run runserver
on a user-defined project named config
.
python manage.py runserver
It translates to: "connect to config/wsgi.py" and then "use application defined in that file". This is what that file looks like:
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
application = get_wsgi_application()
Relatedly, the gunicorn docs contains a specific section on how it integrates with Django:
Gunicorn will look for a WSGI callable named
application
if not specified. So for a typical Django project, invoking Gunicorn would look like:$
gunicorn myproject.wsgi