0% completed
Understanding the Request-Response Cycle
On This Page
The Cycle in Order
The Request Object
Middleware on the Way In
Middleware on the Way Out
The Response Object
Status Codes
When Something Goes Wrong
Request /articles without the final slash, and Django does not answer with "Page not found". It answers with a redirect to /articles/, even though no view in the blog contains any code for that. The redirect comes from a layer of code that wraps every view, and it is one of several things that happen to a request before your view runs and to the response after your view returns.
The whole journey is called the request-response cycle. The previous lesson followed a request from the browser to the view. This lesson covers the complete cycle around the view: the request object the view receives, the middleware the request and the response pass through, the response objects a view can return, and what Django sends back when something goes wrong.
The Cycle in Order
For every request, Django performs the same sequence of steps:
- The web server passes the raw request to Django, and Django builds an
HttpRequestobject from it. - The request passes through each middleware layer, from the first entry in
MIDDLEWAREto the last. - The URL resolver finds the view, and Django calls it with the request.
- The view returns an
HttpResponseobject. - The response passes back through the middleware layers in reverse order, from the last entry to the first.
- Django hands the response to the web server, which sends it to the browser.
In the diagram, notice that the request and the response cross the same seven layers, but in opposite directions. A layer near the top of the list sees the request first and the response last.
The Request Object
The request argument that every view receives is an HttpRequest object. It holds everything the browser sent. To see its most useful attributes, add a small view to the blog that prints them:
# blog/views.py from django.http import HttpResponse def request_info(request): lines = [ f"method: {request.method}", f"path: {request.path}", f"GET: {request.GET}", f"q: {request.GET.get('q', '')}", f"user agent: {request.headers.get('User-Agent', 'unknown')}", f"user: {request.user}", f"authenticated: {request.user.is_authenticated}", ] return HttpResponse("\n".join(lines), content_type="text/plain")
Add path('info/', views.request_info), to urlpatterns in blog/urls.py, then request /articles/info/?q=django from a browser. The user agent line is shortened here, because a real browser sends a much longer value:
method: GET path: /articles/info/ GET: <QueryDict: {'q': ['django']}> q: django user agent: Mozilla/5.0 user: AnonymousUser authenticated: False
Each line comes from one attribute:
request.methodis the HTTP method, such asGETfor reading a page orPOSTfor submitting a form.request.pathis the path without the domain or the query string.request.GETholds the query string parameters, andrequest.POSTholds submitted form fields. Both areQueryDictobjects, a dictionary that can hold several values for one key. Use.get()with a default, because a missing key inrequest.GET['q']raises an error.request.headersholds the HTTP headers, and you can look them up in any letter case.request.COOKIESholds the cookies as a dictionary.request.useris the signed-in user, or anAnonymousUserobject when nobody is signed in.request.sessionholds data Django keeps for this visitor between requests.
The browser never sent a user or a session. Those two attributes were added by middleware on the way in, which is the subject of the next section.
Middleware on the Way In
Middleware is a layer of code that runs for every request and every response, around all views. Each layer can read or change the request, stop it and answer immediately, or let it continue to the next layer. A new project lists seven layers in its settings:
# mysite/settings.py MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ]
On the way in, the request meets these layers from top to bottom. SessionMiddleware reads the session cookie and attaches request.session. AuthenticationMiddleware then uses that session to find the signed-in user and attaches request.user. This is why the order of the list matters: authentication needs the session, so SessionMiddleware must come before AuthenticationMiddleware.
A middleware layer can also refuse a request, and then the view never runs. CsrfViewMiddleware checks every POST request for the secret CSRF token that Django puts into its own forms. A POST to /articles/info/ from a page that has no token is stopped before it reaches request_info, and the development server logs the reason:
Forbidden (CSRF cookie not set.): /articles/info/
The browser receives a response with status 403 Forbidden. The correct fix is to include the token in your forms, which Chapter 5 shows, not to switch the check off.
Middleware on the Way Out
After the view returns a response, the response travels back through the same layers from bottom to top. Each layer can add headers to it, change it, or replace it. Look at the headers of the article list page:
Content-Type: text/html; charset=utf-8 X-Frame-Options: DENY Content-Length: 320 X-Content-Type-Options: nosniff Referrer-Policy: same-origin Cross-Origin-Opener-Policy: same-origin
The view set only the content. XFrameOptionsMiddleware, the last layer, added X-Frame-Options: DENY, which tells browsers not to show the page inside a frame on another site. That header is the clickjacking protection from the first lesson of this chapter. SecurityMiddleware, the first layer, added the last three security headers.
The missing slash from the start of this lesson is handled on the way out as well. When the URL resolver finds no pattern for /articles, Django produces a 404 response. On its way back up, the 404 reaches CommonMiddleware. That layer sees that /articles/ would have matched, and it replaces the 404 with a redirect, because the APPEND_SLASH setting is True by default. These are the headers the browser receives:
Content-Type: text/html; charset=utf-8 Location: /articles/ Content-Length: 0 X-Content-Type-Options: nosniff Referrer-Policy: same-origin Cross-Origin-Opener-Policy: same-origin
The status is 301 Moved Permanently. The X-Frame-Options header is missing because XFrameOptionsMiddleware added it to the 404 response, which CommonMiddleware then threw away. The security headers are present because SecurityMiddleware runs after CommonMiddleware on the way out. This one response shows the reverse order clearly. Chapter 8 shows how to write middleware of your own.
The Response Object
A view must return an HttpResponse object or an object of one of its subclasses. A plain HttpResponse has status 200 and the content type text/html; charset=utf-8 unless you choose otherwise. You can check this in the shell that python manage.py shell opens:
from django.http import HttpResponse response = HttpResponse("Created", status=201) print(response.status_code, response['Content-Type'])
201 text/html; charset=utf-8
The render() shortcut from earlier lessons also returns an HttpResponse, with the rendered template as its content. Django provides other response classes and shortcuts for common cases:
| Code in the view | Class returned | Status |
|---|---|---|
HttpResponse("text") | HttpResponse | 200 |
render(request, 'blog/welcome.html') | HttpResponse | 200 |
JsonResponse({'id': 1}) | JsonResponse | 200 |
redirect('/articles/') | HttpResponseRedirect | 302 |
redirect('/articles/', permanent=True) | HttpResponsePermanentRedirect | 301 |
HttpResponseNotFound("<h1>No such article</h1>") | HttpResponseNotFound | 404 |
JsonResponse turns a dictionary into JSON and sets the content type to application/json. It is used by views that serve data to JavaScript code or to mobile apps instead of HTML pages. Add this view and the pattern path('<int:pk>/json/', views.article_json), to the blog:
# blog/views.py from django.http import JsonResponse from django.shortcuts import get_object_or_404 from .models import Article def article_json(request, pk): article = get_object_or_404(Article, pk=pk) return JsonResponse({'id': article.pk, 'title': article.title})
A request to /articles/1/json/ returns this body:
{"id": 1, "title": "Hello Django"}
redirect() sends the browser to another address, and the browser then makes a second request to it. A temporary redirect (302) is the default, which tells the browser to keep using the old address in future. A permanent redirect (301) tells browsers and search engines that the old address has moved permanently, and browsers may remember it. Use 301 only when you are sure the move is final. For example, this view always sends readers to the article list:
# blog/views.py from django.shortcuts import redirect def latest(request): return redirect('/articles/')
Status Codes
Every response carries a three-digit status code that tells the browser what happened. The first digit gives the class: 2 means success, 3 means redirection, 4 means the request was wrong, and 5 means the server failed. These are the codes you meet most often in this course:
| Code | Name | When Django sends it |
|---|---|---|
| 200 | OK | A view returned a page or data normally |
| 201 | Created | An API created a new record |
| 301 | Moved Permanently | A permanent redirect, such as the missing slash |
| 302 | Found | A temporary redirect, the default of redirect() |
| 403 | Forbidden | A request was refused, such as a failed CSRF check |
| 404 | Not Found | No URL pattern matched, or a view raised Http404 |
| 405 | Method Not Allowed | The view does not accept this method |
| 500 | Internal Server Error | Code raised an exception that nothing handled |
When Something Goes Wrong
The article_detail view from the previous lesson used Article.objects.get(pk=pk). When no article has that id, get() raises Article.DoesNotExist. Nothing in the view catches it, so Django treats it as a server error, and the reader sees status 500 for what is really a missing page.
The fix is to raise Http404 instead. The get_object_or_404() shortcut does that for you:
# blog/views.py from django.http import HttpResponse from django.shortcuts import get_object_or_404 from .models import Article def article_detail(request, pk): article = get_object_or_404(Article, pk=pk) return HttpResponse(article.title)
Now /articles/999/ returns status 404. Django catches the Http404 exception close to the view and turns it into a 404 response. Because the exception has become an ordinary response, it still travels back through the middleware layers, and the 404 page carries the same X-Frame-Options header as any other page.
What the reader sees depends on the DEBUG setting. While DEBUG is True, Django shows a detailed page with the message "No Article matches the given query.", the view that raised the error, and the URL patterns it tried. With DEBUG set to False, as it must be on a public site, Django shows a short page instead:
<h1>Not Found</h1><p>The requested resource was not found on this server.</p>
Any other exception that escapes a view becomes a 500 response in the same way. To see it, add a view that divides by zero, together with the pattern path('broken/', views.broken),, and request /articles/broken/:
# blog/views.py def broken(request): return 1 / 0
With DEBUG = True, the browser shows the full traceback, the local variables, and the settings, and the development server prints the traceback in the terminal:
Internal Server Error: /articles/broken/ Traceback (most recent call last): ... ZeroDivisionError: division by zero
With DEBUG = False, the reader sees only a short error page:
<h1>Server Error (500)</h1><p></p>
The detailed pages are helpful while you develop, but they show your code and settings to anyone who triggers an error, which is why a production site must run with DEBUG = False. You can replace the two short pages with your own design by adding templates named 404.html and 500.html, which Chapter 9 covers together with logging.
Reading Progress
0%
On This Page
The Cycle in Order
The Request Object
Middleware on the Way In
Middleware on the Way Out
The Response Object
Status Codes
When Something Goes Wrong