barbitoff programmer`s blog

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

четверг, 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 эта самая запись не очевидна).

четверг, 22 декабря 2011 г.

JSP и "multipart/form-data"-формы

"Из коробки" спецификация Servlet API 2.x не поддерживает автоматическое распознавание запросов "multipart/form-data", а, значит, например, Apache Tomcat версии, раньше 7ой, работать с ними также не умеет. В итоге, при передаче файла на JSP страницу среди параметров мы его не найдем. Выход - использовать контейнер сервлетов с поддержкой Servlet API 3.0 или воспользоваться сторонними разработками для работы с такими запросами. Например, Apache Commons FileUpload (Apache Commons FileUpload). Его использование состоит из следующих этапов:
1) В библиотеки добавить jar-ники самого commons-fileupload и commons-io (http://commons.apache.org/io/download_io.cgi).
2) На странице добавить импорты:
<%@ page import ="org.apache.commons.fileupload.*,org.apache.commons.fileupload.disk.*,org.apache.commons.fileupload.servlet.*" %>
3) Теперь в коде странице проверяем, является ли запрос мультипартовым, если да - обрабатываем его с помощью  Apache Commons FileUpload (подробнее про варианты обработки можно почитать тут - http://commons.apache.org/fileupload/using.html):

boolean isMultipart = ServletFileUpload.isMultipartContent(request);
if(isMultipart)
{
// Создаем фабрику (при необходимости можно задать временную директорию и макс. размер файла)
FileItemFactory factory = new DiskFileItemFactory();
// Создаем обработчик
ServletFileUpload upload = new ServletFileUpload(factory);
// Парсим запрос
List /* FileItem */ items = upload.parseRequest(request);
// Обрабатываем результат
Iterator iter = items.iterator();
while (iter.hasNext())
{
FileItem item = (FileItem) iter.next();
if (item.isFormField()) // пропускаем не интересующие нас обычные поля формы
continue;
String fieldName = item.getFieldName();
String fileName = item.getName();
String contentType = item.getContentType();
boolean isInMemory = item.isInMemory();
long sizeInBytes = item.getSize();
System.out.println("fieldName="+fieldName+",fileName="+fileName+",contentType="+contentType+",isInMemory="+isInMemory+",size="+sizeInBytes+"b");
/*
* Здесь делаем, например, item.getInputStream() и работаем с ним.
*/
}
}
else
{
// ...
}