This week I started using uWSGI for the first time. I'm in the process of switching Balrog from Vagrant to Docker, and I'm moving away from Apache in the process. Because of Balrog's somewhat complicated Apache config this ended up being more difficult than I thought. Although uWSGI's docs are OK, I found it a little difficult to put them into practice without examples, so here's hoping this post will help others in similar situations.
Balrog's Admin app consists of a pretty standard Python WSGI app, and a static Angular app hosted on the same domain. To complicate matters, the version of Angular that we use does not support being hosted anywhere except the root of the domain. It took a bit of futzing, but we came up with an Apache config to host both of these pieces on the same domain pretty quickly:
<VirtualHost *:80> ServerName balrog-admin.mozilla.dev DocumentRoot /home/vagrant/project/ui/dist/ # Rewrite virtual paths in the angular app to the index page # so that refreshes/linking works, while leaving real files # such as the js/css alone. <Directory /home/vagrant/project/ui/dist> RewriteEngine On RewriteCond %{REQUEST_FILENAME} -f [OR] RewriteCond %{REQUEST_FILENAME} -d RewriteRule ^ - [L] RewriteRule ^ index.html [L] </Directory> # The WSGI app is rooted at /api WSGIScriptAlias /api /home/vagrant/project/admin.wsgi WSGIDaemonProcess aus4-admin processes=1 threads=1 maximum-requests=50 display-name=aus4-admin WSGIProcessGroup aus4-admin WSGIPassAuthorization On # The WSGI app relies on the web server to do the authentication, and will # bail if REMOTE_USER isn't set. To simplify things, we just set this # variable instead of prompting for auth. SetEnv REMOTE_USER balrogadmin LogLevel Debug ErrorLog "|/usr/sbin/rotatelogs /var/log/httpd/balrog-admin.mozilla.dev/error_log_%Y-%m-%d 86400 -0" CustomLog "|/usr/sbin/rotatelogs /var/log/httpd/balrog-admin.mozilla.dev/access_%Y-%m-%d 86400 -0" combined </VirtualHost>
Translating this to uWSGI took way longer than expected. Among the problems I ran into were:
After much struggle, I came up with an invocation that worked exactly the same as the Apache config:
uwsgi --http :8080 --mount /api=admin.wsgi --manage-script-name --check-static /app/ui/dist --static-index index.html --route "^/.*$ addvar:REMOTE_USER=balrogadmin" --route-if "startswith:\${REQUEST_URI};/api continue:" --route-if-not "exists:/app/ui/dist\${PATH_INFO} static:/app/ui/dist/index.html"
There's a lot crammed in there, so let's break it down:
--http :8080
tells uWSGI to listen on port 8080--mount /api=admin.wsgi
roots the "admin.wsgi" app in /api. This means that when you make a request to http://localhost:8080/api/foo, the application sees "/foo" as the path. If there was no Angular app, I would simply use "--wsgi-file admin.wsgi" to place the app at the root of the server.--manage-script-name
causes uWSGI to rewrite PATH_INFO and SCRIPT_NAME according to the mount point. This isn't necessary if you're not using "--mount".--check-static /app/ui/dist
points uWSGI at a directory of static files that it should serve. In my case, I've pointed it at the fully built Angular app. With this, requests such as http://localhost:8080/js/app.js returns the static file from /app/ui/dist/js/app.js.--static-index index.html
tells uWSGI to serve index.html when a request for a directory is made - the default is to 404, because there's no built-in directory indexing.--route
's chain together, and are evaluated as follows:^/.*$
(all paths will), set the REMOTE_USER variable to balrogadmin.In the end, uWSGI seems to be one of the things that's very scary when you first approach it (I count about 750 command line arguments), but is pretty easy to understand when you get to know it a better. This is almost the opposite of Apache - I find it much more approachable, perhaps because there's such a littany of examples out there, but things like mod_rewrite are very difficult for me to understand after the fact, at least compared to uWSGI's --route's.