Page requests to the metal - Backend web framework
This post is part of the page requests to the metal series where we cover all the steps from a web request down to the electrons that move in the CPU, see the intro/overview page to see where this fits in.
Once the general purpose web framework such as NGinx or Apache has handled the incoming request and has found that the URL will need processing by a Python endpoint it will use a WSGI interface to pass the information over to a Python process.
Since dealing with web requests is a common activity for people making internet apps there's a class of Python software that are known as Web frameworks that will handle the most common web related programming tasks. We will use Django for the example here, but in practice we could use other frameworks like Flask too and it would only really change this one section of the article.
Django
At this point the code enters into our web framework, in this case Django.
Django URL routing:
The web framework must create the needed content by first dealing with the incoming URL to decide what content needs to be generated and how errors are to be handled. The first step is parsing the incoming URL paths and then dispatching to the relevant functions that must be called for that URL.
The Django URL dispatcher takes the incoming path and then routes it to the appropriate view function that will handle that incoming request. In Django's settings.py we have the following:
ROOT_URLCONF = 'ToTheMetal.urls'
This specifies the Python module where Django has the entry point for
URL dispatching. This is the first place that will be searched for a
variable called urlpatterns
which should be a list containing
instances of django.conf.urls.url()
types.
The base urls file (ToTheMetal/ToTheMetal/urls.py) is fairly simple:
from django.conf.urls import url, include
from django.contrib import admin
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^$', views.add_two_numbers_page, name='add-two-numbers-page'),
You'll notice that there are url
"tuples" of regular expressions
that define URL paths and functions. When a regex gets a match Django
will call the associated function. Django's URL dispatcher works by
linearly searching this list until the first regex match occurs at which
point the associated URL handler function gets called with any of the
results in regex capture groups getting forwarded the function as
arguments. If we match '^admin/
it takes us to the admin page and
otherwise if we match ^$
(which matches an empty string, the base URL) it will then
take us to the URLs defined in the python module add_two_numbers.urls
.
Because we are using Python module paths match filesystem paths so that
module is found at ToTheMetal/add_two_numbers/urls.py
:
# URL routing for the add_two_numbers app
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.add_two_numbers_page, name='add-two-numbers-page'),
Now if we match the regex ^$
again we call the function in the second
argument which is views.add_two_numbers_page
. This is our view
function.
If no match can be made by the end of the urlpatterns
Django will call
the 404 error
handler
and if no custom handler is defined it will use the default one to
respond with a 404 status code.
Django view
The Django view is the code that deals with the incoming request and generates the HTTP response.
In this case we have a function add_two_numbers_page
in
ToTheMetal/add_two_numbers/views.py
from django.template import loader, RequestContext
from django.http import HttpResponse
from .forms import AddTwoNumbersForm
def add_two_numbers(first, second):
"""Add two numbers together
:first: first number
:second: second number
"""
result = first + second
return result
def add_two_numbers_page(request):
"""Add two numbers"""
if request.method == "GET":
form = AddTwoNumbersForm()
template = loader.get_template("add_two_numbers/add_two_numbers.html")
return HttpResponse(template.render({'form': form}, request))
elif request.method == "POST":
form = AddTwoNumbersForm(request.POST)
if form.is_valid():
result = add_two_numbers(
form.cleaned_data["number1"],
form.cleaned_data["number2"]
)
return HttpResponse("Answer is {}".format(result))
This takes the incoming request
and deals with what's needed to
handle HTTP responses and render the page.
When a user submits a POST request we go down the "POST"
branch of the
conditional and then need to check that the form input is valid. It
turns out the properly handling forms is a bit complex so instead of
writing a lot of code for processing forms each time we have a new
project we can use the well tested forms functionality found in
Django. So our
form code looks like this
(ToTheMetal/add_two_numbers/forms.py):
from django import forms
class AddTwoNumbersForm(forms.Form):
"""A form for adding two numbers together"""
number1 = forms.IntegerField(label="number1")
number2 = forms.IntegerField(label="number2")
This generated the HTML for the form earlier and now will generate all
the form validation code for when we call .is_valid()
as well. This
saves us a lot of time (for example it implemented the CSRF protection
for us) but introduces another layer of abstraction.
After this we again use the Django forms functionality to clean the data
before calling the add_two_numbers
function.
In the next steps we will see how the add_two_numbers
function creates
the answer by looking into how Python itself executes this code.
This post is part 5 of the "PageRequestsToTheMetal" series:
- Page requests to the metal - introduction
- Page requests to the metal - frontend
- Page requests to the metal - Network stack - From frontend to backend
- Page requests to the metal - Backend - What happens on the server
- Page requests to the metal - Backend web framework *
- Page requests to the metal - Backend implementation
- Page requests to the metal - hardware level