Add a prefix to all Flask routes
Asked Answered
H

16

144

I have a prefix that I want to add to every route. Right now I add a constant to the route at every definition. Is there a way to do this automatically?

PREFIX = "/abc/123"

@app.route(PREFIX + "/")
def index_page():
  return "This is a website about burritos"

@app.route(PREFIX + "/about")
def about_page():
  return "This is a website about burritos"
Herrick answered 23/9, 2013 at 19:39 Comment(0)
S
98

The answer depends on how you are serving this application.

Sub-mounted inside of another WSGI container

Assuming that you are going to run this application inside of a WSGI container (mod_wsgi, uwsgi, gunicorn, etc); you need to actually mount, at that prefix the application as a sub-part of that WSGI container (anything that speaks WSGI will do) and to set your APPLICATION_ROOT config value to your prefix:

app.config["APPLICATION_ROOT"] = "/abc/123"

@app.route("/")
def index():
    return "The URL for this page is {}".format(url_for("index"))

# Will return "The URL for this page is /abc/123/"

Setting the APPLICATION_ROOT config value simply limit Flask's session cookie to that URL prefix. Everything else will be automatically handled for you by Flask and Werkzeug's excellent WSGI handling capabilities.

An example of properly sub-mounting your app

If you are not sure what the first paragraph means, take a look at this example application with Flask mounted inside of it:

from flask import Flask, url_for
from werkzeug.serving import run_simple
from werkzeug.middleware.dispatcher import DispatcherMiddleware
 
app = Flask(__name__)
app.config['APPLICATION_ROOT'] = '/abc/123'
 
@app.route('/')
def index():
    return 'The URL for this page is {}'.format(url_for('index'))

def simple(env, resp):
    resp(b'200 OK', [(b'Content-Type', b'text/plain')])
    return [b'Hello WSGI World']

app.wsgi_app = DispatcherMiddleware(simple, {'/abc/123': app.wsgi_app})

if __name__ == '__main__':
    app.run('localhost', 5000)

Proxying requests to the app

If, on the other hand, you will be running your Flask application at the root of its WSGI container and proxying requests to it (for example, if it's being FastCGI'd to, or if nginx is proxy_pass-ing requests for a sub-endpoint to your stand-alone uwsgi / gevent server then you can either:

  • Use a Blueprint, as Miguel points out in his answer.
  • or use the DispatcherMiddleware from werkzeug (or the PrefixMiddleware from su27's answer) to sub-mount your application in the stand-alone WSGI server you're using. (See An example of properly sub-mounting your app above for the code to use).
Strephon answered 23/9, 2013 at 19:57 Comment(11)
@jknupp - looking at flask.Flask#create_url_adapter and werkzeug.routing.Map#bind_to_environ it looks like it should work - how were you running the code? (The app actually needs to be mounted on the sub-path in a WSGI environment for url_for to return the expected value.)Strephon
I ran exactly what you wrote, but added app = Flask(name) and app.run(debug=True)Guppy
@jknupp - that is the problem - you'll need to actually mount the application as a sub-part of a larger application (anything that speaks WSGI will do). I've whipped up an example gist and updated my answer to make it clearer that I'm assuming a sub-mounted WSGI environment, not a stand-alone WSGI environment behind a proxy which is only forwarding sub-path requests.Strephon
This works, using the DispatcherMiddleware approach, when running flask by itself. Can't quite seem to get this working when running behind Gunicorn.Galactic
@Galactic - are you running parent_app or just app via your Gunicorn configuration? (You need to target parent_app as your WSGI application).Strephon
That makes sense, thanks @SeanVieira. I have a subclass of Flask (for some other customizations) so I'll probably need to override wsgi_app, or I could just override add_url_rule, maybe this is easier?Galactic
I believe there is a typo in the sub-mounting example. Replacing "simple" with "run_simple" in the app.wsgi_app = DispatcherMiddleware() call works for me.Volotta
The way mount to sub path in uwsgi uwsgi -s /tmp/yourapplication.sock --manage-script-name --mount /yourapplication=myapp:app . detail refer to (uwsgi document)[flask.pocoo.org/docs/1.0/deploying/uwsgi/]Sorcha
When I add APPLICATION_ROOT I get ... File "/home/ubuntu/anaconda3/envs/vocprez/lib/python3.8/site-packages/gunicorn/workers/sync.py", line 165, in handle_request resp, environ = wsgi.create(req, client, addr, File "/home/ubuntu/anaconda3/envs/vocprez/lib/python3.8/site-packages/gunicorn/http/wsgi.py", line 183, in create path_info = path_info.split(script_name, 1)[1] IndexError: list index out of range Am I doing something wrong?Moonfaced
SOLVED: make sure your proxy_pass directive contains the subdirectory. See: dlukes.github.io/flask-wsgi-url-prefix.html for more detailsMoonfaced
@ChrisCummings 's comment might have been missed. I needed to change this bit of code: DispatcherMiddleware(simple, {'/abc/123': app.wsgi_app}) to this DispatcherMiddleware(run_simple, {'/abc/123': app.wsgi_app}) to fix the typo. Thank you @sean-viera for posting this solution. 🙏Dinothere
P
147

You can put your routes in a blueprint:

bp = Blueprint('burritos', __name__,
                        template_folder='templates')

@bp.route("/")
def index_page():
  return "This is a website about burritos"

@bp.route("/about")
def about_page():
  return "This is a website about burritos"

Then you register the blueprint with the application using a prefix:

app = Flask(__name__)
app.register_blueprint(bp, url_prefix='/abc/123')
Parasympathetic answered 23/9, 2013 at 21:24 Comment(12)
Hi Miguel; do you know the difference between registering a url_prefix for a blueprint as you did below with app.register_blueprint and between registering it when you instantiate the Blueprint object above, by passing url_prefix='/abc/123? Thank you!Corlisscorly
The difference is that having the URL prefix in the register_blueprint call gives the application the freedom to "mount" the blueprint anywhere it wants, or even mount the same blueprint multiple times on different URLs. If you put the prefix in the blueprint itself you make it easier for the application, but you have less flexibility.Parasympathetic
Thank you!! That is very helpful. I was confused by the apparent redundancy but I see the trade-off between the two options.Corlisscorly
And actually, I never tried this, but it is likely that you can combine URL prefixes both in the blueprint and the app, with the app's prefix fist, followed by the blueprint prefix.Parasympathetic
Note that it is necessary to register the blueprint after the blueprint.route decorated functions.Votyak
@Miguel What I have to do if I further wants to prefix an url which was already registered as Blueprint? Ie. I just want to add /api after 123 and I don't want to attach api after 123 in the url_prefixFraze
I'm a little confused on where each "part" of this code should go? I have an "app" folder with an init.py that contians my app.run code. I have an init.py file one directory above that that sets my logger and the has the Flask(name) line. I then have a separate routes.py file with all my routes. Where do I put each line of this sample code to try this method out?Sign
The location doesn't really matter, Flask does not care. You can write the blueprint in a separate module inside your app package if you want to keep things organized.Parasympathetic
This does not work for static files though, how to go about it?Monaural
You can define your own static route inside the blueprint. Use the send_from_directory() function to implement the route.Parasympathetic
Your redirects in view functions (or classes) will also require adjusting such that the blueprint name is included in it. For example, instead of return redirect(url_for('index_page')) you will need to have this: return redirect(url_for('burritos.index_page'))Baseler
Don't forget to additionally set the app.config['APPLICATION_ROOT'] = '/abc/123' Otherwise your cookies won't work and thus no login, logout, etcCulver
S
98

The answer depends on how you are serving this application.

Sub-mounted inside of another WSGI container

Assuming that you are going to run this application inside of a WSGI container (mod_wsgi, uwsgi, gunicorn, etc); you need to actually mount, at that prefix the application as a sub-part of that WSGI container (anything that speaks WSGI will do) and to set your APPLICATION_ROOT config value to your prefix:

app.config["APPLICATION_ROOT"] = "/abc/123"

@app.route("/")
def index():
    return "The URL for this page is {}".format(url_for("index"))

# Will return "The URL for this page is /abc/123/"

Setting the APPLICATION_ROOT config value simply limit Flask's session cookie to that URL prefix. Everything else will be automatically handled for you by Flask and Werkzeug's excellent WSGI handling capabilities.

An example of properly sub-mounting your app

If you are not sure what the first paragraph means, take a look at this example application with Flask mounted inside of it:

from flask import Flask, url_for
from werkzeug.serving import run_simple
from werkzeug.middleware.dispatcher import DispatcherMiddleware
 
app = Flask(__name__)
app.config['APPLICATION_ROOT'] = '/abc/123'
 
@app.route('/')
def index():
    return 'The URL for this page is {}'.format(url_for('index'))

def simple(env, resp):
    resp(b'200 OK', [(b'Content-Type', b'text/plain')])
    return [b'Hello WSGI World']

app.wsgi_app = DispatcherMiddleware(simple, {'/abc/123': app.wsgi_app})

if __name__ == '__main__':
    app.run('localhost', 5000)

Proxying requests to the app

If, on the other hand, you will be running your Flask application at the root of its WSGI container and proxying requests to it (for example, if it's being FastCGI'd to, or if nginx is proxy_pass-ing requests for a sub-endpoint to your stand-alone uwsgi / gevent server then you can either:

  • Use a Blueprint, as Miguel points out in his answer.
  • or use the DispatcherMiddleware from werkzeug (or the PrefixMiddleware from su27's answer) to sub-mount your application in the stand-alone WSGI server you're using. (See An example of properly sub-mounting your app above for the code to use).
Strephon answered 23/9, 2013 at 19:57 Comment(11)
@jknupp - looking at flask.Flask#create_url_adapter and werkzeug.routing.Map#bind_to_environ it looks like it should work - how were you running the code? (The app actually needs to be mounted on the sub-path in a WSGI environment for url_for to return the expected value.)Strephon
I ran exactly what you wrote, but added app = Flask(name) and app.run(debug=True)Guppy
@jknupp - that is the problem - you'll need to actually mount the application as a sub-part of a larger application (anything that speaks WSGI will do). I've whipped up an example gist and updated my answer to make it clearer that I'm assuming a sub-mounted WSGI environment, not a stand-alone WSGI environment behind a proxy which is only forwarding sub-path requests.Strephon
This works, using the DispatcherMiddleware approach, when running flask by itself. Can't quite seem to get this working when running behind Gunicorn.Galactic
@Galactic - are you running parent_app or just app via your Gunicorn configuration? (You need to target parent_app as your WSGI application).Strephon
That makes sense, thanks @SeanVieira. I have a subclass of Flask (for some other customizations) so I'll probably need to override wsgi_app, or I could just override add_url_rule, maybe this is easier?Galactic
I believe there is a typo in the sub-mounting example. Replacing "simple" with "run_simple" in the app.wsgi_app = DispatcherMiddleware() call works for me.Volotta
The way mount to sub path in uwsgi uwsgi -s /tmp/yourapplication.sock --manage-script-name --mount /yourapplication=myapp:app . detail refer to (uwsgi document)[flask.pocoo.org/docs/1.0/deploying/uwsgi/]Sorcha
When I add APPLICATION_ROOT I get ... File "/home/ubuntu/anaconda3/envs/vocprez/lib/python3.8/site-packages/gunicorn/workers/sync.py", line 165, in handle_request resp, environ = wsgi.create(req, client, addr, File "/home/ubuntu/anaconda3/envs/vocprez/lib/python3.8/site-packages/gunicorn/http/wsgi.py", line 183, in create path_info = path_info.split(script_name, 1)[1] IndexError: list index out of range Am I doing something wrong?Moonfaced
SOLVED: make sure your proxy_pass directive contains the subdirectory. See: dlukes.github.io/flask-wsgi-url-prefix.html for more detailsMoonfaced
@ChrisCummings 's comment might have been missed. I needed to change this bit of code: DispatcherMiddleware(simple, {'/abc/123': app.wsgi_app}) to this DispatcherMiddleware(run_simple, {'/abc/123': app.wsgi_app}) to fix the typo. Thank you @sean-viera for posting this solution. 🙏Dinothere
M
76

You should note that the APPLICATION_ROOT is NOT for this purpose.

All you have to do is to write a middleware to make the following changes:

  1. modify PATH_INFO to handle the prefixed url.
  2. modify SCRIPT_NAME to generate the prefixed url.

Like this:

class PrefixMiddleware(object):

    def __init__(self, app, prefix=''):
        self.app = app
        self.prefix = prefix

    def __call__(self, environ, start_response):

        if environ['PATH_INFO'].startswith(self.prefix):
            environ['PATH_INFO'] = environ['PATH_INFO'][len(self.prefix):]
            environ['SCRIPT_NAME'] = self.prefix
            return self.app(environ, start_response)
        else:
            start_response('404', [('Content-Type', 'text/plain')])
            return ["This url does not belong to the app.".encode()]

Wrap your app with the middleware, like this:

from flask import Flask, url_for

app = Flask(__name__)
app.debug = True
app.wsgi_app = PrefixMiddleware(app.wsgi_app, prefix='/foo')


@app.route('/bar')
def bar():
    return "The URL for this page is {}".format(url_for('bar'))


if __name__ == '__main__':
    app.run('0.0.0.0', 9010)

Visit http://localhost:9010/foo/bar,

You will get the right result: The URL for this page is /foo/bar

And don't forget to set the cookie domain if you need to.

This solution is given by Larivact's gist. The APPLICATION_ROOT is not for this job, although it looks like to be. It's really confusing.

Malenamalet answered 16/3, 2016 at 10:57 Comment(8)
Thanks for adding this answer. Tried the other solutions posted here, but this is the only one that worked for me. A+++I'm deployed on IIS using wfastcgi.pyKillick
"The APPLICATION_ROOT is not for this job" - this is where I was going wrong. I wish Blueprint's url_prefix parameter and APPLICATION_ROOT were combined by default, so that I could have APPLICATION_ROOT scope urls for the entire app, and url_prefix scope urls within APPLICATION_ROOT just for the individual blueprint. SighSermon
See this gist for an example of what I was trying to do using APPLICATION_ROOT.Sermon
If you're using gunicorn, SCRIPT_NAME is already supported. Set it as an environment variable or pass it through as an http header: docs.gunicorn.org/en/stable/faq.htmlGithens
Can you confirm your code? TypeError: module() takes at most 2 arguments (3 given) Looks like the class takes an object?Luteolin
The code as it stands did not work for me. After some research, I came up with this after the else in the __call__ method: response = Response('That url is not correct for this application', status=404) return response(environ, start_response) using from werkzeug.wrappers import BaseResponse as ResponseClownery
alll you have to ... is try the proposed solution to find out it does not work ...Philbrook
This solution works "from the outside", when the user requests a page from the server. However, if I call flask.redirect('/'), it will go to the real / without applying the prefix.Restrain
C
13

This is more of a python answer than a Flask/werkzeug answer; but it's simple and works.

If, like me, you want your application settings (loaded from an .ini file) to also contain the prefix of your Flask application (thus, not to have the value set during deployment, but during runtime), you can opt for the following:

def prefix_route(route_function, prefix='', mask='{0}{1}'):
  '''
    Defines a new route function with a prefix.
    The mask argument is a `format string` formatted with, in that order:
      prefix, route
  '''
  def newroute(route, *args, **kwargs):
    '''New function to prefix the route'''
    return route_function(mask.format(prefix, route), *args, **kwargs)
  return newroute

Arguably, this is somewhat hackish and relies on the fact that the Flask route function requires a route as a first positional argument.

You can use it like this:

app = Flask(__name__)
app.route = prefix_route(app.route, '/your_prefix')

NB: It is worth nothing that it is possible to use a variable in the prefix (for example by setting it to /<prefix>), and then process this prefix in the functions you decorate with your @app.route(...). If you do so, you obviously have to declare the prefix parameter in your decorated function(s). In addition, you might want to check the submitted prefix against some rules, and return a 404 if the check fails. In order to avoid a 404 custom re-implementation, please from werkzeug.exceptions import NotFound and then raise NotFound() if the check fails.

Citarella answered 17/6, 2016 at 9:51 Comment(1)
It's simple and more efficient than using Blueprint. Thanks for sharing!Shithead
S
5

So, I believe that a valid answer to this is: the prefix should be configured in the actual server application that you use when development is completed. Apache, nginx, etc.

However, if you would like this to work during development while running the Flask app in debug, take a look at this gist.

Flask's DispatcherMiddleware to the rescue!

I'll copy the code here for posterity:

"Serve a Flask app on a sub-url during localhost development."

from flask import Flask


APPLICATION_ROOT = '/spam'


app = Flask(__name__)
app.config.from_object(__name__)  # I think this adds APPLICATION_ROOT
                                  # to the config - I'm not exactly sure how!
# alternatively:
# app.config['APPLICATION_ROOT'] = APPLICATION_ROOT


@app.route('/')
def index():
    return 'Hello, world!'


if __name__ == '__main__':
    # Relevant documents:
    # http://werkzeug.pocoo.org/docs/middlewares/
    # http://flask.pocoo.org/docs/patterns/appdispatch/
    from werkzeug.serving import run_simple
    from werkzeug.wsgi import DispatcherMiddleware
    app.config['DEBUG'] = True
    # Load a dummy app at the root URL to give 404 errors.
    # Serve app at APPLICATION_ROOT for localhost development.
    application = DispatcherMiddleware(Flask('dummy_app'), {
        app.config['APPLICATION_ROOT']: app,
    })
    run_simple('localhost', 5000, application, use_reloader=True)

Now, when running the above code as a standalone Flask app, http://localhost:5000/spam/ will display Hello, world!.

In a comment on another answer, I expressed that I wished to do something like this:

from flask import Flask, Blueprint

# Let's pretend module_blueprint defines a route, '/record/<id>/'
from some_submodule.flask import module_blueprint

app = Flask(__name__)
app.config['APPLICATION_ROOT'] = '/api'
app.register_blueprint(module_blueprint, url_prefix='/some_submodule')
app.run()

# I now would like to be able to get to my route via this url:
# http://host:8080/api/some_submodule/record/1/

Applying DispatcherMiddleware to my contrived example:

from flask import Flask, Blueprint
from flask.serving import run_simple
from flask.wsgi import DispatcherMiddleware

# Let's pretend module_blueprint defines a route, '/record/<id>/'
from some_submodule.flask import module_blueprint

app = Flask(__name__)
app.config['APPLICATION_ROOT'] = '/api'
app.register_blueprint(module_blueprint, url_prefix='/some_submodule')
application = DispatcherMiddleware(Flask('dummy_app'), {
    app.config['APPLICATION_ROOT']: app
})
run_simple('localhost', 5000, application, use_reloader=True)

# Now, this url works!
# http://host:8080/api/some_submodule/record/1/
Sermon answered 18/5, 2016 at 19:52 Comment(2)
"So, I believe that a valid answer to this is: the prefix should be configured in the actual server application that you use when development is completed. Apache, nginx, etc." The problem is in redirects; if you have a prefix and don't set it up in Flask, then when it redirects instead of going to /yourprefix/path/to/url it just goes to /path/to/url. Is there a way to set up, in nginx or Apache, what the prefix has to be?Schalles
The way I would probably do this is just to use a configuration management tool like puppet or chef, and set the prefix there and then have the tool propagate the change to the config files where it needs to go. I'm not going to even pretend that I know what I'm talking about for apache or nginx. Since this question/answer was specific to python, I would encourage you to post your scenario as a separate question. If you do this, feel free to link to the question here!Sermon
T
3

Another completely different way is with mountpoints in uwsgi.

From the doc about Hosting multiple apps in the same process (permalink).

In your uwsgi.ini you add

[uwsgi]
mount = /foo=main.py
manage-script-name = true

# also stuff which is not relevant for this, but included for completeness sake:    
module = main
callable = app
socket = /tmp/uwsgi.sock

If you don't call your file main.py, you need to change both the mount and the module

Your main.py could look like this:

from flask import Flask, url_for
app = Flask(__name__)
@app.route('/bar')
def bar():
  return "The URL for this page is {}".format(url_for('bar'))
# end def

And a nginx config (again for completeness):

server {
  listen 80;
  server_name example.com

  location /foo {
    include uwsgi_params;
    uwsgi_pass unix:///temp/uwsgi.sock;
  }
}

Now calling example.com/foo/bar will display /foo/bar as returned by flask's url_for('bar'), as it adapts automatically. That way your links will work without prefix problems.

Tryptophan answered 7/11, 2017 at 10:13 Comment(0)
D
3
from flask import Flask

app = Flask(__name__)

app.register_blueprint(bp, url_prefix='/abc/123')

if __name__ == "__main__":
    app.run(debug='True', port=4444)


bp = Blueprint('burritos', __name__,
                        template_folder='templates')

@bp.route('/')
def test():
    return "success"
Disappearance answered 10/1, 2019 at 11:30 Comment(2)
Please consider adding an explanation.Mitchellmitchem
Two nice explanations I found were in exploreflask and the official docsVenegas
G
1

I needed similar so called "context-root". I did it in conf file under /etc/httpd/conf.d/ using WSGIScriptAlias :

myapp.conf:

<VirtualHost *:80>
    WSGIScriptAlias /myapp /home/<myid>/myapp/wsgi.py

    <Directory /home/<myid>/myapp>
        Order deny,allow
        Allow from all
    </Directory>

</VirtualHost>

So now I can access my app as : http://localhost:5000/myapp

See the guide - http://modwsgi.readthedocs.io/en/develop/user-guides/quick-configuration-guide.html

Graceless answered 11/8, 2017 at 18:14 Comment(0)
Q
1

My solution where flask and PHP apps coexist nginx and PHP5.6

KEEP Flask in root and PHP in subdirectories

sudo vi /etc/php/5.6/fpm/php.ini

Add 1 line

cgi.fix_pathinfo=0
sudo vi /etc/php/5.6/fpm/pool.d/www.conf
listen = /run/php/php5.6-fpm.sock

uwsgi

sudo vi /etc/nginx/sites-available/default

USE NESTED LOCATIONS for PHP and let FLASK remain in root

server {
    listen 80 default_server;
    listen [::]:80 default_server;

    # SSL configuration
    #
    # listen 443 ssl default_server;
    # listen [::]:443 ssl default_server;
    #
    # Note: You should disable gzip for SSL traffic.
    # See: https://bugs.debian.org/773332
    #
    # Read up on ssl_ciphers to ensure a secure configuration.
    # See: https://bugs.debian.org/765782
    #
    # Self signed certs generated by the ssl-cert package
    # Don't use them in a production server!
    #
    # include snippets/snakeoil.conf;

    root /var/www/html;

    # Add index.php to the list if you are using PHP
    index index.html index.htm index.php index.nginx-debian.html;

    server_name _;

    # Serve a static file (ex. favico) outside static dir.
    location = /favico.ico  {    
        root /var/www/html/favico.ico;    
    }

    # Proxying connections to application servers
    location / {
        include            uwsgi_params;
        uwsgi_pass         127.0.0.1:5000;
    }

    location /pcdp {
        location ~* \.php$ {
            try_files $uri =404;
            fastcgi_split_path_info ^(.+\.php)(/.+)$;
            fastcgi_pass unix:/var/run/php/php5.6-fpm.sock;
            fastcgi_index index.php;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            include fastcgi_params;
        }
    }

    location /phpmyadmin {
        location ~* \.php$ {
            try_files $uri =404;
            fastcgi_split_path_info ^(.+\.php)(/.+)$;
            fastcgi_pass unix:/var/run/php/php5.6-fpm.sock;
            fastcgi_index index.php;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            include fastcgi_params;
        }
    }

    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    #location ~ \.php$ {
    #   include snippets/fastcgi-php.conf;
    #
    #   # With php7.0-cgi alone:
    #   fastcgi_pass 127.0.0.1:9000;
    #   # With php7.0-fpm:
    #   fastcgi_pass unix:/run/php/php7.0-fpm.sock;
    #}

    # deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    #
    #location ~ /\.ht {
    #   deny all;
    #}
}

READ carefully https://www.digitalocean.com/community/tutorials/understanding-nginx-server-and-location-block-selection-algorithms

We need to understand location matching (none): If no modifiers are present, the location is interpreted as a prefix match. This means that the location given will be matched against the beginning of the request URI to determine a match. =: If an equal sign is used, this block will be considered a match if the request URI exactly matches the location given. ~: If a tilde modifier is present, this location will be interpreted as a case-sensitive regular expression match. ~*: If a tilde and asterisk modifier is used, the location block will be interpreted as a case-insensitive regular expression match. ^~: If a carat and tilde modifier is present, and if this block is selected as the best non-regular expression match, regular expression matching will not take place.

Order is important, from nginx's "location" description:

To find location matching a given request, nginx first checks locations defined using the prefix strings (prefix locations). Among them, the location with the longest matching prefix is selected and remembered. Then regular expressions are checked, in the order of their appearance in the configuration file. The search of regular expressions terminates on the first match, and the corresponding configuration is used. If no match with a regular expression is found then the configuration of the prefix location remembered earlier is used.

It means:

First =. ("longest matching prefix" match)
Then implicit ones. ("longest matching prefix" match)
Then regex. (first match)
Quamash answered 8/11, 2018 at 6:31 Comment(0)
R
1

For people still struggling with this, the first example does work, but the full example is here if you have a Flask app that is not under your control:

from os import getenv
from werkzeug.middleware.dispatcher import DispatcherMiddleware
from werkzeug.serving import run_simple
from custom_app import app

application = DispatcherMiddleware(
    app, {getenv("REBROW_BASEURL", "/rebrow"): app}
)

if __name__ == "__main__":
    run_simple(
        "0.0.0.0",
        int(getenv("REBROW_PORT", "5001")),
        application,
        use_debugger=False,
        threaded=True,
    )
Reword answered 19/3, 2020 at 5:56 Comment(0)
P
1

In flask blueprint, we can use -

app = Flask(__name__)

app.config['APPLICATION_ROOT'] = '/prefix-text'

Anyone looking to do in flask-restful can make use of -

doc link

app = Flask(__name__)

api = Api(app, prefix='/pefix-text')

Now, all your routes will be prefixed with /prefix-text. Just make sure you use url_for('link') in places where you might have simply used a /link.

Paroxysm answered 1/7, 2021 at 18:6 Comment(0)
R
1

From all the answers I have seen above, they are either too simplistic or over complicating.

That said, I like to accomplish it using nested blueprints:

from .blueprints import blueprint1, blueprint2, blueprint3, etc


app = Flask(__name__)

url_prefix = "/abc/123"
parent = Blueprint('index', __name__, url_prefix=url_prefix)

index.register_blueprint(blueprint1)
index.register_blueprint(blueprint2)
index.register_blueprint(blueprint3)
app.register_blueprint(index)

This way, you basically link your child blueprints to a parent blueprint, where you define the prefix. This is documented here.

With your example, you would simply rewrite it to:

blueprint1 = Blueprint('blueprint1', __name__)

@blueprint1.route("/")
def index_page():
  return "Index page"

@blueprint1.route("/about")
def about_page():
  return "About page"
Refugiorefulgence answered 18/2, 2022 at 14:47 Comment(0)
T
1

If you want to handle the prefix when using Nginx as a reverse proxy. Werkzeug's ProxyFix middleware will be a simpler solution:

from werkzeug.middleware.proxy_fix import ProxyFix
from flask import Flask


app = Flask(__name__)
app.wsgi_app = ProxyFix(
    app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1
)

Werkzeug will read the X-Forwarded-Prefix header and set it to SCRIPT_NAME. So be sure to set the X-Forwarded-Prefix header in Nginx config:

server {
    listen 80;
    server_name _;

    location /api {
        proxy_pass http://127.0.0.1:5000/;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Prefix /api;
    }
}

See more details at https://mcmap.net/q/161165/-how-to-host-a-flask-app-on-a-subfolder-url-prefix-with-nginx

Tuchun answered 16/1, 2023 at 14:55 Comment(0)
C
0

I think su27's answer is right. And I am using gevent, here is my code and it works fine:

from gevent import pywsgi

# your flask code ...
# app = Flask(__name__)

if __name__ == "__main__":
    class MyHandler(pywsgi.WSGIHandler):
        def get_environ(self):
            prefix = "/your_prefix"
            env = super().get_environ()
            if env['PATH_INFO'].startswith(prefix):
                env['PATH_INFO'] = env['PATH_INFO'][len(prefix):]
                env['SCRIPT_NAME'] = prefix
            return env
    
    server = pywsgi.WSGIServer(('', 8080), app, handler_class=MyHandler)
    server.serve_forever()
Converter answered 7/4, 2021 at 8:38 Comment(0)
C
0

If your purpose is to add the prefix in some way,

take a look at the answer https://mcmap.net/q/161166/-url-prefix-for-superset and https://github.com/mskimm/prefixed-superset

Clardy answered 28/9, 2022 at 14:27 Comment(0)
P
0

I am using ApiFlask application via both embedded dev server (dev env) and gunicorn (staging and production env). I believe the solution will work for vanilla Flask as well.

I wanted to have the configuration at one place - package __init__.py file, that imports route definitions placed within views.py file, not to have it scampered across more files or some part before route definition and the rest after them. Solution via Blueprint makes me annotate routes specifically, which I understand but feel that it is a bit overkill for my case.

The solution is based on DispatcherMiddleware and everything necessary is placed to the package root file (__init__.py). I am taking the prefix from an environment variable, but that doesn't matter.

Note that automatically generated OAS documentation is also accessible under a prefixed route.

Minimal example follows:

from apiflask import APIFlask
from werkzeug.middleware.dispatcher import DispatcherMiddleware
import os

route_prefix = os.getenv('APP_ROUTE_PREFIX', '')

def create_app():
    app = APIFlask(
            __name__,
            title="OAS3 Application title",
            version="0.2",
            docs_path='/docs',
            spec_path='/openapi.json'
    )
    app.config['APPLICATION_ROOT'] = route_prefix

    return app

app = create_app()
app.wsgi_app = DispatcherMiddleware(app, {route_prefix: app.wsgi_app})

from app.views import *
Pulpy answered 5/4, 2023 at 14:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.