One of the best parts of using a modern web framework is that you don’t need to understand much about a request before it hits on of your designated routes. You get to sit down, build a view (or controller if you’re using an MVC framework), and immediately have access to a Request object. This object has all the information you could possibly need about the request (method (GET, POST, etc.), body, headers, path, etc.), in order to start building a web app.
But how does this information actually make it from the internet into a Request object?
In this post we’ll dive into a part of that process, specifically how the data gets passed into Django. In future posts we’ll take a look at what Django actually does with the data once it’s been received. As a disclaimer, we’ll be glossing over *a lot* of detail here. For example, this won’t address topics such as how HTTP requests actually work, or how web servers work under the hood. If you’d like to learn about these subjects, there’s plenty of amazing information available online.
Before talking about Django, we need to introduce three additional pieces of the stack, specifically the web server, the application server, and the web server gateway interface.
The Web Server
If you imagine racks and racks of servers in a warehouse, let me confuse you by saying that isn’t actually what this refers to. A web server is simply a long-running program on a computer (it *could* be a computer in a warehouse, but it could also be your home computer) that monitors the computer’s network ports listening for some type of network request (in this case HTTP or HTTPS). Apache and Nginx are currently the two most popular open source web servers. Once the request is received, it is responsible for serving up some type of response.
So how does the web server process the request and generate a response? That’s the responsibility of the application server.
The Application Server
Once the web server has received the request, it hands off the request to the application server to generate the response. Like the web server, the application server is also a program running on a computer (Gunicorn is a popular app server that we’ll use as our example). It may or may not be the same computer that runs the web server. That decision is usually based on the scale of the site and the chosen infrastructure design. The app server is then responsible for calling the Django code and generating a response.
If you’re confused why you need *both* the web server and the app server, that’s understandable. You don’t technically need separate servers, because popular app servers like Gunicorn are capable of working as both web and app server. But using distinct server types has a few advantages.
- Separating concerns lets you use different computers for different pieces of your tech stack. So it’s common for one computer to run the web server, and then to route requests to one of multiple different computers running the app server to process the request fully. This can greatly increase site performance and stability at scale; at lower scale using multiple computers can slow things down because of the additional network requests necessary.
- Web servers like Nginx are better at specific pieces of request handling than application servers. Rather than using a single generalized server, we can use two servers that excel in their respective areas.
So if the application server is responsible for sending the request to Django and then returning the response to the web server, how does the application server know how to call Django and handle the returned value? Developers don’t typically need to worry about this because WSGI solves this problem for us.
Web Server Gateway Interface (WSGI)
Upon first reading about WSGI years ago, it sounded extremely intimidating and brought to mind esoteric looking code that mere mortals could use but never understand.
Thankfully, it’s actually quite simple.
Python app servers want to be able to run any type of python framework. Frameworks want to run on any type of python app server.
WSGI makes this possible by defining a standard interface between frameworks and servers. So if you’re using Gunicorn to run a Django app, you can easily swap out Gunicorn for something like uWSGI, and this will work because they’re both looking for the same interface within the Django framework. Similarly, they’ll both pass in the same type of data to Django, meaning that Django neither knows nor cares what type of application server it’s running on.
To be clear, this is not a Django specific interface. You could swap out your Django app for a Flask app and it would work just as well, because they both implement the application side of WSGI. WSGI is a formally accepted Python Enhancement Proposal, and is interesting reading for those who are curious.
Implementation in Django
We can now answer the original question by looking in the wsgi.py file within the project package that is automatically created when you run the `startproject` command from the command line. This file, which the application server explicitly looks for, returns the WSGI compliant Django callable.
When a request is received by the app server, it passes the request to this callable, knowing that it will receive a valid HTTP response, which it can then pass to the web server and thus the internet/client.
What actually happens when wsgi.py calls the get_wsgi_application() function? That’s what we’ll dive into next time.
Thanks to Ashish Lal and Rose Liu for reading drafts of this post.