7

我想用三个变量组件定义一个 url 规则,例如:

@app.route('/<var_1>/<var_2>/<var3>/')

但我发现开发服务器在尝试匹配静态文件之前会评估这些规则。所以像:

/static/images/img.jpg

将被我的 url 规则捕获,而不是被转发到内置的静态文件处理程序。有没有办法强制开发服务器首先匹配静态文件?

PS 如果规则有两个以上的变量组件,这只是一个问题。

4

4 回答 4

18

这是 werkzeug 路线优化功能。见和:Map.add_Map.updateRule.match_compare_key

def match_compare_key(self):
    """The match compare key for sorting.

    Current implementation:

    1. rules without any arguments come first for performance
    reasons only as we expect them to match faster and some
    common ones usually don't have any arguments (index pages etc.)
    2. The more complex rules come first so the second argument is the
    negative length of the number of weights.
    3. lastly we order by the actual weights.

    :internal:
    """
    return bool(self.arguments), -len(self._weights), self._weights

self.arguments- 当前参数,self._weights- 路径深度。

因为'/<var_1>/<var_2>/<var3>/'我们有(True, -3, [(1, 100), (1, 100), (1, 100)]). 有(1, 100)- 最大长度为 100 的默认字符串参数。

因为'/static/<path:filename>'我们有(True, -2, [(0, -6), (1, 200)]). 有(0, 1)- 路径非参数字符串长度static(1, 200)- 路径字符串参数最大长度 200。

所以我没有找到任何漂亮的方法来为地图规则设置自己的Map实现Flask.url_map或设置优先级。解决方案:

  1. 将应用程序设置Flaskapp = Flask(static_path='static', static_url_path='/more/then/your/max/variables/path/depth/static').
  2. 更改@app.route('/<var_1>/<var_2>/<var3>/')@app.route('/prefix/<var_1>/<var_2>/<var3>/')
  3. 添加自己的转换器并用作@app.route('/<no_static:var_1>/<var_2>/<var3>/').
  4. 导入werkzeug.routing,创建自己的地图实现,更改werkzeug.routing.Map为自己的实现,导入flask
  5. 在生产环境中使用服务器。
于 2013-06-17T11:36:11.527 回答
7

因此,正如所tbicr指出的,这种行为深深植根于 Werkzeug 中,并没有真正优雅的方式从 Flask 中处理它。我能想到的最好的解决方法是:

定义一个互补的静态文件处理程序,如:

@app.route('/static/<subdir>/<path:filename>/')
def static_subdir(subdir=None, filename=None):

    directory = app.config['STATIC_FOLDER'] + subdir
    return send_from_directory(directory, filename)

这里,app.config['STATIC_FOLDER']是运行应用程序的机器上静态文件夹的完整路径。

现在,这个处理程序捕获了类似的东西/static/images/img.jpg,让我的视图只剩下三个变量组件。

于 2013-06-17T14:01:32.017 回答
3

解决此问题的一种方法是通过欺骗已注册规则的方法来欺骗规则排序算法match_compare_key()。请注意,此 hack 仅适用于直接注册到app.route()(Flask 对象)的路由,不适用于蓝图。蓝图的路线仅在蓝图在主应用程序上注册后才会添加到全局 url Map,这使得修改生成的规则具有挑战性。

# an ordinary route
@app.route('/<var1>/<var2>/<var3>')
def some_view(var1, var2, var3):
    pass

# let's find the rule that was just generated
rule = app.url_map._rules[-1]

# we create some comparison keys:
# increase probability that the rule will be near or at the top
top_compare_key = False, -100, [(-2, 0)]
# increase probability that the rule will be near or at the bottom 
bottom_compare_key = True, 100, [(2, 0)]

# rig rule.match_compare_key() to return the spoofed compare_key
rule.match_compare_key = lambda: top_compare_key

请注意,在这种情况下,生成的欺骗函数未绑定到规则对象。因此,在调用 时rule.match_compare_key(),该函数不会收到self参数。如果要正确绑定函数,请改为执行以下操作:

spoof = lambda self: top_compare_key
rule.match_compare_key = spoof.__get__(rule, type(rule))

我们可以用装饰器概括以上内容

def weighted_route(*args, **kwargs):
    def decorator(view_func):
        compare_key = kwargs.pop('compare_key', None)
        # register view_func with route
        app.route(*args, **kwargs)(view_func)

        if compare_key is not None:
            rule = app.url_map._rules[-1]
            rule.match_compare_key = lambda: compare_key

        return view_func
    return decorator

# can be used like @app.route(). To weight the rule, just provide
# the `compare_key` param.
@weighted_route('/<var1>/<var2>/<var3>', compare_key=bottom_compare_key)
def some_view(var1, var2, var3):
    pass

作为上下文管理器实现的相同 hack。

import contextlib

@contextlib.contextmanager
def weighted_route(compare_key=None):
    yield
    if compare_key is not None:
        rule = app.url_map._rules[-1]
        rule.match_compare_key = lambda: compare_key

# and to use

with weighted_route(compare_key):
    @app.route('/<var1>/<var2>/<var3>')
    def some_view(var1, var2, var3):
        pass
于 2014-02-25T08:22:37.727 回答
0

我想提供一个受@tbicr(建议#3)启发的答案,它似乎比其他一些解决方案更干净:

from werkzeug.routing import BaseConverter
class NoStaticConverter(BaseConverter):
    regex = '[^/]+(?<!/static)'
app.url_map.converters['nostatic'] = NoStaticConverter
app.add_url_rule('/<nostatic:page>/<page2>/<page3>/<page4>/',view_func=Main.as_view('level4'),methods=["GET"])

其中pagepage2page3page4被传递给类Main(未显示,我使用它来呈现模板)。

需要注意的一点是,这似乎不适用于带有额外斜杠的http://127.0.0.1:5000/a/b/c//形式的 URL。这是一个格式错误的 URL,我原以为烧瓶会自动重写它以删除多余的斜杠,但事实并非如此。尽管这似乎不是与静态文件冲突所特有的问题,但我认为值得一提。

于 2022-01-18T19:29:38.523 回答