1. Знайомство з фільтрами

Але це ще не все. Ти ж не думаєш, що сервлети влаштовані так просто?

Крім сервлетів, які ми вже розібрали, є ще так звані службові сервлети – фільтри. Вони дуже схожі на сервлети, але їхнє основне завдання — допомагати сервлетам обробляти запити.

Фільтр – це як секретар, а сервлет – директор. Перед тим, як документ потрапить на стіл директорові, він пройде через руки секретаря. І після того, як директор його підпише, він знову потрапить до секретаря, вже як вихідна кореспонденція, наприклад.

Такий секретар може відбраковувати частину запитів до директора (наприклад, спам). Або давати стандартні відповіді на відомі йому питання (директора немає на місці). І так далі. Таких секретарів може бути кілька: один може фільтрувати спам одразу для всіх директорів, інший – перекидати запити між різними директорами тощо.

Так само працюють і фільтри:

2. Класи Filter, FilterChain, FilterConfig

Фільтри дуже схожі на сервлет, але з парою невеликих відмінностей. Щоб написати свій фільтр, потрібно успадковуватись від інтерфейсу javax.servlet.Filter.

Фільтр також має методи init() і destroy(). Замість методу service() у фільтра є метод doFilter(). Навіть є свій клас FilterConfig. Фільтр також додається до сервлета у файлі web.xml або за допомогою анотації @WebFilter.

Список методів:

Методи Опис
1 init(FilterConfig config) ініціалізація фільтра
2 destroy() вивантаження фільтра
3 doFilter(ServletRequest , ServletResponse, FilterChain) обробка (фільтрація) запиту

У чому ж відмінність сервлета та фільтра?

Фільтрів може бути кілька, і вони послідовно обробляють запит (і відповідь). Вони об'єднані в так званий ланцюжок, і для них є навіть спеціальний клас FilterChain.

Після обробки запиту в методі doFilter() потрібно викликати метод doFilter() наступного фільтра в ланцюжку. Приклад:


public class MyFilter implements Filter {

  public void init(FilterConfig arg0) throws ServletException {
  }

  public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws Exception {

      PrintWriter out = resp.getWriter();
      out.print("Дописуємо щось перед тілом відповіді");

      chain.doFilter(req, resp); //викликаємо наступний фільтр у ланцюжку

      out.print("Дописуємо щось після тіла відповіді");
    }

  public void destroy() {
  }
}

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

  • Логування всіх вхідних запитів (і відповідей)
  • Стиснення даних
  • Шифрування (і розшифрування) даних
  • Валідація даних запиту
  • Додавання/видалення потрібних заголовків
  • Перенаправлення запитів
  • Контроль доступу (перевірка, чи залогінений користувач)

3. Клас RequestDispatcher

Іноді в процесі роботи фільтра всередині методу doFilter() може виникнути необхідність викликати інший сервлет. Для цього контейнер має спеціальний об'єкт RequestDispatcher.

Отримати його можна двома способами:

  • У об'єкта HttpServletRequest
  • У об'єкта ServletContext

Цей об'єкт можна використовувати для перенаправлення існуючого запиту на інший сервлет. Наприклад, з'ясувалося, що користувач не авторизований, і ми хочемо показати йому сторінку з авторизацією. Або сталася помилка на сервері, і ми хочемо відобразити користувачу error-сторінку :)


public class HelloServlet extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response)  throws Exception {
           String path = "/error.html";
           ServletContext servletContext = this.getServletContext();
           RequestDispatcher requestDispatcher = servletContext.getRequestDispatcher(path);
           requestDispatcher.forward(request, response);
    }
}

Також ти можеш викликати RequestDispatcher із фільтра.


public class MyFilter implements Filter {

  public void init(FilterConfig arg0) throws ServletException {
  }

  public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws Exception {
           String path = "/error.html";
           ServletContext servletContext = req.getServletContext();
           RequestDispatcher requestDispatcher = servletContext.getRequestDispatcher(path);
           requestDispatcher.forward(req, resp);
    }

  public void destroy() {
  }
}

Зверни увагу, що запит буде оброблений у методі forward(), і викликати doFilter() після використання RequestDispatcher не потрібно.

4. Порівняння редиректу та форварда

І ще один важливий момент. Якщо ти хочеш у своєму сервлеті перенаправити користувача на інший URI, зробити це можна двома способами:

  • redirect
  • forward

Ми вже їх розбирали, але для зручності проговорю це ще раз.

Коли ти виконуєш redirect через виклик response.sendRedirect("посилання"), то сервер відсилає браузеру (клієнту) відповідь 302 та вказане посилання. А браузер, проаналізувавши відповідь сервера, завантажує посилання, яке ти передав. Тобто, посилання в браузері змінюється на нове.

Якщо ти виконуєш forward через виклик requestDispatcher.forward(), то новий запит виконується всередині контейнера, і його відповідь твій сервлет відсилає браузеру (клієнту) як відповідь сервлета. Водночас браузер отримує відповідь від нового сервлета, але посилання у браузері не змінюється.