Статьи

Реализация URL без расширений с помощью Tapestry, Spring MVC, Struts 2 и JSF

За последние пару недель я провел несколько вечерних часов, внедряя URL-адреса без расширений в AppFuse . Я хотел сделать это с тех пор, как написал о том, как это сделать несколько лет назад. В этой статье подробно описан мой опыт, и, надеюсь, мы поможем другим реализовать эту функцию в своих веб-приложениях.

Прежде всего, я использовал UrlRewriteFilter , один из моих любимых проектов с открытым исходным кодом на Java. Затем я последовал шаблону, который я нашел в образце Spring mvc-basic из MVC Упрощения в Spring 3.0 . С тех пор приложение изменилось (поскольку SpringSource интегрировал функциональность типа UrlRewriteFilter в Spring MVC), но шаблон в основном соответствовал пути, а не отображению расширения. То есть «диспетчер» для веб-фреймворка был сопоставлен с / app / * вместо * .html.

До перехода на URL без расширений AppFuse использовал * .html для своего отображения, и это, казалось, вызывало проблемы у пользователей, когда они хотели обслуживать статические HTML-файлы. Для начала я удалил все расширения из URL в тестах ( Canoo WebTest используется для тестирования пользовательского интерфейса). Я также сделал это для любых ссылок на страницах просмотра и перенаправлений в коде Java. Это обеспечило достойную основу для проверки моих изменений. Ниже приведены подробности о каждой платформе, для которой я сделал это, начиная с самой простой и переходя к самой сложной.

Гобелен 5
Гобелен был безусловно самым простым для интеграции URL без расширений. Это потому, что это встроенная функция фреймворка, которая уже была интегрирована как часть реализации Tapestry 5 Сержа Эби . В конце концов, единственное, что мне нужно было сделать, это: 1) добавить пару записей для CXF (сопоставленных с / services / *) и DWR (/ dwr / *) в мои urlrewrite.xml и 2) изменить UrlRewriteFilter так, чтобы это было отображается только на ЗАПРОС, а не ЗАПРОС и ВПЕРЕД. Ниже приведены сопоставления, которые я добавил для CXF и DWR.

<urlrewrite default-match-type="wildcard">
...
<rule>
<from>/dwr/**</from>
<to>/dwr/$1</to>
</rule>
<rule>
<from>/services/**</from>
<to>/services/$1</to>
</rule>
</urlrewrite>

Spring MVC У
меня был достаточный опыт работы с Spring MVC и URL без расширений. Оба приложения Spring MVC, которые мы разработали в прошлом году в Time Warner Cable, использовали их. Изменить отображение * .html на / app / * было довольно просто, и потребовалось удалить больше кода, чем я добавил. Ранее у меня был StaticFilter, который искал HTML-файлы, и если он не нашел их, он отправлялся в Spring DispatcherServlet. Мне удалось удалить этот класс и сделать файл web.xml немного чище.

Чтобы UrlRewriteFilter и Spring Security работали вместе, мне пришлось переместить securityFilter, чтобы он появился после rewriteFilter, и добавить диспетчер INCLUDE, чтобы у включенных JSP был контекст безопасности.

<filter-mapping>
<filter-name>rewriteFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>securityFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>INCLUDE</dispatcher>
</filter-mapping>

Единственными другими вещами, которые мне пришлось изменить, были security.xml и dispatcher-servlet.xml для удаления расширений .html. Файл urlrewrite.xml был довольно простым. Внизу я использовал следующую информацию для отправки в Spring MVC.

<rule>
<from>/**</from>
<to>/app/$1</to>
</rule>
<outbound-rule>
<from>/app/**</from>
<to>/$1</to>
</outbound-rule>

Затем я добавил ряд других правил для j_security_check, DWR, CXF и статических ресурсов (/ images, / scripts, / styles, /favicon.ico). Вы можете просмотреть текущий urlrewrite.xml в FishEye . Единственная серьезная проблема, с которой я столкнулся, заключалась в том, что Spring Security записывал защищенные URL-адреса как / app / URL, поэтому мне пришлось добавить правило для перенаправления, когда это произошло после входа в систему. 

<rule>
<from>/app/**</from>
<to last="true" type="redirect">%{context-path}/$1</to>
</rule>

Struts 2
Использование расширенных URL-адресов в Struts 2, вероятно, довольно просто благодаря плагину Convention . Несмотря на то, что этот плагин включен в AppFuse, он не настроен с правильными константами, и у меня есть struts.convention.action.disableScanning = true в struts.xml. Похоже, я должен был сделать это, когда я обновил Struts 2.0.x до Struts 2.1.6 . Это правда, что поддержка AppFuse в Struts 2 могла бы использовать немного любви, чтобы соответствовать рекомендациям Struts 2, но я не хотел тратить время на это как часть этого упражнения.

В Struts 2 я попытался сопоставить пути, как в Spring MVC, но столкнулся с проблемами. Вместо этого я решил использовать расширение «.action», изменив struts.action.extension с «html» на «action» в struts.xml. Затем мне пришлось сделать кучу переупорядочивания фильтров и изменений диспетчера. Ранее с расширением .html все фильтры были сопоставлены с / * и в следующем порядке.

Имя фильтра диспетчеры
securityFilter запрос
rewriteFilter запрос, переслать
распорки-подготовка запрос
SiteMesh запрос, переслать, включить
staticFilter запрос, переслать
распорки запрос

Как и в Spring MVC, мне пришлось удалить rewriteFilter перед securityFilter, и я смог удалить staticFilter. Мне также пришлось сопоставить фильтр Struts с * .action вместо / *, чтобы Struts не пытался перехватить статический актив и запросы DWR / CXF. Ниже приведен порядок фильтров и их диспетчеров, который работает лучше всего.

Имя фильтра диспетчеры
rewriteFilter запрос
securityFilter запрос, переслать, включить
распорки-подготовка запрос, переслать
SiteMesh запрос, переслать, включить
распорки вперед

После этого нужно было изменить urlrewrite.xml, чтобы он имел следующие универсальные правила и правила для статических ресурсов, j_security_check и DWR / CXF.

<rule match-type="regex">
<from>^([^?]*)/([^?/\.]+)(\?.*)?$</from>
<to last="true">$1/$2.action$3</to>
</rule>
<outbound-rule match-type="regex">
<from>^(.*)\.action(\?.*)?$</from>
<to last="false">$1$2</to>
</outbound-rule>

JSF
JSF, безусловно, было самым трудным для работы с URL без расширений. Я не уверен, что это невозможно, но я потратил несколько часов на несколько дней и не смог полностью их удалить. Я смог заставить все работать так, чтобы я мог запрашивать страницы без расширения, но обнаружил, что при нажатии кнопок и ссылок расширение часто показывалось в URL. Я также все еще использую JSF 1.2, поэтому возможно, что обновление до 2.0 решит многие проблемы, с которыми я столкнулся.

В настоящее время я изменил свое отображение FacesServlet с * .html на * .jsf. Как и в случае с Struts, у меня были проблемы при попытке сопоставить его с / app / *. Другие изменения включают в себя изменение порядка диспетчеров и фильтров , полезную информацию в urlrewrite.xml и изменение security.xml . По какой-то причине я не смог заставить загрузку файлов работать без добавления исключения из правила для исходящих запросов.

<rule match-type="regex">
<from>^([^?]*)/([^?/\.]+)(\?.*)?$</from>
<to last="true">$1/$2.jsf</to>
</rule>
<outbound-rule match-type="regex">
<!-- TODO: Figure out how to make file upload work w/o using *.jsf -->
<condition type="path-info">selectFile</condition>
<from>^(.*)\.jsf(\?.*)?$</from>
<to last="false">$1$2</to>
</outbound-rule>

Я также провел пару часов, пытаясь заставить Pretty Faces работать. Я писал о своих проблемах на форумах . Я попытался написать собственный процессор для удаления расширения, но обнаружил, что попаду в бесконечный цикл, в котором процессор продолжает вызываться. Чтобы обойти это, я попытался использовать SpringCon RequestContextHolder, чтобы гарантировать, что процессор вызывался только один раз, но это оказалось бесплодным. Наконец, я попробовал входящие и исходящие пользовательские процессоры, но не смог заставить их работать. Последнее, что я попробовал, было отображение URL для каждой страницы в pretty-config.xml. 

<url-mapping>
<pattern value="/admin/users"/>
<view-id value="/admin/users.jsf"/>
</url-mapping>
<url-mapping>
<pattern value="/mainMenu"/>
<view-id value="/mainMenu.jsf"/>
</url-mapping>

Проблема с этим заключалась в том, что некоторые из правил навигации в моем face-config.xml перестали работать. Я не тратил много времени на диагностику проблемы, потому что мне не нравилось добавлять записи для каждой страницы в приложении. Одна приятная вещь в Pretty Faces — она ​​позволяла мне делать что-то вроде следующего, что я раньше делал с формой, которая автоматически отправлялась при загрузке страницы. 

<url-mapping>
<pattern value="/passwordHint/#{username}"/>
<view-id value="/passwordHint.jsf"/>
<action>#{passwordHint.execute}</action>
</url-mapping>

Заключение
Мое путешествие по внедрению URL без расширений было интересным, и я укрепил свои знания о порядке фильтров, диспетчеров и UrlRewriteFilter. Я все еще думаю, что мне нужно больше узнать о правильной реализации URL-адресов без расширений в Struts 2 и JSF, и я надеюсь сделать это в ближайшем будущем. Я верю, что плагин Struts Convention поможет мне, и надеюсь, что JSF 2 + Pretty Faces тоже будут работать хорошо. Конечно, было бы замечательно, если бы все Java Web Frameworks имели простой механизм для создания и потребления URL без расширений. В то же время, слава Богу за UrlRewriteFilter.

Если вы хотите попробовать AppFuse и его новые блестящие URL-адреса, обратитесь к
Руководству по быстрому запуску и выберите версию 2.1.0-SNAPSHOT.

От http://raibledesigns.com/rd/entry/implementing_extensionless_urls_with_tapestry