Конфликты маршрутизации URL для статических файлов на сервере Flask dev
Я хочу определить правило URL с тремя переменными компонентами, например:
@app.route('/<var_1>/<var_2>/<var3>/')
Но я обнаружил, что сервер разработки оценивает такие правила, прежде чем пытаться сопоставить статические файлы. Так что-нибудь вроде:
/static/images/img.jpg
будет поймано моим правилом url, а не перенаправлено во встроенный статический обработчик файлов. Есть ли способ заставить сервер разработки сначала найти статические файлы?
PS Это проблема, только если правило содержит более двух переменных компонентов.
4 ответа
Это функция оптимизации маршрута 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()
метод. Обратите внимание, что этот хак работает только с маршрутами, которые были зарегистрированы непосредственно с app.route()
(объект колбы), а не с чертежами. Маршруты чертежей добавляются в глобальную карту URL-адресов только после регистрации чертежа в основном приложении, что усложняет изменение сгенерированных правил.
# 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
Тот же хак реализован как менеджер контекста.
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
(не показано, который я использую для рендеринга шаблонов).
Следует отметить, что это не работает для URL-адресов вида http://127.0.0.1:5000/a/b/c// с дополнительной косой чертой. Это неверный URL-адрес, и я думал, что фляга автоматически перепишет его, чтобы удалить лишнюю косую черту, но это не так. Хотя это не похоже на проблему, связанную с конфликтами со статическими файлами, я подумал, что стоит упомянуть об этом.