Snippets

This is a collection of some of the most “fun” uses of uWSGI features.

X-Sendfile emulation

Even if your frontend proxy/webserver does not support X-Sendfile (or cannot access your static resources) you can emulate it using uWSGI’s internal offloading (your process/thread will delegate the actual static file serving to offload threads).

[uwsgi]
...
; load router_static plugin (compiled in by default in monolithic profiles)
plugins = router_static
; spawn 2 offload threads
offload-threads = 2
; files under /private can be safely served
static-safe = /private
; collect the X-Sendfile response header as X_SENDFILE var
collect-header = X-Sendfile X_SENDFILE
; if X_SENDFILE is not empty, pass its value to the "static" routing action (it will automatically use offloading if available)
response-route-if-not = empty:${X_SENDFILE} static:${X_SENDFILE}

Force HTTPS

This will force HTTPS for the whole site.

[uwsgi]
...
; load router_redirect plugin (compiled in by default in monolithic profiles)
plugins = router_redirect
route-if-not = equal:${HTTPS};on redirect-permanent:https://${HTTP_HOST}${REQUEST_URI}

And this only for /admin

[uwsgi]
...
; load router_redirect plugin (compiled in by default in monolithic profiles)
plugins = router_redirect
route = ^/admin goto:https
; stop the chain
route-run = last:

route-label = https
route-if-not = equal:${HTTPS};on redirect-permanent:https://${HTTP_HOST}${REQUEST_URI}

Eventually you may want to send HSTS (HTTP Strict Transport Security) header too.

[uwsgi]
...
; load router_redirect plugin (compiled in by default in monolithic profiles)
plugins = router_redirect
route-if-not = equal:${HTTPS};on redirect-permanent:https://${HTTP_HOST}${REQUEST_URI}
route-if = equal:${HTTPS};on addheader:Strict-Transport-Security: max-age=31536000

Python Auto-reloading (DEVELOPMENT ONLY!)

In production you can monitor file/directory changes for triggering reloads (touch-reload, fs-reload...).

During development having a monitor for all of the loaded/used python modules can be handy. But please use it only during development.

The check is done by a thread that scans the modules list with the specified frequency:

[uwsgi]
...
py-autoreload = 2

will check for python modules changes every 2 seconds and eventually restart the instance.

And again:

Warning

Use this only in development.

Full-Stack CGI setup

This example spawned from a uWSGI mainling-list thread.

We have static files in /var/www and cgis in /var/cgi. Cgi will be accessed using the /cgi-bin mountpoint. So /var/cgi/foo.lua will be run on request to /cgi-bin/foo.lua

[uwsgi]
workdir = /var
ipaddress = 0.0.0.0

; start an http router on port 8080
http = %(ipaddress):8080
; enable the stats server on port 9191
stats = 127.0.0.1:9191
; spawn 2 threads in 4 processes (concurrency level: 8)
processes = 4
threads = 2
; drop privileges
uid = nobody
gid = nogroup

; serve static files in /var/www
static-index = index.html
static-index = index.htm
check-static = %(workdir)/www

; skip serving static files ending with .lua
static-skip-ext = .lua

; route requests to the CGI plugin
http-modifier1 = 9
; map /cgi-bin requests to /var/cgi
cgi = /cgi-bin=%(workdir)/cgi
; only .lua script can be executed
cgi-allowed-ext = .lua
; .lua files are executed with the 'lua' command (it avoids the need of giving execute permission to files)
cgi-helper = .lua=lua
; search for index.lua if a directory is requested
cgi-index = index.lua

Multiple flask apps in different mountpoints

Let’s write three flask apps:

#app1.py
from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello World! i am app1"
#app2.py
from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello World! i am app2"
#app3.py
from flask import Flask
app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello World! i am app3"

each will be mounted respectively in /app1, /app2, /app3

To mount an application with a specific “key” in uWSGI, you use the –mount option:

` --mount <mountpoint>=<app> `

in our case we want to mount 3 python apps, each keyed with what will be the WSGI SCRIPT_NAME variable:

[uwsgi]
plugin = python
mount = /app1=app1.py
mount = /app2=app2.py
mount = /app3=app3.py
; generally flask apps expose the 'app' callable instead of 'application'
callable = app

; tell uWSGI to rewrite PATH_INFO and SCRIPT_NAME according to mount-points
manage-script-name = true

; bind to a socket
socket = /var/run/uwsgi.sock

now directly point your webserver.proxy to the instance socket (without doing additional configurations)

Note: by default every app is loaded in a new python interpreter (that means a pretty-well isolated namespace for each app). If you want all of the app to be loaded in the same python vm, use the –single-interpreter option.

Another note: you may find reference to an obscure “modifier1 30” trick. It is deprecated and extremely ugly. uWSGI is able to rewrite request variables in lot of advanced ways

Final note: by default, the first loaded app is mounted as the “default one”. That app will be served when no mountpoint matches.

rbenv on OSX (should work on other platforms too)

install rbenv

brew update
brew install rbenv ruby-build

(do not set the magic line in .bash_profile as described in the classic howto, as we want to not clobber the environment, and allow uWSGI to get rid of it)

get a uWSGI tarball and build the ‘nolang’ version (it is a monolithic one without language plugins compiled in)

wget https://projects.unbit.it/downloads/uwsgi-latest.tar.gz
tar zxvf uwsgi-latest.tar.gz
cd uwsgi-xxx
make nolang

now start installing the ruby versions you need

rbenv install 1.9.3-p551
rbenv install 2.1.5

and install the gems you need (sinatra in this case):

# set the current ruby env
rbenv local 1.9.3-p551
# get the path of the gem binary
rbenv which gem
# /Users/roberta/.rbenv/versions/1.9.3-p551/bin/gem
/Users/roberta/.rbenv/versions/1.9.3-p551/bin/gem install sinatra
# from the uwsgi sources directory, build the rack plugin for 1.9.3-p551, naming it rack_193_plugin.so
# the trick here is changing PATH to find the right ruby binary during the build procedure
PATH=/Users/roberta/.rbenv/versions/1.9.3-p551/bin:$PATH ./uwsgi --build-plugin "plugins/rack rack_193"
# set ruby 2.1.5
rbenv local 2.1.5
rbenv which gem
# /Users/roberta/.rbenv/versions/2.1.5/bin/gem
/Users/roberta/.rbenv/versions/2.1.5/bin/gem install sinatra
PATH=/Users/roberta/.rbenv/versions/2.1.5/bin:$PATH ./uwsgi --build-plugin "plugins/rack rack_215"

now to switch from one ruby to another, just change the plugin:

[uwsgi]
plugin = rack_193
rack = config.ru
http-socket = :9090

or

[uwsgi]
plugin = rack_215
rack = config.ru
http-socket = :9090

ensure plugins are stored in the current working directory, or set the plugins-dir directive or specify them with absolute path like

[uwsgi]
plugin = /foobar/rack_215_plugin.so
rack = config.ru
http-socket = :9090

Authenticated WebSocket Proxy

App server identifies websocket traffic, authenticates/authorizes the user using whatever CGI variables against the app’s own policies/infrastructure, then offloads/proxies the request to a simple kafka-websocket backend.

First create auth_kafka.py:

from pprint import pprint

def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/plain')])
    return ['It Works!']

def auth_kafka(request_uri, http_cookie, http_authorization):
    pprint(locals())
    return 'true'

import uwsgi
uwsgi.register_rpc('auth_kafka', auth_kafka)

Then create auth_kafka.ini:

[uwsgi]

; setup
http-socket = 127.0.0.1:8000
master = true
module = auth_kafka

; critical! else worker timeouts apply to proxied websocket connections
offload-threads = 2

; match websocket protocol
kafka-ws-upgrade-regex = ^[Ww]eb[Ss]ocket$

; DRY place for websocket check
is-kafka-ws-request =  regexp:${HTTP_UPGRADE};%(kafka-ws-upgrade-regex)

; location of the kafka-ws server
kafka-ws-host = 127.0.0.1:7080

; base endpoint uri for websocket server
kafka-ws-endpoint-uri = /v2/broker/

; call auth_kafka(...); if AUTH_KAFKA gets set, request is good!
route-if = %(is-kafka-ws-request) rpcvar:AUTH_KAFKA auth_kafka ${REQUEST_URI} ${HTTP_COOKIE} ${HTTP_AUTHORIZATION}

; update request uri to websocket endpoint (rewrite only changes PATH_INFO?)
route-if-not = empty:${AUTH_KAFKA} seturi:%(kafka-ws-endpoint-uri)?${QUERY_STRING}

; route the request to our websocket server
route-if-not = empty:${AUTH_KAFKA} httpdumb:%(kafka-ws-host)

Start a “kafka-websocket” server:

nc -l -k -p 7080

Now go to http://127.0.0.1:8000 in a web browser! You should see Hello!. Open chrome inspector or firebug and type:

ws = new WebSocket('ws://127.0.0.1:8000/?subscribe=true')

You should see this request proxied to your nc command! This pattern allows the internal network to host a more-or-less wide-open/generic kafka -> websocket gateway and delegates auth needs to the app server. Using offload-threads means proxied requests do NOT block workers; using httpdumb prevents mangling the request (http action forces HTTP/1.0)

SELinux and uWSGI

SELinux allows you to isolate web application processes from each other, and limits each program to its purpose only. The applications can be placed into strongly isolated individual sandboxes, separating them from one another and from the underlying operating system. Since SELinux is implemented within the kernel, applications do not need to be specifically written or modified to work under SELinux. There is an SELinux security policy for web applications at github well suited for uWSGI. This security policy also supports the uWSGI emperor process running in one domain, and each web application’s worker processes running in a separate domain, requiring only minimal privileges for the worker processes even if Linux namespaces are used. Of course, there is no requirement for emperor mode, or Linux namespaces, to use SELinux with uWSGI.

On Linux it is possible to run each vassal with a dedicated view of the filesystems, ipc, uts, networking, pids and uids. Then each vassal can, for example, modify the filesystem layout, networking, and hostname without damaging the main system. With this setup, privileged tasks, like mounting filesystems, setting hostnames, configuring the network, and setting gid and uid of the worker processes can be done before changing the SELinux security context of the vassals’ process ensuring that only minimal privileges are required for the worker processes.

First configure, compile and load the SELinux web application security policy. Then, relabel the application files. Further information on how to configure web application policies can be found in the README.md included in the SELinux security policy for web applications. Finally, in each vassall’s configuration file, call the setcon function in libselinux to set the web application’s SELinux security context:

[uwsgi]
...
hook-as-user = callret:setcon system_u:system_r:webapp_id_t:s0

where id is the identity of the domain. Example, foo is the identity of the webapp_foo_t domain.

It may be required to load libselinux in the uWSGI address space with the –dlopen option:

/path/to/uwsgi --dlopen /path/to/libselinux.so