I am moving a Flask application from a apache2 and mod_wsgi environment to Nginx, and am having problems getting the urls to work correctly.
I want the root page of my app to appear at, for example, http://example.org/myapp/
My @app.route decorators are e.g. @app.route('/') for the root of my app (http://example.org/myapp) and @app.route('/subpage') for subpages like http://example.org/myapp/subpage.
Under apache this all "just worked" and my calls to url_for() produced URLS that got the job done.
Now my URLs from url_for() are in the form: href="/subpage", which is sending me to the domain root, http://example.org/subpage instead of what I wanted: href="./subpage", which would bring me to http://example.org/myapp/subpage.
For what it's worth, the relevant section from my Nginx config is:
location /myapp/ {
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $server_name;
proxy_pass http://127.0.0.1:8001/;
}
I am serving the application with gunicorn.
With the situation as it stands, visiting http://example.org/myapp/ brings me to the root page of my Flask application, but all other URLs bring me back to the domain level: http://example.org/subpage.
I have tried setting APPLICATION_ROOT to "/myapp", but it seems to have no effect. What am I doing (horribly) wrong?
In your nginx config you should proxy pass with a HOST header that includes the subpath that your app lies at:
location /myapp {
...
proxy_set_header Host $host/myapp;
...
}
Then in Flask you have two options
Option A
Use a new url_for that actually prepends the HOST header to the url. This will take a path like /mypage and turn it into /myapp/mypage
from urllib.parse import urlparse
from flask import request, url_for as _url_for
def url_with_host(path):
return '/'.join((urlparse(request.host_url).path.rstrip('/'), path.lstrip('/')))
def url_for(*args, **kwargs):
if kwargs.get('_external') is True:
return _url_for(*args, **kwargs)
else:
return url_with_host(_url_for(*args, **kwargs))
You can even then update the Jinja url_for using:
app.jinja_env.globals['url_for'] = url_for
Option B
Use the _external flag in url_for. This will render the links with a full path like: http://www.example.com/myapp/mypage
So in your flask app generate url's using
url_for('index', _external=True)
Despite url_for mentioning APPLICATION_ROOT, that's only used if there is no current request context (i.e. not in a web server), so no use at all most of the time.
The actual mechanism is via the wsgi variable SCRIPT_NAME. With gunicorn you can pass it on the command line a couple of ways:
gunicorn --env SCRIPT_NAME=/myapp app:app
SCRIPT_NAME=/myapp gunicorn app:app
In some wsgi systems (e.g. mod_wsgi) it can also be passed as a request header.
For this to work, you need to not strip the path at the reverse proxy. The nginx config should be:
location /myapp {
proxy_pass http://localhost:8000/myapp/;
proxy_set_header Host $host;
proxy_redirect off;
}
or http://unix:/run/gunicorn.sock/myapp/ or wherever the upstream sever is.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With