为所有 Flask 路由添加前缀

2024-12-30 08:42:00
admin
原创
43
摘要:问题描述:我有一个要添加到每条路线的前缀。现在我在每个定义中都向路线添加一个常量。有没有办法自动完成此操作?PREFIX = "/abc/123" @app.route(PREFIX + "/") def index_page(): return "Th...

问题描述:

我有一个要添加到每条路线的前缀。现在我在每个定义中都向路线添加一个常量。有没有办法自动完成此操作?

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 在他的回答中指出的那样。

  • 或者使用DispatcherMiddlewarefrom werkzeug(或PrefixMiddleware来自su27 的答案) 在您正在使用的独立 WSGI 服务器中子挂载您的应用程序。 (有关要使用的代码,请参阅上面正确子挂载应用程序的示例)。

解决方案 3:

您应该注意到 不用APPLICATION_ROOT于此目的。根据文档,在请求上下文之外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 域。

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,你需要更改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'),因为它会自动适应。这样,您的链接将不会出现前缀问题。

解决方案 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;
    #}
}

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

解决方案 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 *
相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   1008  
  在项目管理中,变更是一个不可避免的现象。无论是客户需求的调整、市场环境的变化,还是技术方案的更新,都可能引发项目的变更。如果处理不当,这些变更可能会导致项目延期、成本超支,甚至项目失败。因此,如何有效地应对项目变更,成为项目管理中的核心挑战之一。IPD(集成产品开发)作为一种高效的项目管理方法,其流程图不仅能够帮助团队...
IPD流程中的charter   0  
  IPD(Integrated Product Development,集成产品开发)是华为在长期实践中总结出的一套高效产品开发管理体系。它不仅帮助华为在全球市场中脱颖而出,也成为许多企业提升产品开发效率的参考标杆。IPD的核心在于通过跨部门协作、流程优化和资源整合,实现从需求分析到产品交付的全生命周期管理。通过实施IP...
IPD开发流程管理   0  
  华为IPD(集成产品开发)流程是一种以客户需求为导向、跨部门协同的高效项目管理方法。它通过系统化的流程设计和严格的阶段控制,确保项目从概念到交付的每个环节都能高效运作。IPD流程的核心在于打破传统职能部门的壁垒,将产品开发、市场、销售、供应链等关键环节整合到一个统一的框架中,从而实现资源的优化配置和信息的无缝流动。这种...
IPD流程中TR   0  
  在项目管理的实践中,CDCP(Certified Data Center Professional)认证评审是一个至关重要的环节。通过这一评审,项目团队不仅能够验证其数据中心设计和运营的合规性,还能提升整体管理水平。为了确保评审的顺利进行,准备一系列关键文档是必不可少的。这些文档不仅是评审的依据,也是项目团队与评审专家...
华为IPD是什么   0  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用