Flask dev服务器中静态文件的URL路由冲突

时间:2013-06-16 15:48:19

标签: python flask werkzeug

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

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

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

/static/images/img.jpg

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

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

3 个答案:

答案 0 :(得分:17)

这是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 - 路径深度。

对于'/<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. Flask应用设置为app = 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. 将服务器用作生产。

答案 1 :(得分:6)

因此,正如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之类的东西,只留下三个变量组件的视图。

答案 2 :(得分:3)

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

# 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