Статьи

Пользовательские страницы ошибок для просроченных разговоров с участием CDI и JSF

Прошло много времени с тех пор, как я последний раз писал в блоге. Я продолжаю думать о ведении блога на что-то техническое, но в итоге занимаюсь другими вещами. На прошлой неделе было очень интересное обсуждение на форумах Coderanch . Это было еще интереснее, потому что в нем участвовал JBoss :).

Разработчики, знакомые с веб-приложениями Java EE, знали бы, что дескриптор развертывания веб-приложения (web.xml) позволяет указывать «страницы ошибок», которые контейнер будет отображать при появлении определенного исключения (класс ) или код ошибки выдается сервером для веб-запроса. Вот быстрый пример того, как это выглядит:

<web-app>  
   ...  
   <!-- A custom error page for error code == 500 -->  
   <error-page>   
     <error-code>500</error-code>   
     <location>/my-foo-bar-500-page.html</location>   
   </error-page>   
     
   <!-- A custom error page for exception type org.myapp.foo.bar.MyException -->  
   <error-page>   
     <exception-type>org.myapp.foo.bar.MyException</exception-type>   
     <location>/my-foo-bar-exception-page.html</location>   
   </error-page>   
   ...  
     
 </web-app>  

Достаточно просто — пара пользовательских страниц ошибок, определенных для конкретного кода ошибки и типа исключения соответственно. Все это прекрасно работает.

В настоящее время при разработке веб-приложений появляется все больше и больше моделей и структур программирования. CDI и JSF — некоторые из них. CDI имеет эту концепцию областей (например: область запроса, область сеанса, область приложения, область диалога). Мы не будем вдаваться в подробности того, что это такое и когда они используются, но давайте рассмотрим предмет разговора в этом блоге, поскольку именно об этом шла речь в ветке форума, которая вызвала этот блог.

Таким образом, CDI позволяет нескольким запросам быть частью «области диалога». Разговор имеет «начало» и «конец», оба из которых могут управляться приложением. Когда приложение использует JSF, любой диалог (id) автоматически распространяется на запрос (ы) JSF. Помимо явного начала / конца разграничения разговоров, разговор также может быть прерван. Запрос, который относится к разговору, который закончился или истек тайм-аут, приведет к исключению.

Таким образом, мы знаем, что у нас есть немного предыстории для обсуждения CDI. Итак, давайте рассмотрим случай, когда приложение хочет представить красиво выглядящую страницу, когда выдается исключение «разговора больше нет» (возможно, из-за тайм-аута). Мы видели, как написать файл web.xml для конфигураций страниц с ошибками — это было бы так просто:

<web-app>  
   ...  
     
   <!-- A custom error page for exception type org.jboss.weld.context.NonexistentConversationException -->  
   <error-page>   
     <exception-type>org.jboss.weld.context.NonexistentConversationException</exception-type>   
     <location>/my-foo-bar-exception-page.html</location>   
   </error-page>   
   ...  
     
 </web-app>

Достаточно просто. Org.jboss.weld.context.NonexistentConversationException — это тип класса исключения, который генерируется по истечении времени ожидания диалога (обратите внимание, что мы предполагаем, что веб-приложение полагается на Weld в качестве библиотеки реализации спецификации CDI). Вышеуказанная конфигурация работает отлично. My-foo-bar-exception-page.html отображается, когда генерируется исключение org.jboss.weld.context.NonexistentConversationException.

НО, давайте теперь рассмотрим, что мы хотим включить JSF на страницу ошибок, как и другие части нашего приложения. Итак, давайте укажем страницу ошибок на шаблон URL, который отображается на сервлет JSF:

<web-app>  
   ...  
     
   <!-- A custom error page for exception type org.jboss.weld.context.NonexistentConversationException -->  
   <error-page>   
     <exception-type>org.jboss.weld.context.NonexistentConversationException</exception-type>   
     <location>/my-foo-bar-exception-page.xhtml</location>   
   </error-page>   
   ...  
     
 </web-app> 

Обратите внимание, что мы изменили отображение страницы ошибки на my-foo-bar-exception-page.xhtml (обратите внимание на расширение xhtml) из my-foo-bar-exception-page.html. Мы снова предположим, что ресурсы .xhtml сопоставлены с сервлетом JSF, поэтому эти запросы рассматриваются как запросы JSF.

С этим изменением файла web.xml вы заметите, что my-foo-bar-exception-page.xhtml больше не будет отображаться, если вы видите большую трассировку стека с неоднократно отображаемым исключением org.jboss.weld.context.NonexistentConversationException в трассировке стека, создавая впечатление, что конфигурация страницы с ошибками пошла не так.

Так что же мы сделали не так? Хорошо, помните, что ранее я упоминал, что для запросов JSF идентификатор разговора распространяется автоматически. Это именно то, что здесь происходит. Сервер замечает исключение org.jboss.weld.context.NonexistentConversationException, а затем пытается отобразить страницу с ошибкой, которая поддерживается JSF, и, поскольку идентификатор диалога распространяется, сервер пытается найти этот несуществующий диалог и завершается с ошибкой то же самое org.jboss.weld.context.NonexistentConversationException и в конечном итоге не может отобразить страницу ошибки. Это все равно что ходить по кругу, чтобы отобразить эту страницу с ошибкой.

Так как же пройти через это? Оставляя в стороне все технические детали, очевидной вещью было бы не распространять несуществующий идентификатор разговора при отображении страницы ошибок (при поддержке JSF). Вот что мы собираемся сделать. CDI 1.1 (и Weld 1.1.2 и более поздние версии) позволяет вам явно указать, что разговор не должен распространяться для определенного запроса. Вы можете сделать это, передав параметр nocid в этом запросе. Имея это знание, давайте теперь изменим наш web.xml, чтобы внести необходимые изменения, чтобы наша страница ошибок отображалась правильно:

<web-app>  
   ...  
     
   <!-- A custom error page for exception type org.jboss.weld.context.NonexistentConversationException.  
     Notice the "nocid" parameter being passed to make sure that the non-existent conversation id  
     isn't passed to the error page  
   -->  
   <error-page>   
     <exception-type>org.jboss.weld.context.NonexistentConversationException</exception-type>   
     <location>/my-foo-bar-exception-page.xhtml?nocid=true</location>   
   </error-page>   
   ...  
     
 </web-app>  

Обратите внимание, что мы передаем параметр «nocid» как часть строки запроса местоположения страницы ошибки. Значение параметра «nocid» на самом деле не имеет значения, но для сохранения логичности этого значения мы использовали здесь значение «true». Как только это изменение будет сделано, вы начнете замечать, что страница с ошибкой (поддерживаемая JSF) теперь отображается правильно!

Нам потребовалось некоторое время, чтобы найти это решение в этой ветке форума, потому что оно выглядело так просто, что должно было «просто сработать », но это не сработало. Вот ветка форума на coderanch, о которой я говорил. Заслуга Грег Чарльз для выяснения того , как передать этот nocid параметр.