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

Сервлетный фильтр, в соответствии со спецификацией, это Java-код, пригодный для повторного использования и позволяющий преобразовать содержание HTTP-запросов, HTTP-ответов и информацию, содержащуюся в заголовках HTML. Сервлетный фильтр занимается предварительной обработкой запроса, прежде чем тот попадает в сервлет, и/или последующей обработкой ответа, исходящего из сервлета.

Сервлетные фильтры могут :

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

Интерфейс javax.servlet.Filter

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

  • void init (FilterConfig config) throws ServletException;
  • void destroy();
  • void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException;

Метод init вызывается прежде, чем фильтр начинает работать, и определяет конфигурационные параметры фильтра. Метод doFilter выполняет непосредственно работу фильтра. Таким образом, сервер вызывает init один раз, чтобы запустить фильтр в работу, а затем вызывает doFilter столько раз, сколько запросов будет сделано непосредственно к данному фильтру. После того, как фильтр заканчивает свою работу, вызывается метод destroy.

package common; 
  
import java.io.*; 
import javax.servlet.*; 
import javax.servlet.http.*; 
  
public class FilterConnect implements Filter 
{ 
    private FilterConfig config = null; 
    private boolean active = false; 

    public void init (FilterConfig config) throws ServletException 
    { 
        this.config = config; 
        String act = config.getInitParameter("active"); 
        if (act != null) 
            active = (act.toUpperCase().equals("TRUE")); 
    } 

    public void doFilter (ServletRequest request, ServletResponse response, 
                          FilterChain chain) throws IOException, ServletException 
    { 
        if (active){ 
            // Здесь можно вставить код для обработки 
        } 
        chain.doFilter(request, response); 
    } 

    public void destroy() 
    { 
        config = null; 
    } 
} 

В примере представленного шаблона фильтра инициализируется значение параметра active. При вызове фильтра (doFilter) проверяется значение параметра active, и если active = true, могут быть выполнены действия, определенные разработчиком.

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

После того, как класс-фильтр откомпилирован, его необходимо установить в контейнер и "приписать"(map) к одному или нескольким сервлетам. Объявление и подключение фильтра определяется в дескрипторе приложения web.xml внутри элементов <filter> и <filter-mapping>. Для подключение фильтра к сервлету необходимо использовать вложенные элементы <filter-name> и <servlet-name>. Например:

<filter>
    <filter-name>FilterName</filter-name>
    <filter-class>common.FilterConnect</filter-class>
    <init-param>
        <param-name>active</param-name>
        <param-value>true</param-true>
    </init-param>
</filter>
 
<filter-mapping>
    <filter-name>FilterName</filter-name>
    <servlet-name>ServletName</servlet-name>
</filter-mapping>

В представленном коде дескриптора приложения web.xml объявлен класс-фильтр FilterConnect с именем FilterName. Фильтр имеет параметр инициализации active, которому присвивается значение true. Фильтр FilterName в разделе <filter-mapping> подключен к сервлету ServletName.

Порядок, в котором контейнер строит цепочку фильтров для запроса определяется следующими правилами:

  • цепочка, определяемая url-pattern, выстраивается в том порядке, в котором встречаются соответствующие описания фильтров в web.xml;
  • последовательность сервлетов, определенных с помощью servlet-name, также выполняется в той последовательности, в какой эти элементы встречаются в дескрипторе поставки web.xml.

Для связи фильтра со страницами HTML или группой сервлетов необходимо использовать тег <url-pattern>. Например, после следующего кода

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

фильтр будет применен ко всем вызовам страниц HTML.

Использование дополнительных ресурсов, RequestDispatcher

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

public RequestDispatcher getRequestDispatcher(String path);

представленному в интерфейсе ServletRequest. Здесь path - это путь к ресурсу относительно контекста. Например, необходимо обратиться к сервлету Connect:

RequestDispatcher rd = request.getRequestDispatcher("Connect");

Если ресурс находится в другом контексте, то необходимо предварительно получить контекст методом

public ServletContext getContext(String uripath);

интерфейса ServletContext, а потом использовать метод

public RequestDispatcher getRequestDispatcher (String uripath);

интерфейса ServletContext. Здесь путь uripath должен быть абсолютным, т.е. начинаться с наклонной черты '/'. Например:

RequestDispatcher rd = config.getServletContext().getContext("/prod").getRequestDispatcher("/prod/Customer");

Если требуемый ресурс - сервлет, помещенный в контекст под своим именем, то для его получения можно обратиться к методу

RequestDispatcher getNamedDispatcher (String name);

интерфейса ServletContext. Все три метода возвращают null, если ресурс недоступен или сервер не реализует интерфейс RequestDispatcher.

Как видно из описания методов, к другим ресурсам можно обратиться только через объект типа RequestDispatcher, который предлагает два метода обращения к ресурсу. Первый метод

public void forward (ServletRequest request, ServletResponse response);

просто передает управление другому ресурсу, предоставив ему свои аргументы ServletRequest и ServletResponse. Вызывающий сервлет выполняет предварительную обработку объектов request и response и передает их вызванному сервлету или другому ресурсу, который окончательно формирует ответ response и отправляет его клиенту или, опять-таки, вызывает другой ресурс. Например:

if (rd != null) 
    rd.forward (request, response); 
else 
   response.sendError (HttpServletResponse.SC_NO_CONTENT); 

Исключение IllegalStateException

Вызывающий сервлет не должен выполнять какую-либо отправку клиенту до обращения к методу forward, иначе будет выброшено исключение класса IllegalStateException. Если же вызывающий сервлет уже что-то отправлял клиенту, то следует обратиться ко второму методу

public void include (ServletRequest request, ServletResponse response);

Этот метод вызывает ресурс, который на основании объекта request может изменить тело объекта response. Но вызванный ресурс не может изменить заголовки и код ответа объекта response. Это естественное ограничение, поскольку вызывающий сервлет мог уже отправить заголовки клиенту. Попытка вызванного ресурса изменить заголовок будет просто проигнорирована. Можно сказать, что метод include выполняет такую же работу, как вставки на стороне сервера SSI(Server Side Include).

Пример использования сервлетного фильтра

Рассмотрим пример простого фильтра, выполняющего функцию интерцептора фреймворка Struts2, т.е. перехватывающего обращения пользователя до их обработки сервером. В примере пользователь открывает страницу index.jsp, с которой он может перейти на страницу login.jsp или registration.jsp, и после этого открыть страницу main.jsp. Фильтр будет выполнять проверку входа пользователя на страницу main.jsp с одной из указанных страниц. Если пользователь попытается войти на страницу main.jsp напрямую, то фильтр перенаправит его на страницу авторизации login.jsp.

Таким образом, фильтр будет выполнять проверку "прохождения" пользователем авторизации или регистрации (имитация). Пример упрощен, поэтому ввод данных на страницах не требуется, поскольку они нигде не контролируются. В примере реализуется только логика переходов.

На следующем скриншоте представлена структура проекта в среде IDE Eclipse.

Пример включает :

  1. SimpleServletFilter.java - сервлетный фильтр;
  2. web.xml - дескриптор приложения;
  3. index.jsp - открываемая по умолчанию страница JSP;
  4. login.jsp - JSP страница авторизации;
  5. registration.jsp - JSP страница регистрации;
  6. main.jsp - главная страница JSP;

Листинг сервлетного фильтра SimpleServletFilter.java

package example;

import java.io.IOException;
import java.util.ArrayList;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.ServletException;
import javax.servlet.RequestDispatcher;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;

/**
 * SimpleServletFilter реализует интерфейс Filter 
 */
@WebFilter("/SimpleServletFilter")
public class SimpleServletFilter implements Filter
{
    private FilterConfig filterConfig;
    private static ArrayList<String> pages;  // хранилище страниц

    /**
     * Конструктор по умолчанию 
     */
    public SimpleServletFilter() 
    {
        // Создание хранилища страниц
        if (pages == null)
            pages = new ArrayList<String>();
    }

    /**
     * Метод освобождения ресурсов
     * @see Filter#destroy()
     */
    @Override
    public void destroy() 
    {
        filterConfig = null;
    }

    /**
     * Метод инициализации фильтра
     * @see Filter#init(FilterConfig)
     */
    @Override
    public void init(FilterConfig fConfig) throws ServletException
    {
        filterConfig = fConfig;
    }
    /**
     * Метод фильтрации
     * @see Filter#doFilter(ServletRequest, ServletResponse, FilterChain)
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                        FilterChain filterChain) throws IOException, ServletException
    {
        // Если фильтр активной, то выполнить проверку
        if (filterConfig.getInitParameter("active").equalsIgnoreCase("true")) {
            HttpServletRequest req = (HttpServletRequest)request;
            // Раскладываем адрес на составляющие
            String[] list = req.getRequestURI().split("/");
            // Извлекаем наименование страницы 
            String page = null;
            if (list[list.length - 1].indexOf(".jsp") > 0) {
                page = list[list.length - 1];
            }
            // Если открывается главная страница, то выполняем проверку
            if ((page != null) && page.equalsIgnoreCase("main.jsp")) {
                // Если была предварительно открыта одна из страниц 
                // login.jsp или registration.jsp, то передаем управление
                // следующему элементу цепочки фильтра
                if (pages.contains("login.jsp") || pages.contains("registration.jsp")) {
                    filterChain.doFilter(request, response);
                    return;
                } else {
                    // Перенаправление на страницу login.jsp
                    ServletContext ctx = filterConfig.getServletContext();
                    RequestDispatcher dispatcher = ctx.getRequestDispatcher("/login.jsp");
                    dispatcher.forward(request, response);
                    return;
                }
            } else if (page != null) {
                // Добавляем страницу в список
                if (!pages.contains(page))
                    pages.add(page);
            }
        }
        filterChain.doFilter(request, response);
    }
}

В конструкторе фильтра создается хранилище страниц. При инициализации фильтра в методе init(FilterConfig) определяется параметр конфигурации, который содержит флаг активности фильтра "active", определенный в дескрипторе приложения web.xml. Если флаг "true", то будет выполняться обработка в методе doFilter(). Суть обработки заключается в том, что если выполняется вход не на страницу main.jsp, то наименование страницы сохраняется в хранилище pages. Если открывается страница main.jsp, то выполняется проверка на наличие в хранилище страниц авторизации или регистрации. При удачном результате проверки, сервер открывает нужную страницу, в противном случае перенаправляет на страницу авторизации.

Листинг дескриптора приложения web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/j2ee"
         xmlns:web="http://xmlns.jcp.org/xml/ns/javaee" 
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
                             http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd 
                             http://java.sun.com/xml/ns/j2ee 
                             http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
         id="WebApp_9" version="2.4">
  <display-name>Сервлетный фильтр</display-name>
  <filter>
      <filter-name>simpleFilter</filter-name>
      <filter-class>example.SimpleServletFilter</filter-class>
      <init-param>
          <param-name>active</param-name>
          <param-value>true</param-value>
      </init-param>
  </filter>

  <filter-mapping>
      <filter-name>simpleFilter</filter-name>
      <url-pattern>/*</url-pattern>
  </filter-mapping>
  
  <welcome-file-list>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>
  
</web-app>

В дескрипторе приложения определен фильтр simpleFilter с параметром active. Фильтр перехватывает обращение ко всем страницам. В проекте используется страница по умолчанию index.jsp, т.е. при обращении в браузере к данному проекту без определения страницы, сервер откроет эту страницу.

Листинг страницы index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" 
                                              pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 
         "http://www.w3.org/TR/html4/loose.dtd">
<html>
    <head>
        <title>Стартовая страница</title>
    </head>
    <body>
       <p><input type="submit" value="Авторизация" 
                 onclick="window.location='login.jsp';" />  
          <br />
          <input type="submit" value="Регистрация" 
                 onclick="window.location='registration.jsp';" />
       </p>
    </body>
</html>

Иитерфейс страницы включает две кнопки, по нажатию на которые будет выполнен переход на другие страницы.

На следующем скриншоте представлен интерфейс страницы. Обратите внимание на URL, который содержит наименование проекта, но не включает index.jsp, т.е. наименование страницы.

По нажатию на кнопку "Авторизация" сервер открывает страницу login.jsp. По нажатию на кнопку "Регистрация" будет открыта страница registration.jsp.

Листинг страницы login.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" 
                                              pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 
         "http://www.w3.org/TR/html4/loose.dtd">
<html>
  <head>
    <title>Страница авторизации</title>
  </head>
  <body>
    <h2>Страница авторизации</h2>
    <p><b>Логин:</b><br>
       <input type="text" id="login" size="30"></p>
    <p><b>Пароль:</b><br>
       <input type="text" id="password" size="30"></p>
    <p><input type="submit" value="ОK" 
              onclick="window.location='main.jsp';" />
   </body>
</html>

Интерфейс страницы включает заголовок, два поля ввода и кнопку подтверждения "ОК". На следующем скриншоте представлен интерфейс страницы. URL содержит наименование страницы.

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

Листинг страницы registration.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" 
                                              pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 
         "http://www.w3.org/TR/html4/loose.dtd">
<html>
  <head>
    <title>Страница регистрации</title>
  </head>
  <body>
    <h2>Страница регистрации</h2>
    <p><b>Ваше имя:</b><br>
       <input type="text" id="name" size="30"></p>
    <p><b>Логин:</b><br>
       <input type="text" id="login" size="30"></p>
    <p><b>Пароль:</b><br>
       <input type="text" id="password" size="30"></p>
    <p><input type="submit" value="ОK" 
              onclick="window.location='main.jsp';" />
   </body>
</html>

Интерфейс страницы включает заголовок, три поля ввода и кнопку подтверждения "ОК". На следующем скриншоте представлен интерфейс страницы, URL которой содержит наименование страницы.

Данные ввода не проверяются, можно сразу же нажать кнопку для перехода на страницу main.jsp.

Листинг страницы main.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" 
                                              pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 
         "http://www.w3.org/TR/html4/loose.dtd">
<html>
    <head>
        <title>Главная страница</title>
    </head>
    <body>
      <h2>Приветствуем Вас на главной странице!</h2>
   </body>
</html>

На следующем скриншоте представлен интерфейс страницы, включающий строку приветствия. URL содержит наименование страницы.

Несанкционированный вход

При попытке входа на страницу main.jsp без посещения страниц авторизации и регистрации, т.е. когда в поле браузера URL вводится адрес http://localhost:8080/ServletFilter/main.jsp и нажимается клавиша <Enter> сервер получает команду и отдает управлению сервлетному фильтру. Сервлетный фильтр переводит пользователя на страницу авторизации.

На следующем скриншоте представлен интерфейс страницы login.jsp, несмотря на то, что URL содержит наименование страницы main.jsp.

Скачать пример

Исходный код примера, рассмотренного в тексте страницы, можно скачать здесь (194 Кб).

  Рейтинг@Mail.ru