Friday, May 31, 2013

Django Request/Response Cycle, How Requests Die and Responses Are Born


This summer I decided to start contributing to django.  Looking through the easy picking tickets, I realized it would be beneficial to develop an understanding of some of the core components of the django framework before jumping into tickets.

I plan on writing blog posts about the internals of django, starting with today's:  What is the path of a request through the django framework, and what is the path or a response out of the framework.

Lets begin with describing the entry point, and all major functions that are called for a request/response.  We can then elaborate on the important functions.  Django uses the WSGI standard to interact with web servers, there is a lot of information available about this standard on the web.

Basically, django will be returning a callable application with the signature and return value the same as

def simple_app(environ, start_response):
    """Simplest possible application object"""
    status = '200 OK'
    response_headers = [('Content-type', 'text/plain')]
    start_response(status, response_headers)
    return ['Hello world!\n']


When deploying django the webserver needs to know of the location of the wsgi.py file that belongs to your project, this is where it retrieves the callable application (like the one above)

If we look in the wsgi.py file where django that is included with django we see:



from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()

Ah HA!

So lets start tracing how our application is built!

1. get_wsgi_application is a public wrapper function that returns a WSGIHandler instance.
    Notice how WSGI handler has a lot in common with the above sample application:
   
    Django calculates the correct response status codes and headers and calls the `start_response` function that was passed into the app (from the webserver?) when the application is called.  It then returns the response.

2. So we are already at a dead end! Django returned our app to the webserver.  I don't know when the webserver actually calls our app or what start_response actually does, and we don't need to, we can just trust that the wsgi modules/wsgi servers do things right!  The response is calculated when our application (WSGIHandler instance) is called.  This takes place in WSGIHandlers.__call__.

3. The first thing WSGIHandler does is attempt to load middleware that is registered in the settings.py file.

4. The next significant action is the instantiation of a a new WSGIRequest

5. The last main action is retrieving a response for the new request object in get_response.  This is where the bulk of the request logic comes into play.  This function is responsible for calling all middleware hooks at the correct times, and for resolving a url to a view function, and executing that view function.  This is where those nice error messages of your view did not return a HTTPResponse object comes from!

This has been a quick, pretty high level architecture overview, of which main functions django calls to turn a request into a response.  There ARE other things going on like emitting signals along the way, and a couple config options to prepare a request for processing, but this is pretty much the gist of it.