Python 为所有 Flask 路由添加前缀

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/18967441/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-19 12:27:46  来源:igfitidea点击:

Add a prefix to all Flask routes

pythonroutesflask

提问by Evan Hahn

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"

采纳答案by Sean Vieira

The answer depends on how you are serving this application.

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

Sub-mounted inside of another WSGI container

子安装在另一个 WSGI 容器内

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 prefixthe application as a sub-part of that WSGI container (anything that speaks WSGI will do) and to set your APPLICATION_ROOTconfig value to your prefix:

假设您要在 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/"

Setting the APPLICATION_ROOTconfig 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.

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

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:

如果您不确定第一段的意思,请看一下这个安装了 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)

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/ geventserver then you can either:

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

  • Use a Blueprint, as Miguel points out in his answer.
  • oruse the DispatcherMiddlewarefrom werkzeug(or the PrefixMiddlewarefrom 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 appabove for the code to use).
  • 使用蓝图,正如 Miguel 在他的回答中指出的那样。
  • 使用DispatcherMiddlewarefrom werkzeug(或PrefixMiddlewarefrom su27's answer)将您的应用程序子安装在您正在使用的独立 WSGI 服务器中。(有关要使用的代码,请参阅上面正确安装应用程序的示例)。

回答by Miguel

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

回答by su27

You should note that the APPLICATION_ROOTis NOT for this purpose.

您应该注意,这APPLICATION_ROOT不是用于此目的。

All you have to do is to write a middleware to make the following changes:

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

  1. modify PATH_INFOto handle the prefixed url.
  2. modify SCRIPT_NAMEto generate the prefixed url.
  1. 修改PATH_INFO以处理带前缀的 url。
  2. 修改SCRIPT_NAME以生成带前缀的 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,

参观http://localhost:9010/foo/bar

You will get the right result: The URL for this page is /foo/bar

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

And don't forget to set the cookie domain if you need to.

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

This solution is given by Larivact's gist. The APPLICATION_ROOTis not for this job, although it looks like to be. It's really confusing.

这个解决方案是由Larivact 的 gist给出。该APPLICATION_ROOT不是这份工作,尽管它看起来象是。这真的很混乱。

回答by Monkpit

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.

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

However, if you would like this to work during development while running the Flask app in debug, take a look at this gist.

但是,如果您希望在调试中运行 Flask 应用程序的同时在开发过程中使用它,请查看此要点

Flask's DispatcherMiddlewareto the rescue!

FlaskDispatcherMiddleware来拯救你了!

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

现在,当将上述代码作为独立的 Flask 应用程序运行时,http://localhost:5000/spam/将显示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 DispatcherMiddlewareto my contrived example:

适用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/

回答by 7heo.tk

This is more of a python answer than a Flask/werkzeug answer; but it's simple and works.

这与其说是 Flask/werkzeug 答案,不如说是 Python 答案;但它很简单而且有效。

If, like me, you want your application settings (loaded from an .inifile) 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:

如果像我一样,您希望应用程序设置(从.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

Arguably, this is somewhat hackish and relies on the fact that the Flask route function requiresa routeas a first positional argument.

可以说,这有点骇人听闻,并且依赖于 Flask 路由函数需要aroute作为第一个位置参数的事实。

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 prefixparameter 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 NotFoundand then raise NotFound()if the check fails.

注意:可以在前缀中使用变量(例如通过将其设置为/<prefix>),然后在您用@app.route(...). 如果这样做,显然必须prefix在装饰函数中声明参数。此外,您可能希望根据某些规则检查提交的前缀,如果检查失败则返回 404。为了避免404定制的重新实施,请from werkzeug.exceptions import NotFound然后raise NotFound()如果检查失败。

回答by dganesh2002

I needed similar so called "context-root". I did it in conf file under /etc/httpd/conf.d/ using WSGIScriptAlias :

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

myapp.conf:

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

所以现在我可以访问我的应用程序:http://localhost:5000/myapp

See the guide - http://modwsgi.readthedocs.io/en/develop/user-guides/quick-configuration-guide.html

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

回答by luckydonald

Another completely different way is with mountpointsin uwsgi.

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

From the doc about Hosting multiple apps in the same process(permalink).

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

In your uwsgi.iniyou add

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

If you don't call your file main.py, you need to change both the mountand the module

如果您不调用您的文件main.py,则需要同时更改 themount和 themodule

Your main.pycould look like this:

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

And a nginx config (again for completeness):

还有一个 nginx 配置(再次为了完整性):

server {
  listen 80;
  server_name example.com

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

Now calling example.com/foo/barwill display /foo/baras returned by flask's url_for('bar'), as it adapts automatically. That way your links will work without prefix problems.

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

回答by Jayanta

My solution where flask and PHP apps coexist nginx and PHP5.6

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

KEEP Flask in root and PHP in subdirectories

将 Flask 保留在根目录中,将 PHP 保留在子目录中

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

Add 1 line

添加 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

USE NESTED LOCATIONS for PHP and let FLASK remain in root

为 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;
    #}
}

READ carefully https://www.digitalocean.com/community/tutorials/understanding-nginx-server-and-location-block-selection-algorithms

仔细阅读 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.

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

Order is important, from nginx's "location" description:

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

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.

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

It means:

它的意思是:

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

回答by abhimanyu

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"

回答by vishnugopal

For people still struggling with this, the first example does work, but the full example is here if you have a Flask app that is not under your control:

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