$30 off During Our Annual Pro Sale. View Details »

Django's request/response cycle - Django Under The Hood 2015

Django's request/response cycle - Django Under The Hood 2015

Jacob Kaplan-Moss

November 06, 2015
Tweet

More Decks by Jacob Kaplan-Moss

Other Decks in Technology

Transcript

  1. Django’s request/
    response cycle
    [email protected]

    View Slide

  2. Let’s recap:

    View Slide

  3. 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!”)

    View Slide

  4. 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),
    ...
    ]

    View Slide

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

    View Slide

  6. The Internet
    Your View

    View Slide

  7. The Internet
    Your View

    View Slide

  8. The Internet
    Your View

    View Slide

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

    View Slide

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

    View Slide

  11. View Slide

  12. Initial server load

    View Slide

  13. W


    View Slide

  14. 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='...')

    View Slide

  15. First request

    View Slide


  16. M

    View Slide

  17. 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

    View Slide

  18. 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



    View Slide

  19. 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.

    View Slide

  20. Request phase

    View Slide

  21. S

    View Slide

  22. U

    M
    M

    View Slide

  23. 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.

    View Slide

  24. 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 ★

    View Slide

  25. View phase

    View Slide

  26. View Slide

  27. Response phase

    View Slide


  28. M

    !
    S
    L

    View Slide

  29. 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)

    View Slide


  30. View Slide

  31. A short digression about WSGI
    The Django control flow

    View Slide

  32. View Slide

  33. View Slide

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

    View Slide

  35. 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

    View Slide

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

    View Slide

  37. Thanks y’all!
    [email protected]

    View Slide

  38. Bonus: Colophon

    View Slide

  39. 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 ... }"];
    }

    View Slide

  40. View Slide

  41. {% 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 $@

    View Slide