Django HttpRequest and HttpResponse

Posted on with tags: django

Django view "is simply a Python function that takes a web request and returns a response." A typical view function loads template, retrieves some data from database, and returns a response. Django 1.3 starts supporting class-based views, which makes writing views easier. The drawback is that the code is not as straightforward to understand as function-based view.

Here is a typical Django view function from official Django tutorial.

def index(request):
    latest = Question.objects.order_by('-pub_date')[:5]
    template = loader.get_template('polls/index.html')
    context = { 'latest': latest }
    return HttpResponse(template.render(context, request))

The request object passed to the view function is a WSGIRequest object, and WSGIRequest is a subclass of HttpRequest. When web browser sends an http/https request to the web server, Django web framework receives the infomation in the form of an environ object, and creates the request object. Yes, Django creates this object and passes it to the view function. This is a typical example of inversion of control. A web app developer does not have control over the flow of a program when using a web framework.

The HttpRequest and HttpResponse classes are defined in the request.py and response.py files in http directory. The response.py file also defines several other classes such as Http404 and HttpResponseRedirect.

The Django official documentation has a reference page for HttpRequest and HttpResponse. It is probably to easier to read the source code directly. Here is a list of files in the http directory.

george@STK2M3:~/Desktop/django-2.2.2/django$ wc -l http/*.py
   26 http/cookie.py
   21 http/__init__.py
  688 http/multipartparser.py
  602 http/request.py
  559 http/response.py
 1896 total

How do I find this information? The grep command below shows that HttpRequest only appears a few places in Django source code. The wsgi.py file defines the WSGIRequst class as a subclass of HttpRequest.

george@STK2M3:~/Desktop/django-2.2.2/django$ grep -r "HttpRequest" ./
./template/context.py:    Create a suitable Context from a plain dict ...
./core/handlers/base.py:        """Return an HttpResponse object for  ...
./core/handlers/wsgi.py:from django.http import HttpRequest, QueryDict, parse_cookie
./core/handlers/wsgi.py:class WSGIRequest(HttpRequest):    ++++++ Notice ++++
./core/files/uploadhandler.py:        >>> from django.http import HttpRequest
./core/files/uploadhandler.py:        >>> request = HttpRequest()
./views/decorators/debug.py:from django.http import HttpRequest
......

We can then try to find where the WSGIRequest object is defined and passed along to other functions as an argument.

george@STK2M3:~/Desktop/django-2.2.2/django$ grep -rn "WSGIRequest" ./core
./core/management/commands/runserver.py:57:   # way to reach WSGIRequestHandler....
./core/handlers/wsgi.py:66:class WSGIRequest(HttpRequest):
./core/handlers/wsgi.py:131:    request_class = WSGIRequest
...

In the file wsgi.py line 131, it assigns WSGIRequest class to request_class. Let's try to find where request_class is instantiated.

george@STK2M3:~/Desktop/django-2.2.2/django$ grep -rn "request_class" ./core
./core/handlers/wsgi.py:131:    request_class = WSGIRequest
./core/handlers/wsgi.py:140:        request = self.request_class(environ)

Grep command can also see the lines before and after the target line. The command below shows the context code.

george@STK2M3:~/Desktop/django-2.2.2/django$ grep -rn -C 5 "request_class" ./core
....
./core/handlers/wsgi.py-130-class WSGIHandler(base.BaseHandler):
./core/handlers/wsgi.py:131:    request_class = WSGIRequest
./core/handlers/wsgi.py-132-
./core/handlers/wsgi.py-133-    def __init__(self, *args, **kwargs):
./core/handlers/wsgi.py-134-        super().__init__(*args, **kwargs)
./core/handlers/wsgi.py-135-        self.load_middleware()
./core/handlers/wsgi.py-136-
./core/handlers/wsgi.py-137-    def __call__(self, environ, start_response):
./core/handlers/wsgi.py-138-        set_script_prefix(get_script_name(environ))
./core/handlers/wsgi.py-139-        signals.request_started.send(sender=self.__class__, environ=environ)
./core/handlers/wsgi.py:140:        request = self.request_class(environ)
./core/handlers/wsgi.py-141-        response = self.get_response(request)
./core/handlers/wsgi.py-142-
./core/handlers/wsgi.py-143-        response._handler_class = self.__class__
./core/handlers/wsgi.py-144-
./core/handlers/wsgi.py-145-        status = '%d %s' % (response.status_code, response.reason_phrase)

The wsgi.py line 141 response = self.get_response(request) is where the request is passed to the view function. It is also where the response created in the view function passed back to the Django. The WSGI discussion clearly shows that the environ argument is the one passed on by the web server.

"The get_response method is where nearly all of the activity happens." James Bennett's article How Django processes a request has a very detailed discussion on the topic.

If you want to find out what information is contained in the reqeust object, the view function below displays the contents.

def display_meta(request):  # from django book
    values = request.META.items()
    values = sorted(values)
    html = []
    for k, v in values:
        html.append('<tr><td>%s</td><td>%s</td></tr>' % (k, v))

    txt = '<table>%s</table>' % ('\n'.join(html) )  # added by me
    txt += '<p>Number of items in META : ' + str(len(request.META)) + '</p>' 
    txt += '<p>request.path : ' + str(request.path) + '</p>' 
    txt += '<p>request.get_host() : ' + str(request.get_host()) + '</p>' 
    txt += '<p>request.get_full_path() : ' + str(request.get_full_path()) + '</p>' 
    txt += '<p>request.is_secure() : ' + str(request.is_secure()) + '</p>' 
    txt += '<p>request.method : ' + str(request.method) + '</p>' 
    txt += '<p>request.encoding : ' + str(request.encoding) + '</p>' 
    txt += '<p>request.content_type : ' + str(request.content_type) + '</p>' 
    print(type(request))  # for debug only
    return HttpResponse(txt)

Here is the partial output from the display_meta function:

......
XDG_SESSION_DESKTOP cinnamon
XDG_SESSION_ID  c2
XDG_SESSION_PATH    /org/freedesktop/DisplayManager/Session0
XDG_SESSION_TYPE    x11
XDG_VTNR    7
_   /home/george/.venv/django/bin/python
wsgi.errors <_io.TextIOWrapper name='' mode='w' encoding='UTF-8'>
wsgi.file_wrapper   
wsgi.input  
wsgi.multiprocess   False
wsgi.multithread    True
wsgi.run_once   False
wsgi.url_scheme http
wsgi.version    (1, 0)

Number of items in META : 85
request.path : /info/
request.get_host() : 127.0.0.1:8000
request.get_full_path() : /info/
request.is_secure() : False
request.method : GET
request.encoding : None
request.content_type : text/plain