问题
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"
回答1:
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.wsgi 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
fromwerkzeug
(or thePrefixMiddleware
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).
回答2:
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')
回答3:
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:
- modify
PATH_INFO
to handle the prefixed url. - 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.
回答4:
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.
回答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/
回答6:
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.
回答7:
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
回答8:
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)
回答9:
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"
来源:https://stackoverflow.com/questions/18967441/add-a-prefix-to-all-flask-routes