barbitoff programmer`s blog

Здесь я публикую заметки из программерской жизни: грабли, на которые мне случилось наступить, проблемы, для которых было найдено элегантное (или не очень) решение, а также все, с чем мне пришлось столкнуться и чем хотелось бы поделиться =)
PS Если хотите меня поблагодарить - на странице есть 3 места, чтобы это сделать =)

четверг, 19 июля 2012 г.

Повторный вызов HttpServletResponse.sendRedirect() в веб-приложении на Tomcat 6

Задача:

Попытка повторного вызова HttpServletReponse.sendRedirect() в веб-приложении, развернутом на Tomcat 6, приводит к исключению:
java.lang.IllegalStateException: org.apache.catalina.connector.ResponseFacade.sendRedirect(ResponseFacade.java:435)
Необходимо реализовать возможность вызывать  sendRedirect() несколько раз, так, чтобы реальный редирект осуществлялся по URL`у, указанному в последнем вызове.

Решение:

Catalina не позволяет вызывать sendRedirect() повторно. Всё же, порой это бывает необходимо сделать. Например, пусть есть некоторый фильтр, который, после того, как запрос пройдет обработку во всех нижлежащих фильтрах, по какому-либо условию выполняет response.sendRedirect():
chain.doFilter(request, response);
if(...)
     ((HttpServletResponse)response).sendRedirect(...)
Однако, если в каком-либо из нижлежащих фильтров или в конечном сервлете, обрабатывающем запрос, также вызывается response.sendRedirect(), повторный его вызов в нашем фильтре вызовет вышеуказанное исключение, в то врем как редирект в фильтре всё же  делать надо, причем так, чтобы он переопределял URL, указанный в предыдущих вызовах sendRedirect().
Вариант, который пришел мне на ум и был реализован - перед передачей дальше по цепочке завернуть передаваемый фильтру response в некоторый объект-делегатор, который будет делегировать все вызовы оригинальному объекту ответа, кроме вызовов sendRedirect. Последний будет просто запоминать переданный ему URL, а реальный редирект будет выполняться только по вызову нового метода applyRedirect:
public class DeferredRedirectHttpServletResponse implements HttpServletResponse
{
  protected HttpServletResponse responseDelegate;

  protected String deferredRedirectURL = null;

  public DeferredRedirectHttpServletResponse(HttpServletResponse responseDelegate)
  {
    this.responseDelegate = responseDelegate;
  }
  @Override
  public void sendRedirect(String string) throws IOException {
    deferredRedirectURL = string;
  }
  public void applyRedirect() throws IOException
  {
    if(this.deferredRedirectURL != null)
      responseDelegate.sendRedirect(this.deferredRedirectURL);  
  }
...
/* здесь идут все методы из интерфейса HttpServletResponse, вызов которых делегируется объекту responseDelegate*/
...
}
Теперь код фильтра примет вид:
DeferredRedirectHttpServletResponse responseDelegator = new DeferredRedirectHttpServletResponse((HttpServletResponse)response);
chain.doFilter(request, responseDelegator);
if(...)
     responseDelegator.sendRedirect(...);
responseDelegator.applyRedirect();
Всё, теперь приложение работает корректно при всех условиях: и когда фильтр делает редирект (даже если он уже был сделан в сервлете / нижлежащем фильтре), и когда он его не делает.

UPDATE. Пожалуй, удобнее было бы наследовать HttpServletResponseWrapper, чем реализовывать HttpServletResponse, кода было бы меньше. Но на момент реализации моего DeferredRedirectHttpServletResponse я про HttpServletResponseWrapper не знал. Наткнулся на него, лазя по исходникам Spring Security, в частности, глядя на класс org.springframework.security.web.firewall.FirewalledResponse.
UPDATE 2. Кстати, если конечный обработчик запроса - jsp-страница, то тут есть одна особенность. Если без использования DeferredRedirectHttpServletResponse вызвать на jsp-хе response.sendRedirect() без следующего за ним return`а прокатывает (ответ просто игнорирует последующую запись в него jsp-страницей), то теперь валится тот же IllegalStateException, на этот раз уже из-за того, что отправка ответа пользователю на момент вызова applyRedirect() уже началась (с jsp-хи вовремя не return`ались, поэтому она уже успела записать в response свой вывод). В общем-то, вышесказанное относится не только к jsp-хам, а к любому коду, где в response идет запись после вызова sendRedirect() (просто на jsp эта самая запись не очевидна).

Комментариев нет:

Отправить комментарий