为所有Flask路线添加前缀


98

我有一个前缀要添加到每个路由。现在,我在每个定义处都向路线添加了一个常量。有没有一种方法可以自动执行此操作?

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"

Answers:


75

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

子安装在另一个WSGI容器中

假设您将在WSGI容器(mod_wsgi,uwsgi,gunicorn等)中运行此应用程序;您实际上需要将该应用程序作为该WSGI容器的子部分挂载在该前缀处(任何讲WSGI的东西都可以使用),并将APPLICATION_ROOTconfig值设置为您的前缀:

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

代理请求到应用程序

另一方面,如果您将在Flask应用程序的WSGI容器的根目录下运行它并代理对它的请求(例如,如果它是FastCGI的对象,或者如果nginx正在proxy_pass-ing子端点的请求)你的单机uwsgi/ gevent服务器,您可以:

  • 正如Miguel在回答中指出的那样,使用蓝图。
  • 或者使用DispatcherMiddlewarewerkzeug(或PrefixMiddlewareSU27的答案)到副安装在您使用的独立WSGI服务器应用程序。(有关使用代码,请参见上面的正确重新安装您的应用的示例)。

@jknupp -寻找flask.Flask#create_url_adapterwerkzeug.routing.Map#bind_to_environ它看起来像它应该工作- ,您如何运行的代码?(实际上,该应用程序实际上需要安装在WSGI环境中的子路径上url_for才能返回期望值。)
Sean Vieira

我完全按照您的编写来运行,但是添加了app = Flask(name)和app.run(debug = True)
jeffknupp 2014年

4
@jknupp-这是问题所在-您实际上需要将应用程序安装为大型应用程序的子部分(使用WSGI的任何工具都可以)。我整理了一个示例要点并更新了我的答案,以使我更清楚地假设我假设是子安装的WSGI环境,而不是仅在转发子路径请求的代理后面的独立WSGI环境。
肖恩·维埃拉

3
DispatcherMiddleware当单独运行flask时,使用该方法可以工作。在Gunicorn背后奔跑时似乎无法完全正常工作。
贾斯汀2015年

1
在uwsgi中安装到子路径的方式uwsgi -s /tmp/yourapplication.sock --manage-script-name --mount /yourapplication=myapp:app。详细信息请参考(uwsgi
今天的工作

94

您可以将路线设置为蓝图:

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
嗨,米格尔;您是否知道像下面那样为蓝图注册url_prefix app.register_blueprint和通过实例化上面的Blueprint对象时注册蓝图之间的区别url_prefix='/abc/123?谢谢!
aralar

4
不同之处在于,在register_blueprint调用中具有URL前缀使应用程序可以自由地将蓝图“装入”所需的任何位置,甚至可以将同一蓝图多次装入不同的URL。如果将前缀放在蓝图本身中,则可以简化应用程序的工作,但灵活性较低。
Miguel 2014年

谢谢!!那很有帮助。我对明显的冗余感到困惑,但是我看到了这两种选择之间的权衡。
aralar

实际上,我从未尝试过这样做,但是很可能您可以将蓝图和应用程序中的URL前缀与应用程序的“拳头”和“蓝图”前缀相结合。
Miguel 2014年

4
请注意,装饰有blueprint.route的函数之后,有必要注册该蓝图。
Quint

53

您应注意APPLICATION_ROOT并非用于此目的。

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

  1. 修改PATH_INFO以处理带前缀的网址。
  2. 修改SCRIPT_NAME以生成带前缀的网址。

像这样:

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的要旨给出。该APPLICATION_ROOT不是这份工作,尽管它看起来象是。真是令人困惑。


4
感谢您添加此答案。尝试了此处发布的其他解决方案,但这是唯一对我有用的解决方案。我使用wfastcgi.py在IIS上部署了A +++
sytech's

“这APPLICATION_ROOT不是为了这份工作”-这就是我要去的地方。我希望Blueprinturl_prefix参数,APPLICATION_ROOT默认情况下合并,这样我就可以有APPLICATION_ROOT整个应用范围网址,以及url_prefix内范围的URL APPLICATION_ROOT只是对个人的蓝图。叹息
Monkpit '16

请参阅此要点,以获取我尝试使用的示例APPLICATION_ROOT
蒙克皮特

2
如果您使用的是Gunicorn,则已经支持SCRIPT_NAME。将其设置为环境变量或将其作为http标头传递:docs.gunicorn.org/en/stable/faq.html
blurrcat

1
目前的代码对我不起作用。经过研究,我在__call__方法中提出了以下建议:response = Response('That url is not correct for this application', status=404) return response(environ, start_response)使用from werkzeug.wrappers import BaseResponse as Response
Louis Becker,

10

这比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 route函数需要 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()如果检查失败。


比起使用,它更简单,更高效Blueprint。感谢分享!
香港男孩

5

因此,我认为对此的一个有效答案是:应该在开发完成时使用的实际服务器应用程序中配置前缀。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/

“因此,我认为对此的一个有效答案是:应该在开发完成时使用的实际服务器应用程序中配置前缀。Apache,nginx等。” 问题出在重定向上。如果您有前缀并且没有在Flask中设置它,那么当它重定向而不是转到/ yourprefix / path / to / url时,它只会转到/ path / to / url。有没有一种方法可以在nginx或Apache中设置前缀?
Jordan Reiter

我可能这样做的方法仅仅是使用诸如puppet或Chef之类的配置管理工具,并在其中设置前缀,然后让该工具将更改传播到需要的配置文件中。我什至不假装我知道我要说的是apache还是nginx。由于此问题/答案是特定于python的,因此建议您将您的方案作为单独的问题发布。如果您这样做,请随时在此处链接到该问题!
2013年

2

另一种完全不同的方式与挂载点uwsgi

来自有关在同一过程中托管多个应用程序的文档(permalink)。

在您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'),因为它会自动适应。这样,您的链接将可以正常工作而不会出现前缀问题。


2
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

我需要类似的所谓“上下文根”。我是使用WSGIScriptAlias在/etc/httpd/conf.d/下的conf文件中完成的:

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


1

Flask和PHP应用程序共存nginx和PHP5.6的我的解决方案

根目录中的KEEP 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;
    #}
}

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

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

顺序很重要,来自nginx的“位置”描述:

为了找到与给定请求匹配的位置,nginx首先检查使用前缀字符串定义的位置(前缀位置)。其中,选择并记住具有最长匹配前缀的位置。然后按照在配置文件中出现的顺序检查正则表达式。正则表达式的搜索在第一个匹配项上终止,并使用相应的配置。如果未找到与正则表达式匹配的内容,则使用前面记住的前缀位置的配置。

它的意思是:

First =. ("longest matching prefix" match)
Then implicit ones. ("longest matching prefix" match)
Then regex. (first match)

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,
    )
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.