IBM®
Перейти к тексту
    в России и странах СНГ [изменить]    Условия использования
 
 
   
    Главная страница    Продукты    Услуги и решения    Поддержка и загрузка    Мой профиль    
Перейти к тексту

developerWorks Россия  >  SOA и Web-сервисы  >

Преобразователь URL Ranvier

Инициирование работы приложений на основании структуры URL

developerWorks
Опции документа

Загрузить Adobe® Reader®

Опции документа, требующие включения JavaScript, не отображаются

Обсудить


Выскажите мнение об этой странице

Помогите нам улучшить содержание


Уровень сложности: средний

Дэвид Мерц, писатель, Gnosis Software, Inc.

27.04.2009

Ranvier – это написанный на Python пакет, который можно интегрировать в среды разработки Web-приложений для преобразования входящих URL-запросов в исходный код. Преобразование выполняется при помощи механизма delegation-and-consumption, отличающегося от более распространённого способа перезаписи URL на основе регулярных выражений. Ranvier служит также главным реестром всех URL в Web-приложении и может сам генерировать URL, необходимые для связанных перекрёстными ссылками страниц. Функция реестра позволяет Ranvier гарантировать целостность ссылок и автоматизировать анализ покрытия. Ranvier полностью написан на Python и не содержит каких-либо зависимых от сторонних производителей компонентов; он должен работать (с минимальной адаптацией кода) в любой основанной на Python среде разработки Web-приложений.

Предварительные замечания

Назначение унифицированного идентификатора ресурса (Uniform Resource Identifier, URI) - предоставление ресурсу уникального имени. Наиболее известное подмножество URI – унифицированные указатели ресурсов (Uniform Resource Locators, URL), которые также описывают, как обратиться к указанному ресурсу (другими словами, они объединяют в себе сетевой адрес и протокол, необходимый для получения электронного документа или потока). Некоторые URI представляют собой унифицированные имена ресурсов (Uniform Resource Names, URN), не являясь URL, то есть они называют ресурс, но не дают конкретной информации, как к нему обратиться.

Тем не менее существуют некоторые тонкости, связанные с тем, что имеется в виду под URI в каждом конкретном случае. Контекст, в особенности время и место обращения к ресурсу, зачастую изменяет содержимое описываемого ресурса. Например, URI (и URL) file:///etc/hosts описывает разный контент при обращении к этому ресурсу на разных локальных машинах. Если пойти дальше, можно представить себе Web-сервис с возможностью определения географического местоположения, в котором используется URL типа http://weather.example.com/today/local/, обозначающий, что запрашивающий желает получить информацию о погоде в том месте и в то время, где и когда он делает запрос. Для описания этого же ресурса можно использовать URN (не являющийся URL), например, urn:weather:today:local; вероятно, в этом случае определять, какой протокол или механизм использовать для обращения к указанному контенту или для его генерации, будет обрабатывающее запрос приложение.

В принципе URI может иметь любую форму, соответствующую стандарту RFC 3986. Например, можно использовать имя, отвечающее правилам создания универсальных уникальных идентификаторов (Universally Unique Identifiers, UUID), определяемым стандартом RFC 4122. С помощью этой кодировки "местную погоду сегодня" можно обозначить как: urn:uuid:4930fce0-2eda-4f6b-9eaa-fe426fefc655. Или, если требуется формат URL, можно использовать, например, tftp://192.0.2.143/eaecf224-6efc-3499-8cd8-3e50a4f58ec1. Очевидная проблема с вышеприведёнными URI состоит в том, что человеку их довольно сложно запомнить, и они для него не несут никакого смысла. (Машины значительно более безразличны к описательному характеру имен). На практике разработчики и пользователи склонны добавлять в синтаксис URI хоть немного имеющей смысл информации.



В начало


Иерархия и компоненты

Старейшие и самые популярные Web-серверы генерировали URI, напрямую отражающие файловую структуру машины, на которой хранятся ресурсы. Сама схема URI определяет иерархический компонент "пути" URI (хотя путь потенциально пуст), но не требует какого-либо посимвольного преобразования структуры пути URI и файловой системы. Тем не менее Web-серверы обычно просто берут за основу URI относительные имена каталогов. Эта система имеет смысл при организации (относительно) статичных документов, при которой для создания иерархий локальных и сетевых ресурсов применяются одни те же организационные принципы (что в первом приближении и есть первоначальная цель WWW).

В динамических средах разработки Web-приложений иерархии путей URI обычно исключаются из структуры кода, обслуживающего страницы. Так, например, компоненты пути в URI, обслуживаемых, скажем, Rails или Django, указывают относительную вложенность классов и методов, зачастую с добавлением нескольких характерных для конкретной среды соглашений об именах компонентов классов.

Общее у отображений файловой системы и путей иерархии кода то, что URI, которые они предоставляют потребителям ресурса, определяются техническими решениями, использовавшимися при конфигурировании Web-сервера. Особенности кода видны в формате URI. В некоторых случаях эта прозрачность бывает к месту и желаема: в соответствии с одним идеалом, который часто ассоциируется с объектно-ориентированным программированием, структура объектов кода напрямую отражает семантику домена приложения. Однако иногда иерархии, задействованные при создании Web-сервиса, заметно отличаются от "естественных" иерархий самого домена. В этих случаях потребители ресурсов должны довольствоваться URL, являющимися именами ресурсов, к которым они хотят обратиться, и не описывающими ресурс. По меньшей мере эти URI не описывают свои ресурсы так же хорошо, как было бы возможно, если изначально рассматривать процесс с точки зрения потребителя, а не разработчика.

Замена регулярных выражений

Наиболее распространённые инструменты преобразования семантически обусловленных URI во внутренние просто производят замены на основе регулярных выражений. Самым широко известным примером такого подхода является mod_rewrite в Apache. Аналогично работает и Routes для Rails. При этом подходе необходим внутренний набор URI, возможно, таких, формат которых обусловлен техническими решениями, использовавшимися при создания сайта; также нужно понимать, что представляют собой значащие URI. При перезаписи URI на основе регулярных выражений каждый перезаписанный URI имеет по крайней мере одного значащего двойника. Это значит, что "публичные" URI преобразуются Web-сервером в "приватные". Однако приватные URI не обязательно (и далеко не всегда) защищаются от общего доступа; обычно для обеспечения доступа к вашим ресурсам публикуются как раз публичные версии URI.

Простым и распространённым примером перезаписи URL может служить преобразование основанных на пути описаний ресурсов в вызовы FastCGI (или просто CGI). При этом путь может реально и не существовать в файловой системе Web-сервера; чтобы смоделировать "реальный" путь, правило подстановки может вызывать скрипт, возвращающий "виртуальный" путь (виртуальный по отношению к файловой системе сервера, то есть все компоненты пути URI фактически равноценны как компоненты ресурсов). Пример, приведенный в The Django Book, показывает, как настроить Apache так, чтобы он возвращал любой файл, существующий в файловой системе, и выполнял делегирование в том случае, если его не существует. В демо-версии Ranvier на основе CGI, размещённой на его Web-странице, используется аналогичное правило; то же самое на своей тестовой системе делаю и я:


Apache mod_rewrite: делегирование пути, если файл не существует
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^/(.*)$ /mysite.fcgi/$1 [QSA,L]



В начало


Чем отличается Ranvier

Вместо того чтобы с помощью регулярных выражений преобразовывать публичные URI в приватные, в Ranvier реализован конструктивный шаблон "цепочка обязанностей" (chain of responsibility), делегирующий определение ресурсов соответствующему коду. В данном случае цель состоит главным образом в том, чтобы обеспечить возможность многократного использования значащего кода: каждый компонент пути инициирует отсылку соответствующему обработчику кода. В свою очередь, обработчик использует от нуля до нескольких компонентов пути, выполняет всю необходимую обработку и может модифицировать объект контекста, в котором содержится состояние обработки. Так как при обработке различных URI может передаваться один и тот же обработчик, любую общую обязанность легко закодировать в одном обработчике.

Проиллюстрируем эту цепочку примером. В демо-версии Ranvier приводится пример обработки имён пользователей, содержащихся в пути. Например, URI http://furius.ca/ranvier/demo/users/mertz/name возвращает описание моей "учётной записи пользователя" (несмотря на то, что моё имя не содержалось в этой демонстрационной программе). Данный пример всего лишь переписывает с заглавной буквы мою фамилию и выдаёт соответствующее сообщение, однако он наглядно объясняет, как можно использовать URI для извлечения информации об учётной записи произвольного пользователя из базы данных, или, в ином случае, для выполнения некоторых вычислений в процессе распаковки URI.

Центральным элементом приложения Ranvier является функция create_application(), определяющая объект root. Объект root часто (как и в демо-версии) является экземпляром класса Folder, который инициализируется с набором аргументов -- ключевых слов, указывающих на вложенные компоненты пути URI. Поясним это на примере кода. Ниже приведён сокращённый фрагмент файла demoapp.py из дистрибутива Ranvier:


Определение корневого дерева URI в Ranvier
root = Folder(
    _default='home',
    home=Home(),
    resources=EnumResource(mapper),
    users=UsernameRoot(Folder(username=PrintUsername(),
                              name=PrintName(),
                              data=UserData())),
)

Относительный URI /home определяется классом Home(LeafResource), определяемым в demoapp.py. Коротко говоря, это просто способ написания расширенного шаблона для запросчика. Помимо всего прочего, в этот шаблон входят вычисленные URI типа mapurl("@@Home"). После того, как Ranvier обработает этот URI, он будет выглядеть как http://mysite.com/home/. Другие URI вычисляются аналогично. Класс EnumResources встроен в Ranvier и доступен в вышеприведённом описании в /resources.

Более интересно определение компонента пути /users. Оно определяется классом UsernameRoot(VarDelegatorResource). Вот его код:


Класс обработчика VarDelegatorResource
class UsernameRoot(VarDelegatorResource):
    def __init__(self, next, **kwds):
        VarDelegatorResource.__init__(self, 'username', next, **kwds)

    def handle(self, ctxt):
        if not re.match('[a-z]+$', ctxt.username):
            return ctxt.response.errorNotFound()
        ctxt.name = 'Mr or Mrs %s' % ctxt.username.capitalize()

Атрибут .username присваивается объекту контекста в методе .__init__() этого класса, который также использует следующий компонент пути. Атрибут ctxt.username используется в методе .handle() этого класса, в основном для подсчёта другого контекстного атрибута ctxt.name (в нашем примеры выполняются несложные расчёты, но представьте себе доступ к базе данных).

Обратите внимание, что задача UsernameRoot в основном состоит в том, чтобы содержать ещё один Folder. Вложенный элемент folder содержит компоненты пути username, name и data. В результате можно использовать пути URI типа /users/mertz/username, /users/blais/data/keyname и /users/smith/name. Взгляните в свою очередь на класс PrintName, которому передаются полномочия после того, как компоненты /users/username используются UsernameRoot:


Класс обработчика LeafResource
class PrintName(LeafResource):
    def handle(self, ctxt):
        ctxt.page.render_header(ctxt)
        ctxt.response.write('''<p>The user\'s name is %(name)s.</p>''' %
                            {'name': ctxt.name})
        ctxt.page.render_footer(ctxt)

Из всего, что делает PrintName, стоит упомянуть лишь использование атрибута контекста ctxt.name. Вероятно, при интеграции с полноценной средой разработки Web-приложений это значение можно использовать в языке общих шаблонов, а не в упрощённом шаблоне, применяемом в демо-версии.

Этот краткий фрагмент из демо-версии показывает лишь немногое из того, что умеет делать Ranvier. Вопросы обратного преобразования из обработчиков в URI затрагиваются здесь лишь вскользь.



В начало


Вопросы концепции

Хотя мне нравится элегантный код, написанный моим коллегой Мартином Блейсом (Martin Blais), создателем Ranvier, я считаю, что в общем замысле преобразования URI с помощью Ranvier имеется концептуальный недостаток. Проблема, в частности, состоит в несоответствии между форматом URI по стандарту RFC 3986 и более "популярным" добавлением к компоненту пути URI.

Позвольте мне привести пример из документации по Ranvier. Мартин упоминает "наивный URI" следующего вида:

http://example.com/get_balance?user=blais&acc=64

Этот URI можно реализовать с помощью такой функции:


def get_balance(user, accno):
    "Lookup balance based on user and account number"

С помощью Ranvier можно легко создавать различные варианты этого URI, в которых все компоненты пути могут служить аргументами, например:

http://example.com/user/blais/acc/64/get_balance/

Комментарии Мартина в документации по альтернативным URI:

В результате мы получаем более аккуратные URL и стимулируем пользователя иерархически и в соответствии с определённой концепцией организовывать ресурсы, которые он делает доступными на своём сервере. Аргументы запросов лучше всего зарезервировать для дополнительных аргументов.
Поделиться...

digg Разместить на Digg
del.icio.us Разместить на del.icio.us
Slashdot Разместить на Slashdot!

Однако, по моему мнению, суть трансформации формата URI состоит не в разделении дополнительных и обязательных аргументов в соответствии с какой-то концепцией. В конце концов, обработчик в Ranvier вполне в состоянии использовать меняющееся количество (дополнительных) компонентов пути. В действительности разница между компонентами пути и параметрами запросов сводится к отличию иерархических и неупорядоченных аргументов. По большей части, этот тип выполняемого Ranvier преобразования URI просто позволяет встраивать неиерархические аргументы в иерархический путь. С синтаксической точки зрения это выражается в том, что предпочтение отдаётся слэшам, а не амперсандам, знакам равенства и вопросительным знакам. Само по себе стилистическое предпочтение тех или иных символов в общем нейтрально, пока вы не поймёте, что в стандарте RFC 3986 уже разграничены иерархические и неупорядоченные аргументы пути и части запроса URI. Зачем втискивать одну определённую спецификацией часть в другую? Тот факт, что многие неправильно понимают это различие, не кажется мне достаточной причиной.

Несмотря на это, есть множество случаев, когда ресурсы домена обладают семантической иерархией, и не только в том смысле, что они отражают особенности среды разработки и инструментов, использовавшихся при реализации. Ranvier предоставляет гибкий способ организации передачи функциональных аспектов обработки URI множеству многократно использующихся фрагментов кода.



Ресурсы

Научиться

Получить продукты и технологии

Обсудить


Об авторе

Дэвид Мерц (David Mertz) - большой знаток в области открытых стандартов и только умеренно пугает многословием. С Дэвидом можно связаться по mertz@gnosis.cx его жизнь описывается более подробно на http://gnosis.cx/dW/. Предложения и комментарии по этой, предыдущей или будущей статьям приветствуются. Можете также посмотреть книгу Дэвида Text Processing in Python.




Выскажите мнение об этой странице


Пожалуйста, найдите минутку и заполните форму, чтобы повысить уровень сервиса.



 


 


 


Поделиться этой статьей:

забобрить забобрить memori сохранить в memori




В начало


IBM обладает всеми авторскими правами касательно информации, расположенной на developerWorks. Использование информации приведенной на этом ресурсе без явного письменного разрешения от IBM или первоначального автора запрещены. Если Вы желаете использовать информацию с developerWorks, пожалуйста воспользуйтесь регистрационной формой для того, чтобы связаться с нами запрос на использование материалов developerWorks Россия.
    IBM в России Конфиденциальность Контакты