我想用三个变量组件定义一个 url 规则,例如:
@app.route('/<var_1>/<var_2>/<var3>/')
但我发现开发服务器在尝试匹配静态文件之前会评估这些规则。所以像:
/static/images/img.jpg
将被我的 url 规则捕获,而不是被转发到内置的静态文件处理程序。有没有办法强制开发服务器首先匹配静态文件?
PS 如果规则有两个以上的变量组件,这只是一个问题。
这是 werkzeug 路线优化功能。见和:Map.add
_Map.update
Rule.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
或设置优先级。解决方案:
Flask
为app = Flask(static_path='static', static_url_path='/more/then/your/max/variables/path/depth/static')
.@app.route('/<var_1>/<var_2>/<var3>/')
为@app.route('/prefix/<var_1>/<var_2>/<var3>/')
。@app.route('/<no_static:var_1>/<var_2>/<var3>/')
.werkzeug.routing
,创建自己的地图实现,更改werkzeug.routing.Map
为自己的实现,导入flask
。因此,正如所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
,让我的视图只剩下三个变量组件。
解决此问题的一种方法是通过欺骗已注册规则的方法来欺骗规则排序算法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
我想提供一个受@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"])
其中page
、page2
、page3
和page4
被传递给类Main
(未显示,我使用它来呈现模板)。
需要注意的一点是,这似乎不适用于带有额外斜杠的http://127.0.0.1:5000/a/b/c//形式的 URL。这是一个格式错误的 URL,我原以为烧瓶会自动重写它以删除多余的斜杠,但事实并非如此。尽管这似乎不是与静态文件冲突所特有的问题,但我认为值得一提。