Since I came to Warwick, I've been working extensively on the LMFDB, which uses python, sage, flask, and mongodb at its core. Thus I've become very familiar with flask. Writing a simple flask application is very quick and easy. So I thought it would be a good idea to figure out how to deploy a flask app on the server which runs this website, which is currently at WebFaction.
In short, it was not too hard, and now the app is set up for use. (It's not a public tool, so I won't link to it).
But there were a few things that I had to think figure out which I would quickly forget. Following the variety of information I found online, the only nontrivial aspect was configuring the site to run on a non-root domain (like
davidlowryduda.com/subdomain instead of at
davidlowryduda.com). I'm writing this so as to not need to figure this out when I write and hoost more flask apps. (Which I'll almost certainly do, as it's so straightforward).
There are some uninteresting things one must do on WebFaction.
- Log into your account.
- Add a new application of type
mod_wsgi(and the desired version of python, which is hopefully 3.6+).
- Add this application to the desired website and subdomain in the WebFaction control panel.
After this, WebFaction will set up a skeleton "Hello World" mod_wsgi application with many reasonable server setting defaults. The remainder of the setup is done on the server itself.
~/webapps/application_name there will now appear
apache2/ # Apache config files and bin htdocs/ # Default location where Apache looks for the app
We won't change that structure. In htdocs1
1I think htdocs is so named because it once stood for HyperText DOCuments. A bit of trivia.
there is a file
index.py, which is where apache expects to find a python wsgi application called
application. We will place the flask app along this structure and point to it in
Usually I will use a virtualenv here. So in
~/webapps/application_name, I will run something like
virtualenv flask_app_venv and
virtualenv activate (or actually out of habit I frequently source the
flask_app_venv/bin/activate file). Then pip install flask and whatever other python modules are necessary for the application to run. We will configure the server to use this virtual environment to run the app in a moment.
Copy the flask app, so that the resulting structure looks something like
~/webapps/application_name: - apache2/ - htdocs/ - flask_app_venv/ - flask_app/ # My flask app - config.py - libs/ - main/ - static/ - templates/ - __init__.py - views.py - models.my
I find it conceptually easiest if I have
flask_app/main/init.py to directly contain the flask
app to reference it by name in
htdocs/index.py. It can be made elsewhere (for instance, perhaps in a file like
flask_app/main/app.py, which appears to be a common structure), but I assume that it is at least imported in
init.py might look something like
# application_name/flask_app/main/__init__.py # ... other import statements from project if necessary from flask import Flask app = Flask(__name__) app.config.from_object('config') # Importing the views for the rest of our site # We do this here to avoid circular imports # Note that I call it "main" where many call it "app" from main import views if __name__ == '__main__': app.run()
The Flask constructor returns exactly the sort of wsgi application that apache expects. With this structure, we can edit the
htdocs/index.py file to look like
# application_name/htdocs/index.py import sys # append flask project files sys.path.append('/home/username/webapps/application_name/my_flask_app/') # launching our app from main import app as application
Now the server knows the correct wsgi_application to serve.
We must configure it to use our python virtual environment (and we'll add a few additional convenience pieces). We edit
/apache2/conf/httpd.conf as follows. Near the top of the file, certain modules are loaded. Add in the alias module, so that the modules look something like
#... other modules LoadModule wsgi_module modules/mod_wsgi.so LoadModule alias_module modules/mod_alias.so # <-- added
This allows us to alias the root of the site. Since all site functionality is routed through
htdocs/index.py, we want to think of the root
/ as beginning with
/htdocs/index.py. At the end of the file
Alias / /home/username/webapps/application_name/htdocs/index.py/
We now set the virtual environment to be used properly. There will be a set of lines containing names like
WSGIProcessGroup. We edit these to refer to the correct python. WebFaction will have configured
WSGIDaemonProcess to point to a local version of python by setting the python-path. Remove that, making that line look like
WSGIDaemonProcess application_name processes=2 threads=12
(or similar). We set the python path below, adding the line
I believe that this could also actually be done by setting puthon-path in WSGIDaemonProcess, but I find this more aesthetically pleasing.
We must also modify the section. Edit it to look like
<Directory /home/username/webapps/application_name/htdocs> AddHandler wsgi-script .py RewriteEngine On # <-- added RewriteBase / # <-- added WSGIScriptReloading On # <-- added <Directory>
It may very well be that I don't use the RewriteEngine at all, but if I do then this is where it's done. Script reloading is a nice convenience, especially while reloading and changing the app.
I note that it may be convenient to add an additional alias for static file hosting,
Alias /static/ /home/your_username/webapps/application_name/app/main/static/
though I have not used this so far. (I get the same functionality through controlling the flask views appropriately).
The rest of this file has been setup by WebFaction for us upon creating the wsgi application.
If the application is on a non-root domain...
If the application is to be run on a non-root domain, such as
davidlowryduda.com/subdomain, then there is currently a problem. In flask, when using url getters like
url_for, urls will be returned as though there is no subdomain. And thus all urls will be incorrect. It is necessary to alter provided urls in some way.
The way that worked for me was to insert a tiny bit of middleware in the wsgi_application. Alter
htdocs/index.py to read
#application_name/htdocs/index.py import sys # append flask project files sys.path.append('/home/username/webapps/application_name/my_flask_app/') # subdomain url rerouting middleware from webfaction_middleware import Middleware from main import app # set app through middleware application = Middleware(app)
Now of course we need to write this middleware.
application_name/flask_app, I create a file called
webfaction_middleware.py, which reads
# application_name/flask_app/webfaction_middleware.py class Middleware(object): # python2 aware def __init__(self, app): self.app = app def __call__(self, environ, start_response): app_url = '/subdomain' if app_url != '/': environ['SCRIPT_NAME'] = app_url return self.app(environ, start_response)
I now have a template file in which I keep
app_url = '/' so that I can forget this and not worry, but that is where the subdomain url is prepended. Note that the leading slash is necessary. When I first tried using this, I omitted the leading slash. The application worked sometimes, and horribly failed in some other places. Some urls were correcty constructed, but most were not. I didn't try to figure out which ones were doomed to fail — but it took me an embarassingly long time to realize that prepending a slash solved all problems.
The magical-names of
start_response are because the flask app is a wsgi_application, and this is the api of wsgi_applications generically.
Now it's ready
Restart the apache server (
/apache2/bin/restart) and go. Note that when incrementally making changes above, some changes can take a few minutes to fully propogate. It's only doing it the first time which takes some thought.
Leave a comment
Info on how to comment
To make a comment, please send an email using the button below. Your email address won't be shared (unless you include it in the body of your comment). If you don't want your real name to be used next to your comment, please specify the name you would like to use. If you want your name to link to a particular url, include that as well.
bold, italics, and plain text are allowed in
comments. A reasonable subset of markdown is supported, including lists,
links, and fenced code blocks. In addition, math can be formatted using
$(inline math)$ or
$$(your display equation)$$.
Please use plaintext email when commenting. See Plaintext Email and Comments on this site for more. Note also that comments are expected to be open, considerate, and respectful.
Comment via email
2019-05-02 Eli B
Thank god, finally one that works.