Диспетчеризация URL в веб-фреймворке Pyramid. Описан преимущественно "Traversal"
Здеся написано избранное про диспетчеризацию URL и нахождение обработчиков в веб-приложениях, созданных на основе веб-фреймворка Pyramid.
Не рассматриваются подробно все возможные типы объектов, с помощью которых реализуется "Traversal". Рассматриваются только основные принципы.
Рассматривается с позиции разбора URI, а не полного URL (то есть особенности работы Pyramid на виртуальном хостинге не рассматриваются).
Рассматривается только центро-императивный метод конфигурирования. Декларативный и имеративный по месту определения (с помощью декораторов) - не рассматривается.
Диспетчеризация запроса может осуществляться в фреймворке двумя методами "Traversal" или "URL Dispath". Возможно совмещение обоих методов в одном веб-приложении.
"URL Dispath" являет собой более явный метод - по определенному куску в URI определяем кто должен заниматься обработкой. Слышал, что данный механизм практикуется в Django.
При первичной загрузке веб-приложения указание какой именно URI как именно будет обрабатываться по методу "URL Dispath" делается, например, так (все писать в одну строку):
config.add_route('home', '/', view='myappname.views.my_view', view_renderer='templates/mytemplate.pt')
То есть здесь явно прописан URI корень "/" и указано что он будет обработан функцией my_view, что в описана в файле views.py.
Не проверял, но подозреваю, что проверка соответствия URI по методу "URL Dispath" делается с помощью регулярных выражений.
Разработчики Pyramid считают, что метод разбора URI "URL Dispath" необходим - он хорошо описан в документации, в поставку Pyramid входит шаблон построения веб-приложения с "URL Dispath", кроме этого в документации описана совместная работа в одном веб-приложении двух методов одновременно - и "URL Dispath" и "Traversal".
Тем не менее, разработчики настоятельно советуют использовать прежде всего метод "Traversal". Данный метод знаком тем, кто использует Zope/BlueBream.
На русский язык термин "traversal" в контексте этой статьи лучше всего перевести как "обход", "прохождение".
Метод "Traversal" также может быть назван "Местоположение ресурса".
Поиск подходящего обработчика для запроса в методе "Traversal" построен на обходе предварительно построенного дерева, называемого "деревом ресурсов".
"Дерево ресурсов" может быть как статической структурой (но тогда нет смысла в "Traversal" - фактически тоже самое можно получить и с помощью "URL Dispath"), так и структурой, динамически генерируемой программным кодом при каждом запросе.
При динамической генерации при каждом запросе нет необходимости создавать все дерево всевозможных URI сайта. Pyramid будет опрашивать последовательно только ту часть "дерева ресурсов", которая соответствует URI обслуживаемого запросом.
"Дерево ресурсов" - это структура, построенная на вложенных друг в друга объектов, напоминающих словари (dict) языка Python. Это могут быть, например, классы, для которых определен метод "__getitem__(...)" - тогда к объекту можно будет обратиться как к словарю, например, "myobject['my_url_part']"
Корень дерева инициализируется на этапе первичной загрузки веб-приложения.
Объекты (словари) в этом дереве называются "ресурсами" (resource), а найденное в результате работы механизма "Traversal" (обход) значение называется "контекстом" (context).
Механизм обхода дерева ресурсов подобен прохождению через дерево каталогов в файловой системе при поиске файла.
URI вида "/product/category_1/product_item" будет разобран с помощью обращения к дереву ресурсов так: 'get_root()["product"]["category_1"]["product_item"]'. Здесь get_root() некая функция, сформировавшая и возвратившая дерево ресурсов.
Допустим может быть 2 способа отображения содержимого сайта согласно URI: "/product/category_1/product_item" и другой вариант "/product/category_1/product_item/edit". То есть простой просмотр и редактирование страницы.
Тогда нам нужно позаботиться, чтобы для "product_item" было зарегистрировано два представления - для URI, оканчивающихся на "edit" и без "edit" - то есть для редактирования и для просмотра.
В этом случае Pyramid при диспечеризации "/product/category_1/product_item/edit" сделает нечто вроде:
context = get_root()['product']['category_1']['product_item']
view_callable = get_view(context, 'edit')
request.context = context
view_callable(request)
В примере выше view_callable() - это обработчик в нашем веб-приложении, который и должен сгенерировать соответствующий URI ответ.
А вот в том случае, если при первичной загрузке нашего веб-приложения не было сконфигурировано никакого обработчика, то при обходе по алгоритму "Traversal" не будет найдено никакого обработчика для URI запроса - и тогда Pyramid сгенерирует ошибку 404 (страница не найдена).
В каких случаях есть у "Traversal" преимущества перед "URL Dispath"? Например, для таких URI:
/{userid}/{typename}/{objectid}[/{view_name}]
Здесь "userid" и "objectid" являются переменными значениям, зависящими от конкретной ситуации и алгоритм "URL Dispath" не функционален - с его точки зрения весь URI является необрабатываемой строкой с параметрами.
А алгоритм "Traversal", благодаря тому, что дерево ресурсов может быть построено из объектов, содержащих "__getitem__(...)" прекрасно отработает ситуацию с переменными "userid" и "objectid" в середине URI.
Подробно рассмотрим реализацию с помощью "Traversal" следущего URI:
"/tovary/gruppa_11/podgruppa_2/tovar_333?dobavit_v_korzinu=5"
Алгоритм "Traversal" (да и алгоритм "URL Dispath" поступит аналогично) будет рассматривать только часть строки URI "/tovary/gruppa_11/podgruppa_2/tovar_333".
Строка будет разбита на компоненты: "tovary", "gruppa_11", "podgruppa_2", "tovar_333".
Алгоритм "Traversal" начнет обход "дерева ресурсов" с вызова "фабрики ресурсов", чтобы получить "корень дерева ресурсов".
"Дерево ресурсов" алгоритм "Traversal" получает для каждого запроса. Если при начальной загрузке приложения на этапе конфигурирования не определена фабрика ресурсов, то будет использован некий "корень дерева ресурсов" по умолчанию.
Затем пройдет по дереву, примерно так:
root = get_root()
context = root.__getitem__("tovary")
context = context.__getitem__("gruppa_11")
context = context,__getitem__("podgruppa_2")
context = context.__getitem__("tovar_333")
В случае, если на каком то из шагов у полученного объекта "context" не окажется метода "__getitem__(...)" или будет возбуждено исключение "KeyError", то обход "дерева ресурсов" прекращается. И контекстом (context) будет являться элемент, полученный на предыдущем шаге.
В результате работы механизма "Traversal" кроме контекста (context) должно быть получено и имя представления (view name). Если оход дерева ресурсов заканчивается раньше, чем закончатся все элементы URI, то именем представления становится оставшаяся часть URI. Если же дерево ресурсов было обойдено целиком, то именем представления становится пустая строка.
Например, для URI "/tovary/gruppa_11/podgruppa_2/tovar_333/edit":
root = get_root()
context = root.__getitem__("tovary")
context = context.__getitem__("gruppa_11")
context = context,__getitem__("podgruppa_2")
context = context.__getitem__("tovar_333")
# а у товара нет метода __getitem__(...)
view_name = "edit"
Полученные контекст и имя представления используются системой поиска представлений для нахождения подходящего обработчика (view callable).
"Фабрика ресурсов" являет собой функцию или класс. Эта функция или класс передаются при начальной загрузке приложения конфигуратору Configurator как аргумент "root_factory", например:
config = Configurator(root_factory=get_root)
В качестве параметра эта функция получает http-запрос (request). Возвращает - корень дерева ресурсов.
Ежели фабрикой ресурсов является класс, то в качестве параметра http-запрос получает конструктор класса "__init(...)__":
Если при первичной загрузке веб-приложения конфигуратору не передана фабрика ресурсов или передано значение "None", то используется фабрика ресурсов по умолчанию, которая позволяет отрабатывать один единственный URI - "/" - потому что создает дерево ресурсов, которое не имеет дочерних элементов.
Целью работы механизма "Traversal" является определения по запросу контекста и имени представления. Поиск представления являет собой отдельную задачу.
1. В самом начале обработки запроса диспетчер Pyramid создает переменную "запрос" (request) из параметров, полученных от веб-сервера по протоколу WSGI.
2. Затем диспетчер вызывает фабрику ресурсов с параметром "запрос" (request) и получает корень дерева ресурсов. Фабрика ресурсов, имеет возможность, получив в качестве параметра запрос, сгенерировать дерево ресурсов индивидуально, например, для конкретного пользователя.
3. Диспетчер разбивает URI на компоненты, рассматривая символ "/" как разделитель компонент. При этом каждая компонента проходит обработку функциями urllib.unquote и Unicode.decode в URI представляется как строка в кодировке UTF-8. На этапе декодирования Unicode возможно получение исключения TypeError.
4. Затем происходит описанный несколькими абзацами выше последовательный вызов метода "__getitem__(...)" сначала у корня дерева ресурсов, затем у полученных элементов. В качестве параметров передаются поочередно компоненты URI.
5. Обработка заканчивается при: завершении прохождения всех компонентов URI или при отсутствии у очередного полученного из дерева ресурсов элемента метода "__getitem__(...)" или при получении исключения KeyError или если компонента URI начинается с символов "@@". Символы "@@" указывают на то, что то, что за ними следует следует воспринимать как имя представления.
6. Последний полученный элемент из дерева ресурсов является контекстом, а первый из оставшихся компонентов URI (или пустая строка, если в результате обхода были все компоненты URI использованы) является именем представления.
7. Остальные оставшиеся компоненты URI называются subpath.
Часто встречающая ситуация, когда имя представления является пустой строкой, трактуется в Pyramid так, что программа будет использовать представление по умолчанию (default view).
Предположим, что есть такой URI:
"/foo/bar/baz/biz/buz.txt"
Предположим, что обход по дереву ресурсов выявил, что контекст это“biz”, имя представление это “buz.txt”, а subpath являет собою пустую последовательность.
На этом алгоритм "Traversal" свою работу завершает, а начинает работать алгоритм "view lookup" (поиска представления).
Предположим, что контекст “biz” оказался экземпляром класса Buz в Python, тогда нам нужно будет:
"Найти обработчик (view callable), зарегистрированный под именем buz.txt который может обработать класс Biz"
Отладка непонятных ошибок "404 Not Found" при отработке механизма "Traversal" можно облегчить, включив в ini-файле веб-приложения опцию:
[app:название моего веб-приложения]
debug_notfound = true
Тогда в дополнение к надписи "404 Not Found" на странице и в поток stderr (по умолчанию - в консоль) будет выдаваться нечто вроде:
debug_notfound of url http://127.0.0.1:6543/admin/xchng1c/load; path_info: '/admin/xchng1c/load', context:
Здесь видно, что:
- разбирается URI '/admin/xchng1c/load'
- в качестве контекста выбран ресурс "eshoppython.admin.resources.Xchng1c", являющийся объектом
- в качестве имени представления (view_name) используется "load"
- а subpath - пуст
- алгоритм "Traversal" прошелся по компонентам URI "admin" и "xchng1c"
- фабрикой ресурсов является объект "eshoppython.resources.Root"
- и что виртуальные сервера (виртуальный хостинг) не обрабатываются
Следующий этап - передача управления соответствующему обработчику (или обработчику-представлению, "callable view") для генерации данных, выдаваемых веб-приложением по URI.
Можно продолжать читать тут:
http://faq1c.gorbunov.ru/node/89