[an error occurred while processing this directive] IT • archiv :: Print

IT • archiv


[an error occurred while processing this directive]

[an error occurred while processing this directive]

Код фильтров в новой модели Servlet 2.3

[an error occurred while processing this directive](none) [an error occurred while processing this directive](none)[an error occurred while processing this directive] ::
[an error occurred while processing this directive](none)
[an error occurred while processing this directive]([an error occurred while processing this directive] Джейсон Хантер [an error occurred while processing this directive])

[an error occurred while processing this directive](none)

Откройте для себя возможности сервлетных фильтров.

Server-Side Java
PDF versionPDF версия
Обзор
В целях исследования нескольких свободно предоставленных фильтров Джейсон Хантер решил подробнее рассмотреть новую модель сервлетных фильтров. Вы узнаете, как эти фильтры работают, и что с их помощью можно делать. В качестве примера их использования Хантер приводит свой собственный фильтр для многозадачных запросов, который позволяет упростить управление процессом загрузки файлов. (3200 слов)

В статье "Servlet 2.3: Новые возможности раскрыты" читатели могут познакомиться с изменениями, внесёнными в Servlet API 2.3, и кратким описанием новой модели сервлетных фильтров. В этой статье мы более детально рассмотрим сами фильтры и ознакомимся с фильтрами, которые можно бесплатно скачать из Сети. Я подробно расскажу, что выполняет каждый из фильтров, как он работает, и где его можно найти.

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

Сервлетный фильтр

Если вы не знакомы с термином «фильтр», поясняю, что это объект, способный трансформировать запрос или видоизменять ответ. Фильтр — это не сервлет, сам он не создаёт ответ. Он занимается предварительной обработкой запроса, прежде чем тот попадает в сервлет, и/или последующей обработкой ответа, исходящего из сервлета. Как вы потом увидите из примеров, фильтры могут:

Вы можете конфигурировать фильтр так, чтобы он работал с сервлетом или группой сервлетов по вашему усмотрению. Фильтр может фильтровать один и более сервлетов. Фильтр реализует javax.servlet.Filter и определяет три метода:

  1. void init(FilterConfig config) throws ServletException: вызывается прежде, чем фильтр начинает работать, и настраивает конфигурационный объект фильтра
  2. void destroy(): вызывается после того как фильтр закончил работу
  3. void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException: выполняет непосредственно работу фильтра.

Сервер вызывает init(FilterConfig) один раз, чтобы запустить фильтр в работу, а затем вызывает doFilter() столько раз, сколько запросов будет сделано непосредственно к данному фильтру. Интерфейс FilterConfig содержит метод для получения имени фильтра, его параметров инициации ( init ) и контекста активного в данный момент сервлета. Сервер вызывает destroy(), чтобы сообщить, что фильтр отключен. Жизненный цикл фильтра очень похож на жизненный цикл сервлета — изменение, сделанное в последней версии Servlet API 2.3 Public Final Draft #2. Ранее в жизненном цикле принимал участие метод setFilterConfig(FilterConfig).

С помощью своего метода doFilter() каждый фильтр получает текущий запрос и ответ, а также FilterChain, содержащий список фильтров, предназначенных для обработки. В doFilter() фильтр может делать с запросом и ответом всё, что ему захочется (может собирать данные, вызывая из методы, или упаковывать эти объекты для придания им нового поведения, что мы обсудим попозже). Затем фильтр вызывает chain.doFilter(), чтобы передать управление следующему фильтру. После возвращения этого вызова фильтр может по окончании работы своего метода doFilter() выполнить дополнительную работу над полученным ответом. К примеру, сохранить регистрационную информацию об этом ответе. Если фильтр приостановить обработку запроса и получить всю полноту управления над ответом, он может не вызывать следующий фильтр.

Определить, где тормозит

Чтобы лучше понять сущность фильтров, нужно проследить за тем, как они работают. Первый фильтр, который мы рассмотрим, достаточно простой, но при этом мощный. Он смоделирован более свободно в отличие от пространно названного ExampleFilter, включенного в пакет Tomcat 4.0. Его код выглядит следующим образом:

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class TimerFilter implements Filter {

  private FilterConfig config = null;

  public void init(FilterConfig config) throws ServletException {
    this.config = config;
  }

  public void destroy() {
    config = null;
  }

  public void doFilter(ServletRequest request, ServletResponse response,
                FilterChain chain) throws IOException, ServletException {
    long before = System.currentTimeMillis();
    chain.doFilter(request, response);
    long after = System.currentTimeMillis();

    String name = "";
    if (request instanceof HttpServletRequest) {
      name = ((HttpServletRequest)request).getRequestURI();
    }
    config.getServletContext().log(name + ": " + (after — before) +
"ms");
  }
}

Когда сервер вызывает init(), фильтр сохраняет ссылку на config в своей config — переменной, которая позже используется методом doFilter() для получения ServletContext. Когда сервер вызывает doFilter(), фильтр определяет продолжительность обработки запроса и при завершении обработки сохраняет регистрационную запись о времени. Данный фильтр хорошо показывает, как выполняется предварительная и последующая обработки запроса. Обратите внимание на то, что параметры для метода doFilter() не являются HTTP-совместимыми объектами, а значит, для вызова HTTP-метода getRequestURI() требуется завернуть запрос в HttpServletRequest -тип.

Чтобы воспользоваться этим фильтром, вы должны задекларировать его в описании применения web.xml (deployment descriptor) с помощью метки <filter>, как это показано в примере ниже:

<filter>
    <filter-name>timerFilter</filter-name>
    <filter-class>TimerFilter</filter-class>
</filter>

This notifies the server that a filter named timerFilter is implemented in the TimerFilter class. You can apply a registered filter to certain URL patterns or servlet names using the <filter-mapping> tag:

<filter-mapping>
    <filter-name>timerFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

Таким образом, мы конфигурируем фильтр для работы со всеми запросами к серверу (статичными или динамичными), чтобы он засекал продолжительность обработки. Если вы заходите на простую страницу, выходные данные входа могут выглядеть так:

2001-05-25 00:14:11 /timer/: 10ms

В Tomcat 4.0 beta 5, log-файл находится под server_root/logs/.

Скачать WAR-файл для этого фильтра можно с http://www.javaworld.com/jw-06-2001/Filters/timer.war.

Кто заходит на ваш сайт, и что он там делает?

Наш следующий фильтр clickstream создат парнями из OpenSymphony. Этот фильтр отслеживает пользовательские запросы (т.е. маршрут перемещения пользователя в вашей web-среде) и последовательности запросов (т.е. выбор маршрутов), чтобы администратор имел представление о том, кто заходит на сайт, и какие страницы к данному моменту пользователь посетил. Речь идет об открытой библиотеке на условиях лицензии LGPL.

В библиотеке для clickstream вы найдёте класс ClickstreamFilter, который отлавливает информацию запроса, класс Clickstream, который функционирует как хранилище данных, и класс ClickstreamLogger, который следит за сессиями и контекстуальными событиями, чтобы связывать всё воедино. Здесь также есть класс BotChecker, определяющий является ли клиент роботом (используя простую логику "Запросили ли они файл robots.txt?"). Для просмотра данных в библиотеке предусмотрена страница сводных данных о посетителях — viewstream.jsp.

Вначале рассмотрим класс ClickstreamFilter. Все используемые в статье примеры в целях форматирования и переносимости слегка видоизменены. Позже я объясню, зачем.

import java.io.IOException;
import javax.servlet.*;
import javax.servlet.http.*;

public class ClickstreamFilter implements Filter {
  protected FilterConfig filterConfig;
  private final static String FILTER_APPLIED = "_clickstream_filter_applied";

  public void init(FilterConfig config) throws ServletException {
    this.filterConfig = filterConfig;
  }

  public void doFilter(ServletRequest request, ServletResponse response,
                   FilterChain chain) throws IOException, ServletException {
    // Ensure that filter is only applied once per request.
    if (request.getAttribute(FILTER_APPLIED) == null) {
      request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
      HttpSession session = ((HttpServletRequest)request).getSession();
      Clickstream stream = (Clickstream)session.getAttribute("clickstream");
      stream.addRequest(((HttpServletRequest)request));
    }

    // pass the request on
    chain.doFilter(request, response);
  }

  public void destroy() { }
}

Метод doFilter() берёт пользовательскую сессию, получает из неё Clickstream и добавляет в Clickstream данные текущего запроса. Чтобы сообщить, был ли фильтр уже использован для данного запроса (как это случается при обработке запроса) и игнорировать использование следующего фильтра, он применяет специальный маркерный атрибут FILTER_APPLIED. Вас, возможно, заинтересует, как фильтр может знать о присутствии в сессии атрибута clickstream. Это происходит за счёт того, что ClickstreamLogger создаёт его одновременно с сессией. Так выглядит код ClickstreamLogger :

import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class ClickstreamLogger implements ServletContextListener,
                                          HttpSessionListener {
  Map clickstreams = new HashMap();

  public ClickstreamLogger() { }

  public void contextInitialized(ServletContextEvent sce) {
    sce.getServletContext().setAttribute("clickstreams", clickstreams);
  }

  public void contextDestroyed(ServletContextEvent sce) {
    sce.getServletContext().setAttribute("clickstreams", null);
  }

  public void sessionCreated(HttpSessionEvent hse) {
    HttpSession session = hse.getSession();
    Clickstream clickstream = new Clickstream();
    session.setAttribute("clickstream", clickstream);
    clickstreams.put(session.getId(), clickstream);
  }

  public void sessionDestroyed(HttpSessionEvent hse) {
    HttpSession session = hse.getSession();
    Clickstream stream = (Clickstream)session.getAttribute("clickstream");
    clickstreams.remove(session.getId());
  }
}

Затем регистратор процессов (logger) получает события приложения и с их помощью связывает всё вместе. Во время создания контекста регистратор помещает в контекст общую карту потоков. Это позволяет странице clickstreams.jsp знать, какие из потоков в данный момент являются активными. Во время удаления контекста регистратор процессов удаляет и карту. Как только очередной посетитель инициирует сессию, регистратор выкладывает новый экземпляр Clickstream в сессию и добавляет этот Clickstream в центральную карту потоков. При окончании сессии регистратор удаляет поток из центральной карты.

Приведенный ниже отрывок из описания применения ( web.xml) связывает всё воедино:

   <filter>
         <filter-name>clickstreamFilter</filter-name>
         <filter-class>ClickstreamFilter</filter-class>
    </filter>

    <filter-mapping>
         <filter-name>clickstreamFilter</filter-name>
         <url-pattern>*.jsp</url-pattern>
    </filter-mapping>

    <filter-mapping>
         <filter-name>clickstreamFilter</filter-name>
         <url-pattern>*.html</url-pattern>
    </filter-mapping>

    <listener>
         <listener-class>ClickstreamLogger</listener-class>
    </listener>

Так мы регистрируем ClickstreamFilter и заставляем его обрабатывать *.jsp и *.html запросы. ClickstreamLogger также регистрируется как слушатель событий приложения по мере их происхождения.

Обе страницы JSP достают данные clickstream из объектов сессии и контекста и с помощью HTML-интерфейса отображают текущее состояние. Ниже приводится файл clickstreams.jsp со сводными данными:

<%@ page import="java.util.*" %>
<%@ page import="Clickstream" %>
<%
Map clickstreams = (Map)application.getAttribute("clickstreams");
String showbots = "false";

if (request.getParameter("showbots") != null) {
  if (request.getParameter("showbots").equals("true"))
    showbots = "true";
  else if (request.getParameter("showbots").equals("both"))
    showbots = "both";
}
%>

<font face="Verdana" size="-1">
<h1>All Clickstreams</h1>

<link href="clickstreams.jsp?showbots=false">No Bots</a> |
<link href="clickstreams.jsp?showbots=true">All Bots</a> |
<link href="clickstreams.jsp?showbots=both">Both</a> <p>

<% if (clickstreams.keySet().size() == 0) { %>
        No clickstreams in progress
<% } %>

<%
Iterator it = clickstreams.keySet().iterator();
int count = 0;
while (it.hasNext()) {
  String key = (String)it.next();
  Clickstream stream = (Clickstream)clickstreams.get(key);

  if (showbots.equals("false") && stream.isBot()) {
    continue;
  }
  else if (showbots.equals("true") && !stream.isBot()) {
    continue;
  }
  count++;
  try {
%>

<%= count %>.
<link href="viewstream.jsp?sid=<%= key %>"><b>
<%= (stream.getHostname() != null &&
!stream.getHostname().equals("") ?
     stream.getHostname() : "Stream") %>
</b></a> <font size="-1">[ <%= stream.getStream().size()
%> reqs ]</font><br>

<%
  }
  catch (Exception e) {
%>
  An error occurred — <%= e %><br>
<%
  }
}
%>

Пакет можно скачать с сайта OpenSymphony. Положите и скомпилируйте Java-файлы в WEB-INF/classes, выложите JSP-файлы в корневой маршрут приложения и сделайте изменения в файле web.xml, как сказано в инструкции. В крайнем случае, вы можете скачать уже готовый WAR-файл с http://www.javaworld.com/jw-06-2001/Filters/clickstream.war.

Чтобы фильтр заработал на Tomcat 4.0 beta 5, оказалось, что нужно сделать кое-какие модификации. Изменения, которые я сделал, отражают некоторые традиционные проблемы, возникающие при переносе сервлетов и фильтров. Я приведу их список:

WAR, который можно скачать, содержит все перечисленные изменения и должен работать “с места в карьер” на всех серверах, хотя я не проводил широкомасштабных испытаний.

Как сжать ответ

Третьи блюдом в нашем меню идёт фильтр для автоматической компрессии получаемого ответного потока, что позволяет более рационально использовать пропускную способность и продемонстрировать возможности упаковки объекта ответа. Данный фильтр создан на основе фильтра, написанного Эйми Ро (Amy Roh) из Sun, предназначенного для демонстрационных приложений под Tomcat 4.0. Оригинальный код вы найдёте под webapps/examples/WEB-INF/classes/compressionFilters. Сам фильтр можно скачать на условиях стандартной лицензии Apache. Демонстрационный код, приведенный здесь и в WAR, для простоты был немного отредактирован.

В задачу класса CompressionFilter входит исследование заголовков в ответе с целью определения, поддерживает ли клиент возможность компрессии, и если да, упаковка объекта ответа в стандартный ответ, методы getOutputStream() и getWriter() которого настроены так, чтобы использовать сжатый выходной поток. Использование фильтров обеспечивает простое и в то же время грамотное решение.

При рассмотрении кода фильтра, мы начнём с метода init() :

 public void init(FilterConfig filterConfig) {
    config = filterConfig;
    compressionThreshold = 0;
    if (filterConfig != null) {
      String str = filterConfig.getInitParameter("compressionThreshold");
      if (str != null) {
        compressionThreshold = Integer.parseInt(str);
      }
      else {
        compressionThreshold = 0;
      }
    }
  }

Если метод init() вызывается до того, как задействуется фильтр, он определяет наличие в фильтре параметра init, чтобы вычислить пороговый уровень компрессии — объём байт, который должен входить в ответ, чтобы был смысл применять компрессию.

Метод doFilter(), вызываемый при поступлении запроса, находит заголовок для шифрования ( Accept-Encoding header), и если его значение содержит gzip, метод упаковывает ответ и устанавливает пороговое значение контейнера:

 public void doFilter(ServletRequest request, ServletResponse response,
                    FilterChain chain ) throws IOException, ServletException {
    boolean supportCompression = false;
    if (request instanceof HttpServletRequest) {
      Enumeration e = ((HttpServletRequest)request)
                           .getHeaders("Accept-Encoding");
      while (e.hasMoreElements()) {
        String name = (String)e.nextElement();
        if (name.indexOf("gzip") != -1) {
          supportCompression = true;
        }
      }
    }
    if (!supportCompression) {
      chain.doFilter(request, response);
    }
    else {
      if (response instanceof HttpServletResponse) {
        CompressionResponseWrapper wrappedResponse =
          new CompressionResponseWrapper((HttpServletResponse)response);
        wrappedResponse.setCompressionThreshold(compressionThreshold);
        chain.doFilter(request, wrappedResponse);
      }
    }
  }

Обратите внимание на то, как ответ должен укладываться в HttpServletRequest, прежде чем будут просканированы его заголовки, как в первом примере. Фильтр пользуется контейнерным классом (wrapper class) CompressionResponseWrapper, получаемым при расширении стандартного HttpServletResponseWrapper. Код контейнера достаточно прост:

public class CompressionResponseWrapper extends HttpServletResponseWrapper {

  protected ServletOutputStream stream = null;
  protected PrintWriter writer = null;
  protected int threshold = 0;
  protected HttpServletResponse origResponse = null;

  public CompressionResponseWrapper(HttpServletResponse response) {
    super(response);
    origResponse = response;
  }

  public void setCompressionThreshold(int threshold) {
    this.threshold = threshold;
  }

  public ServletOutputStream createOutputStream() throws IOException {
    return (new CompressionResponseStream(origResponse));
  }

  public ServletOutputStream getOutputStream() throws IOException {
    if (writer != null) {
      throw new IllegalStateException("getWriter() has already been " +
                                      "called for this response");
    }

    if (stream == null) {
      stream = createOutputStream();
    }
    ((CompressionResponseStream) stream).setCommit(true);
    ((CompressionResponseStream) stream).setBuffer(threshold);
    return stream;
  }

  public PrintWriter getWriter() throws IOException {
    if (writer != null) {
      return writer;
    }

    if (stream != null) {
      throw new IllegalStateException("getOutputStream() has already " +
                                      "been called for this response");
    }

    stream = createOutputStream();
    ((CompressionResponseStream) stream).setCommit(true);
    ((CompressionResponseStream) stream).setBuffer(threshold);
    writer = new PrintWriter(stream);
    return writer;
  }
}

Любое обращение к getOutputStream() или getWriter() будет возвращать объект, с помощью класса java.util.zip.GZIPOutputStream.

Демонстрационные приложения для Tomcat уже сконфигурированы таким образом, чтобы компрессионный фильтр мог работать с сервлетом. Демонстрационный сервлет отзывается на /CompressionTest URL (убедитесь, что /examples стоит вначале контестного маршрута). Используя WAR, вы можете обратиться к тестовому сервлету на /servlet/compressionTest (опять же, не забудьте установить вначале соответствующий маршрут). Для конфигурации теста вы можете воспользоваться следующим отрывком файла web.xml :

<filter>
    <filter-name>compressionFilter</filter-name>
    <filter-class>CompressionFilter</filter-class>
    <init-param>
      <param-name>compressionThreshold</param-name>
      <param-value>10</param-value>
    </init-param>
</filter>

<filter-mapping>
    <filter-name>compressionFilter</filter-name>
    <servlet-name>compressionTest</servlet-name>
</filter-mapping>

<servlet>
  <servlet-name>
    compressionTest
  </servlet-name>
  <servlet-class>
    CompressionTestServlet
  </servlet-class>
</servlet>

CompressionTestServlet (не показанный здесь) сообщает, возможна или нет компрессия, и если возможна, оповещает об успешном выполнении операции.

Фильтр загрузки файла

Последний фильтр, который мы здесь рассмотрим, отвечает за обработку POST –запросов на комбинированные/входящие в форму данные, то есть запросов, содержащих загрузку файлов. Каждый из POST -запросов на данные заполняемой формы может содержать любое количество параметров и файлов, использующих свои собственные форматы, необязательно понятные сервлетам. Разработчики сервлетов обычно пользуются для закачки файлов классами разных производителей, к примеру, классами MultipartRequest и MultipartParser, которые можно найти в моём пакете own com.oreilly.servlet. Здесь можно увидеть новый подход к использованию MultipartFilter, облегчающий обработку подобных запросов. Фильтр создан на основе парсеров из пакета com.oreilly.servlet и интегрирован в пакет (см. Ресурсы ).

MultipartFilter отслеживает поступающие запросы и при обнаружении запроса на загрузку файла (с указанием типа multipart/form-data ), фильтр заворачивает объект запроса с помощью специального упаковщика, умеющего анализировать контекст в данном формате. Сервлет, получающий специальный упаковщик запроса, имеет автоматический доступ к параметрам через стандартные методы getParameter(), так как упаковщик переопределяет их поведение. Сервлет может также обрабатывать скачиваемые файлы путём приведения запроса к типу упаковщика и использования вместе с ним дополнительных методов getFile().

Код фильтра выглядит следующим образом:

package com.oreilly.servlet;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class MultipartFilter implements Filter {

  private FilterConfig config = null;
  private String dir = null;

  public void init(FilterConfig config) throws ServletException {
    this.config = config;

    // Determine the upload directory. First look for an uploadDir filter
    // init parameter. Then look for the context tempdir.
    dir = config.getInitParameter("uploadDir");
    if (dir == null) {
      File tempdir = (File) config.getServletContext()
                  .getAttribute("javax.servlet.context.tempdir");
      if (tempdir != null) {
        dir = tempdir.toString();
      }
      else {
        throw new ServletException(
          "MultipartFilter: No upload directory found: set an uploadDir " +
          "init parameter or ensure the javax.servlet.context.tempdir " +
          "directory is valid");
      }
    }
  }

  public void destroy() {
    config = null;
  }

  public void doFilter(ServletRequest request, ServletResponse response,
                     FilterChain chain) throws IOException, ServletException {
    HttpServletRequest req = (HttpServletRequest) request;
    String type = req.getHeader("Content-Type");

    // If this is not a multipart/form-data request continue
    if (type == null || !type.startsWith("multipart/form-data")) {
      chain.doFilter(request, response);
    }
    else {
      MultipartWrapper multi = new MultipartWrapper(req, dir);
      chain.doFilter(multi, response);
    }
  }
}

Метод init() определяет каталог для скачиваемого файла. Это место, куда парсер складывает файлы, чтобы не забивать память запросом. Сначала он ищет параметр uploadDir filter init и, если не находит, по умолчанию отправляется в директорию tempdir — стандартный контекстный атрибут, появившийся в Servlet API 2.2.

Метод doFilter( ) определяет тип содержимого запроса и, если это multipart/form-data, заворачивает запрос с помощью MultipartWrapper. Код упаковщика выглядит так:

package com.oreilly.servlet;

import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class MultipartWrapper extends HttpServletRequestWrapper {

  MultipartRequest mreq = null;

  public MultipartWrapper(HttpServletRequest req, String dir)
                                     throws IOException {
    super(req);
    mreq = new MultipartRequest(req, dir);
  }

  // Methods to replace HSR methods
  public Enumeration getParameterNames() {
    return mreq.getParameterNames();
  }
  public String getParameter(String name) {
    return mreq.getParameter(name);
  }
  public String[] getParameterValues(String name) {
    return mreq.getParameterValues(name);
  }
  public Map getParameterMap() {
    Map map = new HashMap();
    Enumeration enum = getParameterNames();
    while (enum.hasMoreElements()) {
      String name = (String) enum.nextElement();
      map.put(name, mreq.getParameterValues(name));
    }
    return map;
  }

  // Methods only in MultipartRequest
  public Enumeration getFileNames() {
    return mreq.getFileNames();
  }
  public String getFilesystemName(String name) {
    return mreq.getFilesystemName(name);
  }
  public String getContentType(String name) {
    return mreq.getContentType(name);
  }
  public File getFile(String name) {
    return mreq.getFile(name);
  }
}

Для выполнения загрузки упаковщик создаёт объект com.oreilly.servlet.MultipartRequest и переопределяет все методы getParameter() так, чтобы они могли использовать MultipartRequest, а не необработанный запрос, для чтения значений параметров. Упаковщик также определяет методы getFile(), чтобы сервлет, получающий такой упакованный запрос, мог вызывать дополнительные методы для работы с загружаемым файлом.

Описание применения web.xml добавляет фильтр следующим образом:

<filter>
    <filter-name>multipartFilter</filter-name>
    <filter-class>com.oreilly.servlet.MultipartFilter</filter-class>
    <!--
    <init-param>
      <param-name>uploadDir</param-name>
      <param-value>/tmp</param-value>
    </init-param>
    -->
</filter>

<filter-mapping>
    <filter-name>multipartFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

<servlet>
    <servlet-name>
        uploadTest
    </servlet-name>
    <servlet-class>
        UploadTest
    </servlet-class>
</servlet>

<servlet-mapping>
    <servlet-name>
        uploadTest
    </servlet-name>
    <url-pattern>
        /uploadTest
    </url-pattern>
</servlet-mapping>
The UploadTest servlet looks like this:
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

import com.oreilly.servlet.*;

public class UploadTest extends HttpServlet {

  public void doPost(HttpServletRequest req, HttpServletResponse res)
                                throws ServletException, IOException {
    res.setContentType("text/html");
    PrintWriter out = res.getWriter();

    out.println("<HTML>");

out.println("<HEAD><TITLE>UploadTest</TITLE></HEAD>");
    out.println("<BODY>");
    out.println("<H1>UploadTest</H1>");

    // Parameters can now be read the same way for both
    // application/x-www-form-urlencoded and multipart/form-data requests!

    out.println("<H3>Request Parameters:</H3><PRE>");
    Enumeration enum = req.getParameterNames();
    while (enum.hasMoreElements()) {
      String name = (String) enum.nextElement();
      String values[] = req.getParameterValues(name);
      if (values != null) {
        for (int i = 0; i < values.length; i++) {
          out.println(name + " (" + i + "): " + values[i]);
        }
      }
    }
    out.println("</PRE>");

    // Files can be read if the request class is MultipartWrapper
    // Init params to MultipartWrapper control the upload handling

    if (req instanceof MultipartWrapper) {
      try {
        // Cast the request to a MultipartWrapper
        MultipartWrapper multi = (MultipartWrapper) req;

        // Show which files we received
        out.println("<H3>Files:</H3>");
        out.println("<PRE>");
        Enumeration files = multi.getFileNames();
        while (files.hasMoreElements()) {
          String name = (String)files.nextElement();
          String filename = multi.getFilesystemName(name);
          String type = multi.getContentType(name);
          File f = multi.getFile(name);
          out.println("name: " + name);
          out.println("filename: " + filename);
          out.println("type: " + type);
          if (f != null) {
            out.println("length: " + f.length());
          }
          out.println();
        }
        out.println("</PRE>");
      }
      catch (Exception e) {
        out.println("<PRE>");
        e.printStackTrace(out);
        out.println("</PRE>");
      }
    }

    out.println("</BODY></HTML>");
  }
}

Первая половина сервлета показывает, как фильтр отображает данные параметров, ничего не меняя в принимающем сервлете. Во второй половине показано, как сервлет может модифицировать запрос в MultipartWrapper, чтобы получить дополнительные методы доступа к файлам.

Пример HTML-формы для управления сервлетом приведён ниже:

<FORM ACTION="uploadTest" ENCTYPE="multipart/form-data"
METHOD=POST>
What is your name? <INPUT TYPE=TEXT NAME=submitter> <BR>
What is your age? <INPUT TYPE=TEXT NAME=age> <BR>
Which file do you want to upload? <INPUT TYPE=FILE NAME=file1> <BR>
Any other file to upload? <INPUT TYPE=FILE NAME=file2> <BR>
<INPUT TYPE=SUBMIT>
</FORM>

Так может выглядеть получаемый результат:

UploadTest
Request Parameters:
submitter (0): Jason
age (0): 28

Files:
name: file1
filename: 4008b21.tif
type: application/octet-stream
length: 39396

name: file2
filename: null
type: null

Полагаю, что у кого-то могут возникнуть обоснованные вопросы по поводу гарантий, что MultipartWrapper, установленный фильтром, будет напрямую работать с сервлетом. Спецификация Public Draft #2 и Tomcat 4.0 beta 5 дают такую гарантию. В действительности, если вы попробуете зайти в сервлет через /servlet/UploadTest, вы обнаружите, что фильтр работает не так, как нужно. Это происходит из-за того, что инициирующий объект при обработке /servlet упаковывает MultipartWrapper с помощью упаковщика, принадлежащего Tomcat, что позволяет правильно анализировать параметры, но не даёт возможности напрямую воспользоваться методами доступа. При обсуждении этой проблемы в нашей экспертной группе “Servlet API” мы решили, что контейнер сервлета не должен заходить дальше, чем упаковщик фильтра. Спецификация в дальнейшем будет отредактирована, чтобы сделать эту процедуру более понятной. Соответственно, следующие версии Tomcat 4.0 будут отвечать этим требованиям. А пока что мы предлагаем использовать в упаковщике запроса метод getRequest() для прохождения по запросу в поисках скрытого упаковщика.

Скачать WAR-файл с данным фильтром можно с http://www.javaworld.com/jw-06-2001/Filters/mulitpart.war.

Роль фильтров

Сервлетные фильтры позволяют управлять процессом обработки запросов и ответов, предоставляя сервлету полезную функциональность и позволяя избежать больших объёмов кода в самом сервлете. Надеюсь, приведенные здесь примеры показали вам преимущества и эффективность применения фильтров.

Особая благодарность авторам фильтров и комментариев к ним: Amy Roh, Criag McClanahan, Serge Knystautas и сотрудникам компании OpenSymphony.

Об авторе

Jason Hunter работает главным технологом в компании CollabNet, занимающейся созданием инструментальных средств коллективной разработки программного обеспечения, основанного на концепции открытых стандартов. Он также является aвтором книги Java Servlet Programming, 2nd Edition (O'Reilly, апрель 2001), Одним из издателей Servlets.com и участником проекта Apache Tomcat (работая над ним ещё в ту пору, когда он только зарождался в недрах Sun). Он является членом экспертной группы по проблемам Servlet/JSP и JAXP API, а также представителем Apache Software Foundation при комитете JCP Executive Committee, отвечающем за разработки в области Java-платформы. Большую часть времени посвящает разработкам для открытой библиотеки JDOM, помогая оптимизировать процесс интеграции Java и XML.

Ресурсы

WAR-файлы

Reprinted with permission from the June 2001 edition of JavaWorld magazine. Copyright © ITworld.com, Inc., an IDG Communications company.
View the original article at: http://www.javaworld.com/javaworld/ jw-06-2001/jw-0622-filters.html

Русский перевод опубликован с разрешения Java Журнал © IBA Java Team, 2001. Оригинал статьи: http://java.iba.com.by/javaweb/ibajavat.nsf/ lnarticlesview/8FBDFFF7DE34F3A642256A850037C676

[an error occurred while processing this directive]
[an error occurred while processing this directive] Перевод на русский © Андрей Чернухо, 2001
< Вернуться на caйт :: Copyright © 1999 — 2010, IT • archiv.