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