django gunicorn

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