Slide 1

Slide 1 text

Django’s request/ response cycle [email protected]

Slide 2

Slide 2 text

Let’s recap:

Slide 3

Slide 3 text

What’s a view? “A view function… is simply a Python function that takes a Web request and returns a Web response.” — https://docs.djangoproject.com/en/1.9/topics/http/views/ def my_view(request): return HttpResponse(“it worked!”)

Slide 4

Slide 4 text

How does Django know to call my view? “A URLconf… is a simple mapping between URL patterns (simple regular expressions) to Python functions (your views).” — https://docs.djangoproject.com/en/1.9/topics/http/urls/ patterns = [ url(‘^view/$’, my_view), ... ]

Slide 5

Slide 5 text

OK, but what does Django do before and after calling my view? Well…

Slide 6

Slide 6 text

The Internet Your View

Slide 7

Slide 7 text

The Internet Your View

Slide 8

Slide 8 text

The Internet Your View

Slide 9

Slide 9 text

The Internet Your View GET /view/ HttpRequest HttpResponse 200 OK

Slide 10

Slide 10 text

The Internet Your View But what the heck happens in here? GET /view/ HttpRequest HttpResponse 200 OK

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

Initial server load

Slide 13

Slide 13 text

W ★ ★

Slide 14

Slide 14 text

Extension point “W”: WSGI middleware Use cases: • wrap the entire site in some behavior
 (much code in Django happens “unprotected” by Django middleware) • middleware that’s not Django-specific 
 (most doesn’t need to be…) Examples: • https://github.com/dahlia/wsgi-oauth2 • https://github.com/evansd/whitenoise from django.core.wsgi import get_wsgi_application from wsgioauth2 import github application = get_wsgi_application() client = github.make_client(client_id='...', client_secret='...') application = client.wsgi_middleware(application, secret='...')

Slide 15

Slide 15 text

First request

Slide 16

Slide 16 text

★ M

Slide 17

Slide 17 text

Extension point “M”: Django middleware Use cases: do something on every request — with more Django- specific context than WSGI middleware. Careful: Middleware are classic Django foot-gun! Examples: • https://github.com/carljm/django-secure • https://github.com/django-debug-toolbar/django-debug-toolbar

Slide 18

Slide 18 text

Django middleware example (django-secure) class SecurityMiddleware(object): def __init__(self): self.xss_filter = conf.SECURE_BROWSER_XSS_FILTER ... def process_request(self, request): ... path = request.path.lstrip("/") if (self.redirect and not request.is_secure() and not any(pattern.search(path) for pattern in self.redirect_exempt)): host = self.redirect_host or request.get_host() return HttpResponsePermanentRedirect( "https://%s%s" % (host, request.get_full_path())) def process_response(self, request, response): ... if self.xss_filter and not 'x-xss-protection' in response: response["x-xss-protection"] = "1; mode=block" return response ★ ★ ★

Slide 19

Slide 19 text

WSGI middleware vs Django middleware + Works with other Python frameworks, not just Django. + Limited access to other parts of the stack (e.g. databases). - More confusing API. - Hard to interoperate with Django-specific parts of 
 your app. + Simple API. + Easy integration with the rest of your app. - Django-specific. - Easy to mess up and introduce performance problems. - Misleading name.

Slide 20

Slide 20 text

Request phase

Slide 21

Slide 21 text

S ★

Slide 22

Slide 22 text

U ★ M M

Slide 23

Slide 23 text

Extension point “S”: signals Use case: I’m… not sure, exactly. Timing data? import time from django.core import signal from django.dispatch import receiver @receiver(signals.request_started): def started(sender, **kwargs): print("at=request_started time={0} path={1}".format( time.time(), sender.environ['PATH_INFO'])) @receiver(signals.request_finished) def ended(sender, **kwargs): print("at=request_finished time={0} path={1}".format( time.time(), sender.environ['PATH_INFO'])) Yeah, silly example. But seriously, I don’t really know why you’d use signals — either middleware form works better.

Slide 24

Slide 24 text

Extension point “U”: request-specific urls Use cases: •Modifying URLs based on request details: multi- tenancy, internationalization, … •Probably under-used! class MTMiddleware(object): def process_request(self, request): request.tenant = lookup_tenant(request) request.urlconf = ‘mt.urls.%s’ % request.tenant.slug ★

Slide 25

Slide 25 text

View phase

Slide 26

Slide 26 text

No content

Slide 27

Slide 27 text

Response phase

Slide 28

Slide 28 text

★ M ★ ! S L

Slide 29

Slide 29 text

Extension point “L”: lazy responses Use cases: • Defer response rendering until “later” in the response cycle. • Better support more complex composed views. • Allow composed views or middleware to inspect/modify 
 template/context details. Note: lazy responses are duck-typed! def non_lazy(request): ... content = templ.render(context) return HttpResponse(content) def lazy(request): ... return TemplateResponse(request, tmpl, context)

Slide 30

Slide 30 text

Slide 31

Slide 31 text

A short digression about WSGI The Django control flow

Slide 32

Slide 32 text

No content

Slide 33

Slide 33 text

No content

Slide 34

Slide 34 text

Modifying Django’s request/response cycle: WSGI apps and middleware Django middleware Per-request URLconfs Signals Lazy responses W M U S L

Slide 35

Slide 35 text

It’s a very elegant architecture you’ve got there. Be a shame if something were to happen to it… https://github.com/andrewgodwin/channels

Slide 36

Slide 36 text

Channels The Internet Your View GET /view/ HttpRequest HttpResponse 200 OK Django, today Django, tomorrow The Internet Your View Websocket Channels

Slide 37

Slide 37 text

Thanks y’all! [email protected]

Slide 38

Slide 38 text

Bonus: Colophon

Slide 39

Slide 39 text

Generating diagrams with seqdiag (http://blockdiag.com/en/seqdiag/index.html) seqdiag { client -> server [label = "GET /resource"]; server => database [label = "SELECT * FROM table;"]; client <-- server [label = "{ ... json ... }"]; }

Slide 40

Slide 40 text

No content

Slide 41

Slide 41 text

{% extends "parts/base.jinja" %} {% block content %} === initial server load === {% include "parts/initial-server-load.jinja" %} === request begins === server -> app [label = "__call__(env,\nstart_response)"]; {% include "parts/first-request.jinja" %} {% include "parts/request-phase.jinja" %} ... {% endblock %} %.diag: %.jinja $(jinja2) $< data.json > $@ png/%.png: %.diag $(seqdiag) -T png --antialias --no-transparency $< -o $@