When people start Django development, many of them encounter the word “WSGI” and most of them still don’t know what WSGI is and stands for what. I’ll touch on these points in this article.
“WSGI” stands for “Web Server Gateway Interface”. It’s a Python standard that tells how a web server and web applications should be built together. It’s just a simple and universal specification of communication between a web server and web applications, how a web server interacts with a web application and how a web application handles requests.
WSGI has two parts:
- Server/Gateway: HTTP Server (Nginx or Apache), which is responsible for receiving requests from the client and forwarding to the application and returning the application response to the client.
- Application/Framework: A Python web application or web framework that receives requests forwarded by the WSGI Server, process the requests, executes the logic and prepares a response and sends it to the server.
We can write a very simple web application like this:
def application(environ, start_response): start_response('200 OK', [('Content-Type', 'text/plain')]) return b'Hello World!'
environ is a dictionary containing CGI environment and start_response is a callable which includes business logic and takes two required parameters HTTP status and response_headers. Status and headers are returned to the server through the start_response method, and a response body is also returned as an iterable of byte strings.
Why Do We Need WSGI?
The way of handling requests in Django is sequential. While first request is processing, second request waits in queue, it means second request won’t be handled until the first request is finished. To deal with this concurrency problem, we benefit from WSGI implementations such as uWSGI. We can use Nginx+uWSGI to provide high concurrency for Django.
How Django Implements WSGI?
1- Program Entry: runserver command
Since runserver is an implementation of django BaseCommand, you can execute this command method via
python manage.py runserver. The first method to be executed will be
output = self.handle(*args, **options) # inside BaseComman.execute()
The runserver command overrides this method and calls the
run method, and ongoing method/function calls will be made in this order:
- Call self.run method inside self.handle
- Call self.inner_run method inside self.run
- Call wsgi entry function basehttp.run inside self.inner_run
In this method, both server and application implementation of the WSGI protocol is handled.
Inside basehttp.run, a WSGIServer class is created and instantiated. The server part of the WSGI protocol is done.
We passed a wsgi handler value inside the inner_run method to
basehttp.run function call. This parameter is generated via
Inside this function, django.setup() is called, where the web application part is set. After this call, get_wsgi_application function returns a
WSGIHandler instance. In the end,
httpd.set_app(wsgi_handler) is executed and the application part of the WSGI protocol is done.
Finally, the server starts to listen port via
2- Processing Requests
We saw earlier that the inside
get_wsgi_application function returned a
WSGIHandler instance. This class inherits from
BaseHandler class contains almost whole the request processing flow. We can see
get_response(request) methods to process the incoming requests.
Here we focus on the
_get_respons() method which processes the request and prepares a response.
def _get_response(self, request): # inside BaseHandler """ Resolve and call the view, then apply view, exception, and template_response middleware. This method is everything that happens inside the request/response middleware. """ response = None callback, callback_args, callback_kwargs = self.resolve_request(request) # Apply view middleware for middleware_method in self._view_middleware: response = middleware_method(request, callback, callback_args, callback_kwargs) if response: break if response is None: wrapped_callback = self.make_view_atomic(callback) # If it is an asynchronous view, run it in a subthread. if asyncio.iscoroutinefunction(wrapped_callback): wrapped_callback = async_to_sync(wrapped_callback) try: response = wrapped_callback(request, *callback_args, **callback_kwargs) except Exception as e: response = self.process_exception_by_middleware(e, request) if response is None: raise # Complain if the view returned None (a common error). self.check_response(response, callback) # If the response supports deferred rendering, apply template # response middleware and then render the response if hasattr(response, 'render') and callable(response.render): for middleware_method in self._template_response_middleware: response = middleware_method(request, response) # Complain if the template response middleware returned None (a common error). self.check_response( response, middleware_method, name='%s.process_template_response' % ( middleware_method.__self__.__class__.__name__, ) ) try: response = response.render() except Exception as e: response = self.process_exception_by_middleware(e, request) if response is None: raise return response
Firstly, the request url is resolved via
resolve_request(request) method. Then all the middleware methods are applied to request, and finally if there is a match between the request url and any of the endpoints in the url, then the corresponding view will be dispatched for that request.
from django.conf.urls import url urlpatterns = [ url(r'^example/', view.ExampleView.as_view(), name='example'), ]
The view will prepare the output for the response. The response will be rendered if it should, then it’ll be returned to WSGIServer.