【问题标题】:URL routing conflicts for static files in Flask dev serverFlask 开发服务器中静态文件的 URL 路由冲突
【发布时间】:2013-06-12 16:02:18
【问题描述】:

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

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

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

/static/images/img.jpg

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

附:仅当规则具有两个以上的变量组件时,这才是一个问题。

【问题讨论】:

标签: python flask werkzeug


【解决方案1】:

这是 werkzeug 路线优化功能。见Map.addMap.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 - 路径深度。

对于'/&lt;var_1&gt;/&lt;var_2&gt;/&lt;var3&gt;/',我们有(True, -3, [(1, 100), (1, 100), (1, 100)])。有(1, 100) - 默认字符串参数,最大长度为 100。

对于'/static/&lt;path:filename&gt;',我们有(True, -2, [(0, -6), (1, 200)])。有(0, 1) - 路径非参数字符串长度static(1, 200) - 路径字符串参数最大长度200。

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

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

【讨论】:

  • 好吧,我一开始误解了你的回答。因此,使用选项 1,您更改了提供静态文件的 url,并向该 url 添加了足够的“虚拟”子目录,这样它就不可能与由可变组件组成的任何现有 url 规则发生冲突。因此,对于我的示例,static_url_path 需要更改为:/static/static/static/static,然后它将不再被@app.route('/&lt;var_1&gt;/&lt;var_2&gt;/&lt;var3&gt;/') 捕获
  • 我使用了选项 1。我不知道为什么,但是当我有 static_path 参数时它不起作用,但当我删除它时它起作用。我最终使用了:app = Flask(__name__, static_url_path='/r/s/static')
【解决方案2】:

因此,正如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 这样的东西,让我的视图只剩下三个变量组件。

【讨论】:

  • 这是个好主意,但您不需要添加自己的处理程序,因为您可以使用Flask 应用程序设置来执行此操作。请参阅我的答案解决方案 1:将 Flask 应用程序设置为 app = Flask(static_path='static', static_url_path='/more/then/your/max/variables/path/depth/static'),但它会更改静态 url 路径(url_for('static', filename='images/img.jpg' 没问题)。
  • IMO 你的解决方案是这里提到的最好的,petrus。引入虚拟子目录肯定比在 URL 规则中显式捕获更难看。
【解决方案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

【讨论】:

    【解决方案4】:

    我想提供一个受@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,我原以为烧瓶会自动重写它以删除多余的斜杠,但事实并非如此。虽然这似乎不是与静态文件冲突所特有的问题,但我认为值得一提。

    【讨论】:

      猜你喜欢
      • 2017-07-12
      • 1970-01-01
      • 2020-05-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多