为所有 Flask 路由添加前缀

我有一个前缀,我想添加到每个路由。现在,我在每个定义的路由中添加一个常量。有没有办法自动完成这项工作?

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"
132740 次浏览

答案取决于您如何为此应用程序提供服务。

子安装在另一个 WSGI 容器内

假设您要在 WSGI 容器(mod _ WSGI、 uwsgi、 gunicorn 等)中运行这个应用程序; 您需要实际将该应用程序作为 WSGI 容器的子部分 挂载,在这个前缀(任何说 WSGI 的都可以) ,并将 APPLICATION_ROOT配置值设置为前缀:

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/"

设置 APPLICATION_ROOT配置值只是将 Flask 的会话 cookie 限制为该 URL 前缀。其他一切都将自动为您处理 Flask 和 Werkzeug 的出色的 WSGI 处理能力。

正确下载应用程序的一个例子

如果你不确定第一段是什么意思,看看这个示例应用程序,其中安装了 Flask:

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)

代理对应用程序的请求

另一方面,如果您将在 Flask 应用程序的 WSGI 容器的根目录下运行该应用程序并代理对它的请求(例如,如果它是 FastCGI 的,或者如果 nginx 是针对独立 uwsgi/gevent服务器的子端点的 proxy_pass请求,那么您可以:

  • 正如 Miguel 在 他的回答中指出的那样,使用蓝图。
  • 使用来自 werkzeugDispatcherMiddleware(或来自 Su27的回答PrefixMiddleware)将应用程序下载到您正在使用的独立 WSGI 服务器中。(有关使用的代码,请参见上面的 正确下载应用程序的一个例子)。

你可以把你的路线放在一个蓝图中:

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"

然后使用前缀向应用程序注册蓝图:

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

你应该注意到 APPLICATION_ROOT不是为了这个目的。

您所要做的就是编写一个中间件来进行以下更改:

  1. 修改 PATH_INFO以处理前缀 URL。
  2. 修改 SCRIPT_NAME以生成前缀 URL。

像这样:

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()]

用中间件包装你的应用,像这样:

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)

访问 http://localhost:9010/foo/bar,

您将得到正确的结果: The URL for this page is /foo/bar

如果需要,不要忘记设置 cookie 域。

这个解决方案是由 Larvact 的要点给出的。APPLICATION_ROOT不适合这项工作,虽然它看起来像是。真的很让人困惑。

因此,我认为这个问题的有效答案是: 前缀应该在实际的服务器应用程序中配置,在开发完成时使用该服务器应用程序。Apache、 nginx 等等。

但是,如果您希望在开发过程中在调试中运行 Flask 应用程序时使用这种方法,那么可以看一下 这个要点

烧瓶的 DispatcherMiddleware救援!

我会把密码抄下来留给子孙后代:

"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)

现在,当作为一个独立的 Flask 应用程序运行上述代码时,http://localhost:5000/spam/将显示 Hello, world!

在对另一个答案的评论中,我表示我希望做这样的事情:

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/

DispatcherMiddleware应用到我的人造例子中:

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/

这与其说是 Flask/werkzeug 的回答,不如说是一个 python 的回答; 但是它很简单,而且很有效。

如果像我一样,你希望你的应用程序设置(从 .ini文件加载)也包含 Flask 应用程序的前缀(因此,不是在部署期间设置值,而是在运行期间设置值) ,你可以选择以下内容:

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

有争议的是,这有点骇人听闻,并且依赖于 Flask 路由函数 需要 a route作为第一个位置参数这一事实。

你可以这样使用它:

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

注意: 在前缀中使用一个变量(例如将它设置为 /<prefix>) ,然后在用 @app.route(...)修饰的函数中处理这个前缀,这是没有价值的。如果这样做,显然必须在修饰函数中声明 prefix参数。此外,您可能希望根据某些规则检查提交的前缀,如果检查失败,则返回404。为了避免404自定义重新实现,请 from werkzeug.exceptions import NotFound,然后 raise NotFound(),如果检查失败。

我需要类似的“ context-root”,我在/etc/httpd/conf 文件中使用了 WSGIScriptAlias:

返回文章页面 myapp.conf:

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


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


</VirtualHost>

因此,现在我可以访问我的应用程序为: http://localhost:5000/myapp

请参阅指南 -http://modwsgi.readthedocs.io/en/develop/user-guides/quick-configuration-guide.html

另一个完全不同的方法是在 uwsgi中使用 装配点

从文件关于 在同一进程中托管多个应用程序(永久链接)。

在你的 uwsgi.ini里加上

[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

如果不调用文件 main.py,则需要同时更改 mountmodule

你的 main.py可以是这样的:

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

还有一个 nginx 配置(同样是为了完整性) :

server {
listen 80;
server_name example.com


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

现在调用 example.com/foo/bar将显示由烧瓶的 url_for('bar')返回的 /foo/bar,因为它会自动适应。这样你的链接就不会出现前缀问题。

我的解决方案,烧瓶和 PHP 应用程序共存 Nginx 和 PHP5.6

在根目录中保存烧瓶,在子目录中保存 PHP

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

加一行

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

使用 PHP 的嵌套位置,让 FLASK 保持在 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;
#}
}

仔细阅读 Https://www.digitalocean.com/community/tutorials/understanding-nginx-server-and-location-block-selection-algorithms

我们需要了解位置匹配 (none) : 如果不存在修饰符,则将位置解释为前缀匹配。这意味着给定的位置将与请求 URI 的开头匹配,以确定匹配。 = : 如果使用等号,则如果请求 URI 与给定的位置完全匹配,则此块将被视为匹配。 ~ : 如果存在一个波浪形修饰符,这个位置将被解释为区分大小写的正则表达式匹配。 ~ * : 如果使用波浪形和星号修饰符,位置块将被解释为不区分大小写的正则表达式匹配。 ^ ~ : 如果存在一个 carat 和 tilde 修饰符,并且选择该块作为最佳非正则表达式匹配,则不会进行正则表达式匹配。

从 nginx 的“位置”描述来看,秩序很重要:

要查找与给定请求匹配的位置,nginx 首先检查使用前缀字符串(前缀位置)定义的位置。其中,选择和记忆匹配前缀最长的位置。然后按照正则表达式在配置文件中的出现顺序检查它们。对正则表达式的搜索在第一次匹配时终止,并使用相应的配置。如果找不到与正则表达式匹配的表达式,则使用前面记住的前缀位置的配置。

意思是:

First =. ("longest matching prefix" match)
Then implicit ones. ("longest matching prefix" match)
Then regex. (first match)
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"

对于那些还在纠结这个问题的人来说,第一个例子确实有用,但是如果你有一个 Flask 应用程序不在你的控制之下,那么完整的例子就在这里:

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,
)

我认为 su27的回答是正确的。我正在使用 gevent,这是我的代码,它运行良好:

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()

烧瓶设计图中,我们可以使用-

app = Flask(__name__)


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

任何希望在 酒瓶,很安静中做的人都可以使用-

Doc Link

app = Flask(__name__)


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

现在,你所有的路线将以 /prefix-text为前缀。只要确保在可能只使用 /link的地方使用 url_for('link')即可。

从我上面看到的所有答案来看,它们要么过于简单,要么过于复杂。

也就是说,我喜欢用 嵌套式蓝图来完成它:

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)

这样,您基本上可以将子蓝图链接到父蓝图,在父蓝图中定义前缀。这是记录在案的 给你

有了你的例子,你可以简单地将它重写为:

blueprint1 = Blueprint('blueprint1', __name__)


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


@blueprint1.route("/about")
def about_page():
return "About page"

如果您的目的是以某种方式添加前缀,

看看这个 https://stackoverflow.com/a/73883005/553095的答案 和一个 href = “ https://github.com/mskimm/prefix-superset”rel = “ nofollow norefrer”> https://github.com/mskimm/prefixed-superset