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 emulateit using uWSGI’s internal offloading (your process/thread will delegate the actual static file serving to offload threads).

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

Force HTTPS

This will force HTTPS for the whole site.

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

And this only for /admin

  1. [uwsgi]
  2. ...
  3. ; load router_redirect plugin (compiled in by default in monolithic profiles)
  4. plugins = router_redirect
  5. route = ^/admin goto:https
  6. ; stop the chain
  7. route-run = last:
  8.  
  9. route-label = https
  10. 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.

  1. [uwsgi]
  2. ...
  3. ; load router_redirect plugin (compiled in by default in monolithic profiles)
  4. plugins = router_redirect
  5. route-if-not = equal:${HTTPS};on redirect-permanent:https://${HTTP_HOST}${REQUEST_URI}
  6. 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:

  1. [uwsgi]
  2. ...
  3. 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 mailing list thread.

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

  1. [uwsgi]
  2. workdir = /var
  3. ipaddress = 0.0.0.0
  4.  
  5. ; start an http router on port 8080
  6. http = %(ipaddress):8080
  7. ; enable the stats server on port 9191
  8. stats = 127.0.0.1:9191
  9. ; spawn 2 threads in 4 processes (concurrency level: 8)
  10. processes = 4
  11. threads = 2
  12. ; drop privileges
  13. uid = nobody
  14. gid = nogroup
  15.  
  16. ; serve static files in /var/www
  17. static-index = index.html
  18. static-index = index.htm
  19. check-static = %(workdir)/www
  20.  
  21. ; skip serving static files ending with .lua
  22. static-skip-ext = .lua
  23.  
  24. ; route requests to the CGI plugin
  25. http-modifier1 = 9
  26. ; map /cgi-bin requests to /var/cgi
  27. cgi = /cgi-bin=%(workdir)/cgi
  28. ; only .lua script can be executed
  29. cgi-allowed-ext = .lua
  30. ; .lua files are executed with the 'lua' command (it avoids the need of giving execute permission to files)
  31. cgi-helper = .lua=lua
  32. ; search for index.lua if a directory is requested
  33. cgi-index = index.lua

Multiple flask apps in different mountpoints

Let’s write three flask apps:

  1. #app1.py
  2. from flask import Flask
  3. app = Flask(__name__)
  4.  
  5. @app.route("/")
  6. def hello():
  7. return "Hello World! i am app1"
  1. #app2.py
  2. from flask import Flask
  3. app = Flask(__name__)
  4.  
  5. @app.route("/")
  6. def hello():
  7. return "Hello World! i am app2"
  1. #app3.py
  2. from flask import Flask
  3. app = Flask(__name__)
  4.  
  5. @app.route("/")
  6. def hello():
  7. 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:

  1. [uwsgi]
  2. plugin = python
  3. mount = /app1=app1.py
  4. mount = /app2=app2.py
  5. mount = /app3=app3.py
  6. ; generally flask apps expose the 'app' callable instead of 'application'
  7. callable = app
  8.  
  9. ; tell uWSGI to rewrite PATH_INFO and SCRIPT_NAME according to mount-points
  10. manage-script-name = true
  11.  
  12. ; bind to a socket
  13. 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

  1. brew update
  2. 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)

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

now start installing the ruby versions you need

  1. rbenv install 1.9.3-p551
  2. rbenv install 2.1.5

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

  1. # set the current ruby env
  2. rbenv local 1.9.3-p551
  3. # get the path of the gem binary
  4. rbenv which gem
  5. # /Users/roberta/.rbenv/versions/1.9.3-p551/bin/gem
  6. /Users/roberta/.rbenv/versions/1.9.3-p551/bin/gem install sinatra
  7. # from the uwsgi sources directory, build the rack plugin for 1.9.3-p551, naming it rack_193_plugin.so
  8. # the trick here is changing PATH to find the right ruby binary during the build procedure
  9. PATH=/Users/roberta/.rbenv/versions/1.9.3-p551/bin:$PATH ./uwsgi --build-plugin "plugins/rack rack_193"
  10. # set ruby 2.1.5
  11. rbenv local 2.1.5
  12. rbenv which gem
  13. # /Users/roberta/.rbenv/versions/2.1.5/bin/gem
  14. /Users/roberta/.rbenv/versions/2.1.5/bin/gem install sinatra
  15. 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:

  1. [uwsgi]
  2. plugin = rack_193
  3. rack = config.ru
  4. http-socket = :9090

or

  1. [uwsgi]
  2. plugin = rack_215
  3. rack = config.ru
  4. 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

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

Authenticated WebSocket Proxy

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

First create auth_kafka.py:

  1. from pprint import pprint
  2.  
  3. def application(environ, start_response):
  4. start_response('200 OK', [('Content-Type', 'text/plain')])
  5. return ['It Works!']
  6.  
  7. def auth_kafka(request_uri, http_cookie, http_authorization):
  8. pprint(locals())
  9. return 'true'
  10.  
  11. import uwsgi
  12. uwsgi.register_rpc('auth_kafka', auth_kafka)

Then create auth_kafka.ini:

  1. [uwsgi]
  2.  
  3. ; setup
  4. http-socket = 127.0.0.1:8000
  5. master = true
  6. module = auth_kafka
  7.  
  8. ; critical! else worker timeouts apply to proxied websocket connections
  9. offload-threads = 2
  10.  
  11. ; match websocket protocol
  12. kafka-ws-upgrade-regex = ^[Ww]eb[Ss]ocket$
  13.  
  14. ; DRY place for websocket check
  15. is-kafka-ws-request = regexp:${HTTP_UPGRADE};%(kafka-ws-upgrade-regex)
  16.  
  17. ; location of the kafka-ws server
  18. kafka-ws-host = 127.0.0.1:7080
  19.  
  20. ; base endpoint uri for websocket server
  21. kafka-ws-endpoint-uri = /v2/broker/
  22.  
  23. ; call auth_kafka(...); if AUTH_KAFKA gets set, request is good!
  24. route-if = %(is-kafka-ws-request) rpcvar:AUTH_KAFKA auth_kafka ${REQUEST_URI} ${HTTP_COOKIE} ${HTTP_AUTHORIZATION}
  25.  
  26. ; update request uri to websocket endpoint (rewrite only changes PATH_INFO?)
  27. route-if-not = empty:${AUTH_KAFKA} seturi:%(kafka-ws-endpoint-uri)?${QUERY_STRING}
  28.  
  29. ; route the request to our websocket server
  30. route-if-not = empty:${AUTH_KAFKA} httpdumb:%(kafka-ws-host)

Start a “kafka-websocket” server:

  1. 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:

  1. 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-lesswide-open/generic kafka -> websocket gateway and delegates auth needs to the app server. Using offload-threads meansproxied 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:

  1. [uwsgi]
  2. ...
  3. 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:

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