Running a Flask application at a URL that is not the domain root
Asked Answered
P

2

11

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?

Putrescent answered 21/9, 2014 at 17:58 Comment(0)
S
4

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.

Shackleton answered 11/5, 2021 at 16:23 Comment(0)
H
3

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)
Hyla answered 31/1, 2018 at 8:30 Comment(2)
These are both really nasty workarounds that ignore the actual standard way to do itShackleton
A Host header that contains a path is invalid, for example.Shackleton

© 2022 - 2024 — McMap. All rights reserved.