为所有 Flask 路由添加前缀
- 2024-12-30 08:42:00
- admin 原创
- 43
问题描述:
我有一个要添加到每条路线的前缀。现在我在每个定义中都向路线添加一个常量。有没有办法自动完成此操作?
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:
你可以把你的路线放在蓝图中:
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')
解决方案 2:
答案取决于您如何提供该应用程序。
在另一个 WSGI 容器内进行子安装
假设您要在 WSGI 容器(mod_wsgi、uwsgi、gunicorn 等)内运行此应用程序;您需要实际挂载该前缀,将该APPLICATION_ROOT
应用程序作为该 WSGI 容器的子部分(任何使用 WSGI 的东西都可以),并将您的配置值设置为您的前缀:
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)
代理对应用程序的请求
另一方面,如果你将在其 WSGI 容器的根目录下运行 Flask 应用程序并将请求代理到它(例如,如果它被 FastCGI 所取代,或者如果 nginx 正在proxy_pass
将对子端点的请求发送到你的独立uwsgi
/gevent
服务器,那么你可以:
使用蓝图,正如 Miguel 在他的回答中指出的那样。
或者使用
DispatcherMiddleware
fromwerkzeug
(或PrefixMiddleware
来自su27 的答案) 在您正在使用的独立 WSGI 服务器中子挂载您的应用程序。 (有关要使用的代码,请参阅上面正确子挂载应用程序的示例)。
解决方案 3:
您应该注意到 不用APPLICATION_ROOT
于此目的。根据文档,在请求上下文之外APPLICATION_ROOT
使用。
你所要做的就是编写一个中间件来进行以下更改:
修改
PATH_INFO
以处理带前缀的 url。修改
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 域。
Larivact 的 gist给出了此解决方案。APPLICATION_ROOT
虽然看起来像是,但并不适合这项工作。这确实令人困惑。
解决方案 4:
这更像是一个 python 答案,而不是 Flask/werkzeug 答案;但它很简单并且有效。
如果像我一样,您希望应用程序设置(从.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 路由函数需要作为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()
解决方案 5:
因此,我认为对此问题的有效答案是:前缀应在开发完成时使用的实际服务器应用程序中进行配置。Apache、nginx 等。
但是,如果您希望在开发过程中在调试中运行 Flask 应用程序时此功能能够正常工作,请查看此要点。
FlaskDispatcherMiddleware
来救援了!
我将把代码复制在这里以供后人参考:
"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/
解决方案 6:
另一种完全不同的方式是使用挂载点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
,你需要更改mount
和module
你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
将显示/foo/bar
为 flask 的返回值url_for('bar')
,因为它会自动适应。这样,您的链接将不会出现前缀问题。
解决方案 7:
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"
解决方案 8:
我需要类似的所谓的“context-root”。我在 /etc/httpd/conf.d/ 下的 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
解决方案 9:
我的解决方案是 flask 和 PHP 应用共存 nginx 和 PHP5.6
将 Flask 保留在根目录中,将 PHP 保留在子目录中
sudo vi /etc/php/5.6/fpm/php.ini
添加 1 行
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 保留在根目录中
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;
#}
}
我们需要了解位置匹配(无):如果没有修饰符,则位置将被解释为前缀匹配。这意味着给定的位置将与请求 URI 的开头进行匹配以确定匹配。=:如果使用等号,则当请求 URI 与给定的位置完全匹配时,此块将被视为匹配。~:如果存在波浪号修饰符,则此位置将被解释为区分大小写的正则表达式匹配。~*:如果使用波浪号和星号修饰符,则位置块将被解释为不区分大小写的正则表达式匹配。^~:如果存在插入符号和波浪号修饰符,并且如果此块被选为最佳非正则表达式匹配,则不会进行正则表达式匹配。
顺序很重要,来自nginx的“位置”描述:
为了找到与给定请求匹配的位置,nginx 首先检查使用前缀字符串(前缀位置)定义的位置。从中,选择并记住具有最长匹配前缀的位置。然后按照正则表达式在配置文件中出现的顺序检查正则表达式。正则表达式的搜索在第一次匹配时终止,并使用相应的配置。如果没有找到与正则表达式匹配的,则使用先前记住的前缀位置的配置。
这意味着:
First =. ("longest matching prefix" match)
Then implicit ones. ("longest matching prefix" match)
Then regex. (first match)
解决方案 10:
对于仍然为此而苦苦挣扎的人来说,第一个例子确实有效,但如果你有一个不受你控制的 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,
)
解决方案 11:
在Flask Blueprint中,我们可以使用 -
app = Flask(__name__)
app.config['APPLICATION_ROOT'] = '/prefix-text'
任何想要在flask-restful中做的事情都可以利用 -
文档链接
app = Flask(__name__)
api = Api(app, prefix='/pefix-text')
现在,所有路线都将以 为前缀/prefix-text
。只需确保url_for('link')
在可能仅使用 的地方使用 即可/link
。
解决方案 12:
从上面我看到的所有答案来看,它们要么过于简单,要么过于复杂。
也就是说,我喜欢使用嵌套蓝图来实现它:
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"
解决方案 13:
如果要在使用 Nginx 作为反向代理时处理前缀。Werkzeug 的ProxyFix
中间件将是一个更简单的解决方案:
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 将读取X-Forwarded-Prefix
标头并将其设置为。因此,请确保在 Nginx 配置中SCRIPT_NAME
设置标头:X-Forwarded-Prefix
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;
}
}
更多详细信息请参阅https://stackoverflow.com/a/75123044/5511849
解决方案 14:
我认为 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()
解决方案 15:
如果你的目的是以某种方式添加前缀,
看看答案https://stackoverflow.com/a/73883005/553095
和https://github.com/mskimm/prefixed-superset
解决方案 16:
我ApiFlask
通过嵌入式开发服务器(开发环境)和(暂存和生产环境)使用应用程序。我相信该解决方案也gunicorn
适用于 vanilla 。Flask
我希望将配置放在一个地方 - 包__init__.py
文件,导入放置在views.py
文件中的路由定义,而不是让它在多个文件中或路由定义之前的某个部分和之后的其余部分中乱窜。解决方案 viaBlueprint
让我专门注释路由,我理解这一点,但觉得对我的情况来说有点过头了。
解决方案基于并将DispatcherMiddleware
所有必要内容放置在包根文件 ( __init__.py
) 中。我从环境变量中获取前缀,但这并不重要。
请注意,自动生成的 OAS 文档也可以通过前缀路由访问。
最小示例如下:
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 *
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)
- 项目管理必备:盘点2024年13款好用的项目管理软件