The Request-Response Cycle in the ngw2f Stack

What the hell is ngw2f?

It's an acronym I made it up to describe a common Python stack that I use daily. It stands for Nginx, Gunicon, WSGI, Werkzeug and Flask. Cute acronyms are difficult.

I'll be describing this stack by descending from the browser down through the layers to the bottom.

1 - The Stack

First let's define the stack. There are 5 major components we'll be focusing on:

  • Nginx - crazy fast web and proxy server
  • Gunincorn - Python web server (based on Ruby’s Unicorn)
  • WSGI - PEP standard for communication between a server and python application.
  • Werkzeug - Web toolkit, WSGI wrapper
  • Flask - Python web framework

When your browser makes a request Nginx will be listening for the connection on port 80 or 443 for secure connections. ngw2f stack

why two web servers

Thesis: There is a subtle interplay in the components of the NGW2F stack, many pieces could be used independently but would suffer from performance issues or technical shortcomings. Used together they provide a balanced and robust web stack.

2 - HTTP

  • HTTP is a spec that’s been around since ~1990
  • HTTP is a connection, request, response, caching (more complicated in recent RFCs)

2.1 - HTTP Spec History

  • Old
  • Fluid
  • New, exciting changes are happening - Now!

2.2 - Req/Response

  • telnet
  • explain request
  • explain response
  • the request and response are just text (in this example)

3 - Nginx

  • First layer in our stack
  • Web proxy and web server used for many purposes

3.1 - Nginx Overview

  • Basic web server
  • Web proxy
  • SMTP proxy
  • Load balancing

3.2 - Nginx Architecture

Core architecture is evented allowing for insane number of connections.

  • Supports HTTP/HTTPS and Multiplexing
  • Multiple worker processes (= num of procs)
  • Allows hot-upgrade
  • too much to go into here

3.3 - Setup

  • Setup is simple, except on Windows

3.4 - Config

  • Configuration gets complicated fast
  • This is close to a bare minimum config file
worker_processes  1;

events {
    worker_connections  1024;
}

http {
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;

    include /usr/local/etc/nginx/mime.types;
    default_type application/octet-stream;

    access_log /Users/crempp/projects/presentation-ngw2f/part-I-nginx/nginx/logs/access.log;
    error_log /Users/crempp/projects/presentation-ngw2f/part-I-nginx/nginx/logs/error.log;

    server {
        listen 8080 default_server;
        listen [::]:8080 default_server ipv6only=on;
        root /Users/crempp/projects/presentation-ngw2f/part-I-nginx/nginx/html;
        index index.html index.htm;
        server_name localhost;

        location / {
                try_files $uri $uri/ /index.html =404;
        }
    }
}

3.5 - Demo

$ nginx -c config/nginx.conf
$ telnet localhost 8080
  Trying 127.0.0.1...
  Connected to localhost.
  Escape character is '^]'.
  GET / HTTP/1.0
$ nginx -s stop

4 - Gunicorn

  • Second layer in our stack
  • Python web server
  • Gunicorn takes raw HTTP message, parses it and speaks to application using WSGI

4.1 Gunicorn Overview

  • WSGI Compliant
  • Multiple workers
  • Worker management
  • Sync, Async, Tornado, Python3 Async

4.2 - Gunicorn Architecture

  • Master Process spawns and manages workers, does not accept connections
  • Works accept connections and pass them to the application
  • Will not go into the details of the different worker types

4.3 - Gunicorn Hooks

  • 14 hooks allowing custom logic/integrations
  • Some hooks run on the master process, some in the workers
  • Hooks are written in python and included in the Gunicorn config.py

4.4 - Gunicorn Sample App

  • This is a WSGI application which we’ll cover in the next section.

app.py

def application(environ, start_response):
    """Simplest possible WSGI application object"""
    data = 'Hello, World! (from Gunicorn)\n'
    status = '200 OK'

response_headers = [
    ('Content-type','text/plain'),
    ('Content-Length', str(len(data)))
]

start_response(status, response_headers)

return iter([data])

4.5 - Setup/Run

  • Install via pip
  • Lots of command line options
  • Pass the gunicorn command the Python module path of the application
  • If you get an error "SyntaxError: invalid syntax" ignore it, Gunicorn was successfully installed.

4.6 - Demo

Start Nginx

$ nginx -c config/nginx.conf

Start Gunicorn

$ gunicorn --workers=2 app:application

Make a request

$ telnet localhost 8080
  Trying 127.0.0.1...
  Connected to localhost.
  Escape character is '^]'.
  GET / HTTP/1.0

Clean up

$ nginx -s stop

5 - WSGI

  • Web Server Gateway Interface
  • Not a layer in the stack but a way to communicate between layers
  • How servers talk to applications
  • Gunicorn takes raw HTTP message, parses it and speaks to application using WSGI

5.1 - WSGI Spec

  • PEP 333, updated in PEP 3333
  • Server provides ‘environ’ dict - environment vars (parsed request parts, server/os vars)
  • Server provides ‘start_response()’ callback - App calls this to set the status and headers
  • App provides a entry point taking two params - called by server to handle a request
  • App calls start response
  • App returns data for the body (as iterator)

5.2 - WSGI Dispatch

  • Sample Python for how the WSGI dispatch works
  • Server assembles the environ
  • Server calls application entry point
  • Application returns body as utterable
  • Server writes out data

5.3 - WSGI Application

  • Sample python code
  • Very simple

5.4 - Demo

$ cd part-II-wsgi
$ export SECTION=/Users/crempp/projects/presentation-ngw2f/part-II-wsgi
$ nginx -c $SECTION/nginx/config/nginx.conf
$ cd wsgi
$ python app.py
$ telnet localhost 8080
  Trying 127.0.0.1...
  Connected to localhost.
  Escape character is '^]'.
  GET / HTTP/1.0
$ nginx -s stop

5.5 - WSGI Middleware

  • Can chain WSGI applications together to form middleware
  • Fun things to do Insert/Modify headers Caching Pre-application routing Response modification ** Only limit is your imagination

6 - Werkzeug

  • Werkzeug is one of the most advanced WSGI utilities for Python
  • WSGI helpers
  • URL routing
  • HTTP utilities
  • Data Structures
  • Middleware
  • A development server
  • Lots of other utilities

6.1 - Werkzeug Example

  • Example 1) parse and wrap the request and response
  • Example 2) additionally, wrap the application
  • so, so much more you can do

6.2 Demo

(env)$ cd part-IV-werkzeug
(env)$ export SECTION=/Users/crempp/projects/presentation-ngw2f/part-IV-werkzeug
(env)$ nginx -c $SECTION/nginx/config/nginx.conf
(env)$ cd werkzeug
(env)$ gunicorn --workers=2 app:application
(env)$ telnet localhost 8080
  Trying 127.0.0.1...
  Connected to localhost.
  Escape character is '^]'.
  GET / HTTP/1.0
(env)$ nginx -s stop

7 - Flask

  • Flask is an web app framework
  • Flask depends on Werkzeug

7.1 - Flask Overview

  • “Micro” framework (simple but extensible core)
  • Python 3 support (>=3.3) but there are some issues with WSGI and unicode, among other things

7.2 Flask Application

  • Simple application function
  • Gunicorn will call the ‘app’
  • The views can be class-based
  • Dev server

7.3 Demo

(env)$ cd part-V-flask
(env)$ export SECTION=/Users/crempp/projects/presentation-ngw2f/part-V-flask
(env)$ nginx -c $SECTION/nginx/config/nginx.conf
(env)$ cd flask_app
(env)$ gunicorn --workers=2 app:application
(env)$ telnet localhost 8080
  Trying 127.0.0.1...
  Connected to localhost.
  Escape character is '^]'.
  GET / HTTP/1.0
(env)$ nginx -s stop