How Django Works (1)


How websites work in general


The way every website works can be simplified as a pair of HTTP request and response.

  1. A user sends an HTTP request through a client, most likely one of those browsers, such as Firefox, Chrome;
  2. Upon receiving the HTTP request, the website processes the request and sends back an HTTP response.
At HTTP level, every website works this way. They have no knowledge about module, view, template, controller, or any other fancy terms. Below is the HTTP request and response pair generated by me accessing my localhost, where I am running polls – the Django tutorial website.

Request
GET /polls/ HTTP/1.1 Host: 127.0.0.1:8000 Connection: keep-alive Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.125 Safari/537.36 Accept-Encoding: gzip, deflate, sdch Accept-Language: en-GB,en;q=0.8,en-US;q=0.6,zh-CN;q=0.4,zh;q=0.2

Response
HTTP/1.0 200 OK Date: Sun, 02 Aug 2015 07:46:31 GMT Server: WSGIServer/0.2 CPython/3.4.0 Content-Type: text/html; charset=utf-8 X-Frame-Options: SAMEORIGIN

How Python websites work

Django is a web framework written in Python. Like many other Python web frameworks, Django follows WSGI specification, which is a protocol that defines an interface between a Web server and its Python web applications.

The protocol is simple:

  1. The web application provide a callable named "application" to the web server;
  2. The webserver invoke the callable every time there's an HTTP request.

The callable "application" takes two arguments -- one is "environ", the other "start_response". "environ" contains all information about HTTP requests plus environment variables. "start_response" is a callable that help generates HTTP response headers.

1
2
3
def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/plain')])
    yield 'Hello World\n'

The "application" callable serves as the boundary and the only interface between the HTTP server and its Python web applications.
 

How Django implements WSGI 

Taking Django tutorial for example, the application callable is defined in mysite/wsgi.py


1
2
3
4
5
6
7
import os

from django.core.wsgi import get_wsgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings")

application = get_wsgi_application()

get_wsgi_application() is defined site-packages/django/core/wsgi.py


1
2
3
4
5
6
7
8
9
import django
from django.core.handlers.wsgi import WSGIHandler


def get_wsgi_application():
    """    The public interface to Django's WSGI support. Should return a WSGI    callable.
    Allows us to avoid making django.core.handlers.WSGIHandler public API, in    case the internal WSGI implementation changes or moves in the future.
    """    django.setup()
    return WSGIHandler()  

The application callable is actually an instance of WSGIHandler, which is defined in site-packages/django/core/handler/wsgi.py



 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class WSGIHandler(base.BaseHandler):
    initLock = Lock()
    request_class = WSGIRequest

    def __call__(self, environ, start_response):
        # Set up middleware if needed. We couldn't do this earlier, because
        # settings weren't available.
        if self._request_middleware is None:
            with self.initLock:
                try:
                    # Check that middleware is still uninitialized.
                    if self._request_middleware is None:
                        self.load_middleware()
                except:
                    # Unload whatever middleware we got
                    self._request_middleware = None
                    raise

        set_script_prefix(get_script_name(environ))
        signals.request_started.send(sender=self.__class__, environ=environ)
        try:
            request = self.request_class(environ)
        except UnicodeDecodeError:
            logger.warning('Bad Request (UnicodeDecodeError)',
                exc_info=sys.exc_info(),
                extra={
                    'status_code': 400,
                }
            )
            response = http.HttpResponseBadRequest()
        else:
            response = self.get_response(request)

        response._handler_class = self.__class__

        status = '%s %s' % (response.status_code, response.reason_phrase)
        response_headers = [(str(k), str(v)) for k, v in response.items()]
        for c in response.cookies.values():
            response_headers.append((str('Set-Cookie'), str(c.output(header=''))))
        start_response(force_str(status), response_headers)
        if getattr(response, 'file_to_stream', None) is not None and environ.get('wsgi.file_wrapper'):
            response = environ['wsgi.file_wrapper'](response.file_to_stream)
        return response

Now we have found the entry point. Every time our web server receives an HTTP request from users, it invokes the registered callable "application". In the case of Django, WSGIHandler.__call__ is invoked to process the request.

WSGIHandler.__call__ does four things:
  • load internal middlewares;
  • convert environ to an WSGIRequest Object;
  • invoke self.get_response(request) to process the WSGIRequest Object and return an WSGIResponse object;
  • fix response object and return it to web server.

Anything wrong with the default Django configuration?

Yes, in mysite/wsgi.py 
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings")

setdefault is a peculiar (for lack of better word for it) method for dict-like objects. It basically says first invocation will win. Once the key is set to a value for the first time, sebsequent calls to setdefault will not change the value.

The default apache configuration on Linux is most likely prefork, and the default wsgi configration is embedded, as opposed to daemon. If two websites (two different settings) reside in the same server, there will be a time a process is re-used to serve a second website after serving the first. However, the second one cannot win the war of DJANGO_SETTINGS_MODULE because of setdefault is being used. so setting of first application will be loaded. There's some discussion about this issue.

https://code.djangoproject.com/ticket/18518

A fix was committed; it warns the user of the issue above and provides two solutions.
https://github.com/django/django/commit/751a7d0c32746dc6774f1b561db523b25365148a
 

Popular posts from this blog

LeetCode 68 Text Justification (C++, Python)

How Django Works (4) URL Resolution

Python Class Method and Class Only Method