给所有Flask路由添加前缀

139

我有一个前缀,想要添加到每个路由上。目前我在每个定义处都添加一个常量到路由上。有没有一种自动完成这个操作的方式?

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"
16个回答

144
您可以将路由放在蓝图中:
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')

3
嗨,Miguel;你知道在注册蓝图时使用app.register_blueprint下面的url_prefix和在实例化蓝图对象时通过传递url_prefix='/abc/123'之间的区别吗?谢谢! - aralar
4
区别在于在 register_blueprint 调用中使用 URL 前缀使应用程序可以自由地将蓝图 "挂载" 到任何想要的位置,甚至可以在不同的 URL 上多次挂载相同的蓝图。如果在蓝图本身中放置前缀,则这使得应用程序更容易,但灵活性较低。 - Miguel Grinberg
谢谢!这非常有帮助。我之前对这种明显的冗余感到困惑,但现在我看到了这两个选项之间的权衡。 - aralar
实际上,我从未尝试过这个,但很可能您可以在蓝图和应用程序中同时组合URL前缀,先使用应用程序的前缀,然后是蓝图的前缀。 - Miguel Grinberg
17
注意,在使用 blueprint.route 装饰的函数之后,才需要注册蓝图。请注意不要改变原始含义,使翻译更易于理解。 - Quint
显示剩余7条评论

97

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

作为另一个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配置值设置为特定URL前缀,可以简单地限制Flask会话cookie的使用。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在his answer中指出的那样。
  • 或者使用来自werkzeugDispatcherMiddleware(或来自su27's answerPrefixMiddleware)将您的应用程序子挂载到您使用的独立WSGI服务器中。(有关要使用的代码,请参见上面的正确子挂载应用程序的示例)。

@jknupp - 看着flask.Flask#create_url_adapterwerkzeug.routing.Map#bind_to_environ,它看起来应该可以工作 - 你是如何运行代码的?(实际上,应用程序需要在WSGI环境中挂载到子路径上,以便url_for返回预期值。) - Sean Vieira
4
@jknupp - 这就是问题所在 - 你需要将应用程序实际挂载为较大应用程序的子部分(任何使用WSGI的应用程序都可以)。我已经创建了一个示例代码片段,并更新了我的答案,以使其更清晰,我假设是一个子挂载的WSGI环境,而不是仅转发子路径请求的独立WSGI环境。 - Sean Vieira
3
使用 DispatcherMiddleware 这种方法,在单独运行 Flask 时可以正常工作。但是在 Gunicorn 后面运行时似乎无法正常运行。 - Justin
1
我认为子挂载示例中有一个错别字。在 app.wsgi_app = DispatcherMiddleware() 调用中将 "simple" 替换为 "run_simple" 对我有效。 - Chris Cummings
1
在uwsgi中挂载子路径的方法是 uwsgi -s /tmp/yourapplication.sock --manage-script-name --mount /yourapplication=myapp:app。详见(uwsgi文档)[http://flask.pocoo.org/docs/1.0/deploying/uwsgi/]。 - todaynowork
显示剩余8条评论

75

请注意,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

你将获得正确结果:此页面的URL为 /foo/bar

如果需要,请别忘了设置cookie域。

这个解决方案由Larivact的gist提供。虽然它看起来像是APPLICATION_ROOT的工作,但实际上并不是,这真的很令人困惑。


7
谢谢您添加该答案。 尝试了此处发布的其他解决方案,但这是唯一有效的解决方案。评级:A+++。我正在使用wfastcgi.py在IIS上部署。 - sytech
“APPLICATION_ROOT” 不适用于此任务 - 这就是我犯错的地方。我希望 Blueprinturl_prefix 参数和 APPLICATION_ROOT 默认情况下结合在一起,这样我就可以为整个应用程序设置 APPLICATION_ROOT 范围的 URL,并且在 APPLICATION_ROOT 中仅为单个蓝图设置 url_prefix 范围的 URL。唉。 - Kyle Pittman
3
如果你正在使用gunicorn,那么SCRIPT_NAME已经被支持了。将它设置为环境变量或者通过HTTP头传递:http://docs.gunicorn.org/en/stable/faq.html - blurrcat
1
代码在我这里无法正常工作。经过一些研究,在__call__方法中的else之后,我加入了以下代码:from werkzeug.wrappers import BaseResponse as Response response = Response('That url is not correct for this application', status=404) return response(environ, start_response) - Louis Becker
你所需要做的就是尝试提出的解决方案,以确定它是否有效... - Wolfgang Fahl
显示剩余3条评论

13

这更像是一份 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

可以说,这有点像是hackish的,并且依赖于Flask路由函数需要route作为第一个位置参数。

您可以像这样使用它:

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

NB: 值得注意的是,可以在前缀中使用变量(例如将其设置为/<prefix>),然后在用@app.route(...)装饰的函数中处理这个前缀。如果这样做,您显然需要在被装饰的函数中声明prefix参数。此外,您可能想要对提交的前缀进行一些规则检查,并在检查失败时返回404错误。为了避免重新实现404自定义,请from werkzeug.exceptions import NotFound,然后如果检查失败请raise NotFound()


1
这很简单,比使用“Blueprint”更有效。感谢分享! - HK boy

5
所以,我认为一个有效的答案是:当开发完成时,在实际使用的服务器应用程序中配置前缀。Apache、nginx等。

但是,如果你想在调试期间运行Flask应用程序时使其起作用,请查看this gist

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/

1
因此,我认为对此的一个有效答案是:当开发完成时,应在实际使用的服务器应用程序中配置前缀。Apache、nginx等。问题出现在重定向中;如果您有一个前缀并且没有在Flask中设置它,那么当重定向时,它会跳转到/path/to/url而不是/yourprefix/path/to/url。是否有一种方法可以在nginx或Apache中设置前缀? - Jordan Reiter
我可能会使用像Puppet或Chef这样的配置管理工具来完成此操作,然后在那里设置前缀,并让工具将更改传播到需要去的配置文件中。我甚至不会假装我知道自己在谈论什么Apache或Nginx。由于这个问题/答案是针对Python特定的,我鼓励您将您的情况作为一个单独的问题发布。如果您这样做了,请随意在这里链接到该问题! - Kyle Pittman

3
另一种完全不同的方法是使用uwsgi中的挂载点(mountpoints)

来自关于在同一进程中托管多个应用程序的文档(永久链接)。

在您的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将显示/foo/bar,作为flask的url_for('bar')返回的结果,并自动适应。这样,您的链接将不会出现前缀问题。

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"

1
请考虑添加一份说明。 - jpp
1
我发现了两个很好的解释,分别在 exploreflask官方文档 中。 - yuriploc

1

对于仍在苦苦挣扎的人,第一个示例确实可行,但如果您有一个不受控制的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,
    )

1
在 Flask 蓝图中,我们可以使用 -
app = Flask(__name__)

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

想要使用 flask-restful 的人可以利用 -

文档链接

app = Flask(__name__)

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

现在,您所有的路由都将以/prefix-text为前缀。只需确保在可能仅使用/link的地方使用url_for('link')

1

我需要类似所谓的“上下文根”的东西。我在 /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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接