In my last post I demoed a web app written in python for low-power and -bandwidth access to Metlink Real Time Information. The app uses the Flask “microframework” and is hosted on my server with Nginx.
This last component is important. When testing a flask application you generally get a startup message like this:
(rti) [petra@thyme RTI]$ python rti.py * Serving Flask app "rti" (lazy loading) * Environment: production WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Debug mode: off * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
I’m not sure that running this particular app on the dev server behind a reverse proxy would actually cause any issues, but I decided to heed the warning anyway. The simplest method according to the documentation seems to be to use the
flup fastcgi server, which involves connecting nginx to a socket file created via a script along the lines of the following:
#!/usr/bin/python from flup.server.fcgi import WSGIServer from yourapplication import app if __name__ == '__main__': WSGIServer(application, bindAddress='/path/to/fcgi.sock').run()
The docs assume that this script is run (whether with an init file or within something like GNU screen) by the same user as your web server, however for complicated reasons I don’t want to do that. But if you don’t you get a “permission denied” error in your nginx log when it tries to access the socket file, which of course breaks everything.
This isn’t really mentioned anywhere and is very frustrating, but I eventually found reference to the issue in this blog post. The solution they use is to
chmod 777 the socket file in another terminal as root, which was an acceptable stopgap for me at the time.
Doing it better
Still, the sheer awkwardness of this method has taken up an unreasonable amount of my brainspace. I should probably write that init file now that I know the thing works, but how exactly am I supposed to implement that bodge? Because the fastcgi script doesn’t exit until the server does I can’t just add
&& chmod fcgi.sock on the end of the command, and I need root don’t I?
Some investigation today has resolved the matter cleanly though. First: you don’t need root to set file permissions of your own file. I’m not sure why neither I nor the writer of the original post caught that honestly, but there you go.
Second: there probably is a way to get the
WGSIServer(...).run() line to fork off and let you include the
chmod as part of the one-liner, however this is unnecessary because…
Third: it’s possible to set the permissions of the socket file in the
WGSIServer() constructor, it’s just not very well documented. To do it, add a
umask parameter like this:
if __name__ == '__main__': WSGIServer(application, bindAddress='/path/to/fcgi.sock', umask=0).run()
How this works
Buried in the flup source code is documentation of the
If binding to a UNIX socket, umask may be set to specify what the umask is to be changed to before the socket is created in the filesystem. After the socket is created, the previous umask is restored.
umask itself has its own man page but the gist is that that files are created with the inverse mode bits to the value given. We want the (octal) value 777, which would be specified in python as
0o777, and which is inverted with
~0o777. However we need to trim the unneeded prefix bits that will have been flipped to on to get the proper mask, which can be done by
&ing it with
0o777, and of course
0o777 & ~0o777 == 0o000 == 0. (If for some reason you needed arbitrary mode bits you could calculate the mask with
0o777 & ~(mode), but I don’t need that here).
This is a very long way of saying that I wish those extra few characters had been included in the Flask documentation. Even if I managed to figure this out, has everyone else who has had this issue? Probably not: Flask apps can be powerful, but they’re also neat toy projects and they should really be accessible to people who aren’t prepared to dive into the source and into man pages. That’s probably not what they’re using Python for!