Сервлет (Java)

Материал из Национальной библиотеки им. Н. Э. Баумана
Последнее изменение этой страницы: 12:53, 17 июня 2016.

Сервлеты (англ. Servlets) - это специализированные механизмы Java для создания WEB ресурсов.

Сервлеты это java-программы, которые выполняются на серверной стороне Web-приложения. Точно так же, как апплеты динамически расширяют функциональные возможности Web-браузера, сервлеты динамически расширяют функциональные возможности Web-сервера.

Работу сервлета можно описать следующим образом:

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

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

  • установить сервлет на сервер.
  • Послать сервлету запрос на обслуживание

Сервлеты это модули расширения для запрос-ответ ориентированных серверов, таких как web-сервера с поддержкой Java. Например, сервлет ответственный за передачу информации из HTML форм для обновления базы данных компании.

Сервлет для сервера то же самое, что апплет для броузера. Но в отличие от апплета сервлеты не имеют графического интерфейса.

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

Поскольку сервлеты предоставляют великолепное решение для программной поддержки на стороне сервера, они являются одним из наиболее популярных причин перехода на Java. Не только потому, что они предоставляют рабочую среду, которая заменяет CGI программирование, но весь ваш код приобретает портируемость между платформами, получаемую от использования Java, и вы приобретаете доступ ко всему Java API (за исключением, конечно, того, которое производит GUI, такого, как Swing).

Общая информация

Основа сервлета

Архитектура API сервлета основывается на том, что классический провайдер сервиса использует метод service( ), через который все клиентские запросы будут посылаться программным обеспечением контейнера сервлетов, и методы жизненного цикла init( ) и destroy( ), которые вызываются только в то время, когда сервлет загружается и выгружается (это случается редко).

   
public interface Servlet {
   public void init(ServletConfig config) throws ServletException;
   
   public ServletConfig getServletConfig();
   
   public void service(ServletRequest req, ServletResponse res)
         throws ServletException, IOException;
   
   public String getServletInfo();
   
   public void destroy();
}

Основное назначение getServletConfig( ) состоит в возвращении объекта ServletConfig, который содержит параметры инициализации и запуска для этого сервлета. getServletInfo( ) возвращает строку, содержащую информацию о сервлете, такую, как автор, версия и авторские права.

Класс GenericServlet является оболочечной реализацией этого интерфейса и обычно не используется. Класс HttpServlet является расширением GenericServlet и специально предназначен для обработки HTTP протокола - HttpServelt является одним из тех классов, которые вы будете использовать чаще всего.

Наибольшее удобство атрибутов сервлетного API состоит во внешних объектах, которые вводятся вместе с классом HttpServlet для его поддержки. Если вы взглянене на метод service( ) в интерфейсе Servlet, вы увидите, что он имеет два параметра: ServeltRequest и ServletResponse. Вместе с классом HttpServlet, эти два объекта предназначены для HTTP: HttpServletRequest и HttpServletResponse. Вот простейший пример, который показывает использование HttpServelResponse</ code>:

   
//: c15:servlets:ServletsRule.java
// {Depends: j2ee.jar}
import javax.servlet.*;

import javax.servlet.http.*;

import java.io.*;

public class ServletsRule extends HttpServlet {
   int i = 0; // "постоянство" сервлета
   
   public void service(HttpServletRequest req, HttpServletResponse res)
         throws IOException {
      res.setContentType("text/html");
      PrintWriter out = res.getWriter();
      out.print("<HEAD><TITLE>");
      out.print("A server-side strategy");
      out.print("</TITLE></HEAD><BODY>");
      out.print("<h1>Servlets Rule! " + i++);
      out.print("</h1></BODY>");
      out.close();
   }
} // /:~

Программа <code>ServletsRule настолько проста, насколько может быть прост сервлет. Сервлет инициализируется только один раз путем вызова его метода init( ), при загрузке сервлета после того, как контейнер сервлетов будет загружен в первый раз. Когда клиент создает запрос к URL, который представлен сервлетом, контейнер сервлетов перехварывает этот запрос и совершает вызов метода service( ) после установки объектов HttpServletRequest и HttpServletResponse.

Основная ответственность метода service( ) состоит во взаимодействии с HTTP запросом, который посылает клиент, и в построении HTTP ответа, основываясь на атрибутах, содержащихся в запросе. ServletsRule манипулирует только объектом ответа, не обращая внимания на то, что посылает клиент.

После установки типа содержимого клиента (которое должно всегда выполнятся прежде, чем будет получен Writer или OutputStream, метод getWriter( )<code> объекта ответа производит объект <code>PrintWriter, который используется для записи символьных данных ответа (другой вариант:getOutputStream производит OutputStream, используемый для бинарного ответа, который применим для более специализированных решений. Оставшаяся часть программы просто посылает HTML клиенту (предполагается, что вы понимаете HTML, так что эта часть не объясняется), как последовательность строк. Однако, обратите внимание на включение "счетчика показов", представленного переменной i. Он автоматически конвертируется в строку в инструкции print( ).

Когда вы запустите программу, вы увидите, что значение i сохраняется между запросами к сервлету. Это важное свойство сервлетов: так как только один сервлет определенного класса загружается в контейнер, и он никогда не выгружается (до тех пор, пока контейнер не завершит свою работу, что обычно случается только при перезагрузке серверного компьютера), любые поля сервлета этого класса действительно становятся постоянными объектами. Это значит, что вы можете без усилий сохранять значения между запросами к сервлету, в то время, как в CGI вы должны записывать значения на диск, чтобы сохранить их, что требует большого количества дурацкого окружения для правильного их получения, а в результате получаем не кроссплатформенное решение.

Конечно, иногда Web сервер, а таким образом и контейнер сервлетов, должны перегружаться, как часть ухода или во время пропадания питания. Для предотвращения потери любой постоянной информации методы сервлета init( ) и destroy( ) автоматически вызываются во время загрузки или выгрузки клиента, что дает вам возможность сохранить важные данные во время остановки и восстановления после перезагрузки. Контейнер сервлетов вызывает метод destroy( ) когда он завершает свою работу, так что вы всегда получаете возможность сохранить значимые данные до тех пор, пока серверная машина не будет сконфигурирована разумным способом.

Есть другая особенность при использовании HttpServlet. Этот класс обеспечивает методы doGet( ) и doPost( ), которые приспособлены для CGI "GET" пересылки от клиента и CGI "POST". GET и POST отличаются только деталями в способе пересылки данных, которые лично я предпочитаю игнорировать. Однако, большинство доступной информации, которую я видел, поддерживает создание раздельных методов doGet( ) и doPost( ), вместо единого общего метода service( ), который обрабатывает оба случая. Такое предпочтение кажется достаточно общим, но я никогда не видел объяснения, способного заставить меня поверить в то, что это не является наследием от CGI программистов, которые должны были учитывать имеют ли они дело с методом GET или POST. Так что действуя в духе "делать самые простые вещи, которые только могут работать",  я просто буду использовать метод service( ) в этом примере, и позволю этому методу заботится о выборе GET vs. POST. Однако, имейте в виду, что я могу что-то упустить, и что фактически может быть отличная причина для использования методов doGet( ) и doPost( ).

Когда бы форма не отсылалась сервлету, HttpServletRequest поступает со всеми предварительно загруженными данными формы, хранящимися как пары ключ-значение. Если вы знаете имя поля, вы можете просто использовать его напрямую в методе getParameter( ), чтобы найти значение. Вы можете также получить Enumiraton (старая форма Итератора) из имен полей, как показано в следующем примере. Этот пример также демонстрирует как один сервлет может быть использован для воспроизведения страницы, содержащей форму, и для страницы ответа. Если Enumiration пустое, значит нет полей; это означает, что форма не была отправлена. В этом случае производится форма, а кнопка подтверждения будет повторно вызывать тот же самый сервлет. Однако если поля существуют, они будут отображены.

   
//: c15:servlets:EchoForm.java
// Группа пар имя-значение любой HTML формы
// {Depends: j2ee.jar}
import javax.servlet.*;

import javax.servlet.http.*;

import java.io.*;

import java.util.*;

public class EchoForm extends HttpServlet {
   public void service(HttpServletRequest req, HttpServletResponse res)
         throws IOException {
      res.setContentType("text/html");
      PrintWriter out = res.getWriter();
      Enumeration flds = req.getParameterNames();
      if (!flds.hasMoreElements()) {
         // Нет отосланной формы -- создаем:
         out.print("<html>");
         out.print("<form method="POST"" + "action="EchoForm">");
         for (int i = 0; i < 10; i++)
            out.print("<b>Field" + i + "</b> " + "<input type="text""
                  + " size="20" name="Field" + i + "" value="Value"
                  + i + ""><br>");
         out.print("<INPUT TYPE=submit name=submit"
               + " Value="Submit"></form></html>");
      }
      else {
         out.print("<h1>Your form contained:</h1>");
         while (flds.hasMoreElements()) {
            String field = (String) flds.nextElement();
            String value = req.getParameter(field);
            out.print(field + " = " + value + "<br>");
         }
      }
      out.close();
   }
} // /:~

Вы можете заметить одно препятствие, из-за которого Java не выглядит предназначенной для обработки строк в памяти - форматирование возвращаемой страницы достаточно тягостно из-за символов завершения строки, эскейп-символов и знаков "+", необходимых для построения объектов String. Для огромных HTML страницы становится неразумным помещение кода прямо в Java. Одно из решений состоит в хранении страницы, как отдельного текстового файла, который потом открывается и передается Web-серверу. Если вы выполните замену любого вида для содержимого этой страницы, это будет нехорошо, так как Java плохо обрабатывает строки. В этом случае вам, вероятно, лучше использовать более подходящее решение для генерации страницы ответа.

Сервлеты и многопоточность (multithreading)

Контейнер сервлетов имеет пул нитей (thread<c/ode>), которые он будет диспетчеризировать для обработки запросов клиентов. Они вполне подходят для случая, когда два клиентских запроса поступают одновременно и должны одновременно обработаться вашим методом <code>service( ). Поэтому метод service( ) должен быть выполнен безопасным для многопоточности способом. Любой доступ к общим ресурсам (файлам, базам данных) должен быть гарантировано использоваться с ключевым словом synchronized. Следующий простой пример помещает выражение synchronized вокруг метода нити slepp. Это блокирует все другие нити до тех пор, пока не пройдет указанное время (пять секунд). Когда будете тестировать, вы должны запустить несколько экземпляров браузера и обратится к этому сервлету настолько быстро, насколько вы способны - вы увидите, что каждый экземпляр браузера будет ожидать, пока не получит ответ.

   
//: c15:servlets:ThreadServlet.java
// {Depends: j2ee.jar}
import javax.servlet.*;

import javax.servlet.http.*;

import java.io.*;

public class ThreadServlet extends HttpServlet {
   int i;
   
   public void service(HttpServletRequest req, HttpServletResponse res)
         throws IOException {
      res.setContentType("text/html");
      PrintWriter out = res.getWriter();
      synchronized (this) {
         try {
            Thread.currentThread().sleep(5000);
         }
         catch (InterruptedException e) {
            System.err.println("Interrupted");
         }
      }
      out.print("<h1>Finished " + i++ + "</h1>");
      out.close();
   }
} // /:~

Также возможно синхронизировать весь сервлет, поместив ключевое слово synchronized перед методом service( ). Фактически, есть только одна причина использовать выражение synchronized, если есть критическая секция в ходе выполнения, которая может быть не выполнена. В этом случае, вы можете предотвратить накладные расходы на каждую синхронизацию в блоке с помощью использования выражения synchronized. С другой стороны, все нити все равно будут ожидать, так что вы можете точно так же синхронизировать весь метод.

Обработка сессий с помощью сервлетов

HTTP является сессионным протоколом, так что вы не можете общаться из одного обращения к серверу с другим обращением, не зависимо от того, одна и та же персона опрашивает ваш сайт, или это полностью разные персоны. Большое усилие было вложено в механизм, который позволяет Web-разработчику отслеживать сессии. Компании не смогли бы заниматься электронной коммерцией без отслеживания клиента и элементов, которые он положил в свою корзину покупок, например.

Есть несколько методов отслеживания сессии, но наиболее общим методом состоит в постоянстве "cookies", которые являются внутренней частью стандарта Интернет. HTTP Working Group of the Internet Engineering Task Force вписала cookies в официальный стандарт RFC 2109.

Cookie - это ни что иное, как маленькие кусочки информации, посылаемые Web-сервером браузеру. Браузер хранит cookie на локальном диске и когда другой вызов обращается к URL'у, с которым ассоциированы cookie, cookie просто посылается вместе с вызовом, таким образом обеспечивается отсылка необходимой информации серверу (обычно так обеспечивают определенный способ, с помощью которого серверу можно сообщить, что это вы совершаете вызов). Однако клиенты могут отключить возможность браузера принимать cookies. Если ваш сайт должен отслеживать клиента с отключенными cookies, другой механизм отслеживания сессии (перезапись URL или спрятанные поля формы) должен встраиваться вручную, так как возможность отслеживания сессии, встроенная в API-сервлета, основывается на cookies.

Cookie

API сервлета (версия 2.0 и следующие) обеспечивают класс Cookie. Этот класс встроен во все детали HTTP заголовков и позволяет устанавливать различные атрибуты cookie. Использование cookie достаточно просто: необходимо позаботиться о добавлении его в объект ответа. Конструктор принимает имя cookie в качестве первого аргумента и значение в качестве второго. Cookie добавляются в объект ответа прежде, чем вы посылаете какое либо содержимое.

   
Cookie oreo = new Cookie("TIJava", "2002");
res.addCookie(oreo);

Cookie получаются назад с помощью метода getCookies'( )<code> объекта <code>HttpServletRequest( ), который возвращает массив объектов cookie:

   
Cookie[] cookies = req.getCookies();

Затем вы можете вызвать getValue( ) для каждого cookie, чтобы получить стоку содержимого cookie. В приведенном выше примере метод getValue("TIJava") произведет строку, содержащую "2002".

Класс Session

Сессия - это одна или несколько страниц, запрошенных клиентом на Web сайте во время определенного периода времени. Если вы покупаете продукты в режиме он-лайн, например, вы хотите, чтобы ваша сессия ограничивалась периодом от того момента, когда вы в первый раз добавили элемент в "свою корзину покупок" до момента, когда вы подтвердите выбор. Каждый добавленный вами элемент в корзину покупок будет результатом нового HTTP соединения, которое не знает о предыдущих соединениях или элементах в корзине покупок. Чтобы компенсировать этот недостаток информации, механизм обеспечивает спецификацию cookie, позволяющую вашему сервлету выполнить отслеживание сессии.

Объект сессии сервлета живет на серверной стороне коммуникационного канала; его целью является сбор полезных данных об этом клиенте, таких как перемещение клиента по сайту и взаимодействие с вашим Web-сайтом. Эти данные могут относиться к текущей сессии и являться, например, элементами в корзине покупок, или это могут быть такие данные, как информация об авторизации, которая была введена клиентом при первом обращении к вашему Web-сайту и поэтому ее не нужно вводить еще раз во время определенного набора транзакций.

Класс Session API сервлета использует класс Cookie, чтобы выполнить эту работу. Однако все объекты Session нуждаются в уникальном идентификаторе определенного рода, хранящемся у клиента и передающемся на сервер. Web-сайты могут также использовать другие типы отслеживания сессии, но этот механизм будет более сложным для реализации, так как он не встроен в API сервлета (то есть, вы должны написать его руками, обработав ситуации, когда клиент отключает cookies).

Вот пример, который реализует отслеживание сессии с помощью API сервлета:

  
//: c15:servlets:SessionPeek.java
// Используем класс HttpSession.
// {Depends: j2ee.jar}
import java.io.*;

import java.util.*;

import javax.servlet.*;

import javax.servlet.http.*;

public class SessionPeek extends HttpServlet {
   public void service(HttpServletRequest req, HttpServletResponse res)
         throws ServletException, IOException {
      // Получаем объект Session до любой
      // посылки клиенту.
      HttpSession session = req.getSession();
      res.setContentType("text/html");
      PrintWriter out = res.getWriter();
      out.println("<HEAD><TITLE> SessionPeek ");
      out.println(" </TITLE></HEAD><BODY>");
      out.println("<h1> SessionPeek </h1>");
      // Простой щетчик обращений для этой сессии.
      Integer ival = (Integer) session.getAttribute("sesspeek.cntr");
      if (ival == null)
         ival = new Integer(1);
      else
         ival = new Integer(ival.intValue() + 1);
      session.setAttribute("sesspeek.cntr", ival);
      out.println("You have hit this page <b>" + ival + "</b> times.<p>");
      out.println("<h2>");
      out.println("Saved Session Data </h2>");
      // Цикл по всем данным сессии:
      Enumeration sesNames = session.getAttributeNames();
      while (sesNames.hasMoreElements()) {
         String name = sesNames.nextElement().toString();
         Object value = session.getAttribute(name);
         out.println(name + " = " + value + "<br>");
      }
      out.println("<h3> Session Statistics </h3>");
      out.println("Session ID: " + session.getId() + "<br>");
      out.println("New Session: " + session.isNew() + "<br>");
      out.println("Creation Time: " + session.getCreationTime());
      out.println("<I>(" + new Date(session.getCreationTime()) + ")</I><br>");
      out.println("Last Accessed Time: " + session.getLastAccessedTime());
      out.println("<I>(" + new Date(session.getLastAccessedTime())
            + ")</I><br>");
      out.println("Session Inactive Interval: "
            + session.getMaxInactiveInterval());
      out.println("Session ID in Request: " + req.getRequestedSessionId()
            + "<br>");
      out.println("Is session id from Cookie: "
            + req.isRequestedSessionIdFromCookie() + "<br>");
      out.println("Is session id from URL: "
            + req.isRequestedSessionIdFromURL() + "<br>");
      out.println("Is session id valid: " + req.isRequestedSessionIdValid()
            + "<br>");
      out.println("</BODY>");
      out.close();
   }
   
   public String getServletInfo() {
      return "A session tracking servlet";
   }
} // /:~

Внутри метода service( ) метод getSession( )<code> вызывается для объекта запроса, который возвращает объект <code>Session, ассоциированный с этим запросом. Объект Session не посылается по сети, вместо этого он живет на сервере и ассоциируется с клиентом и его запросом.

getSession( ) существует в двух версиях: без параметра, как использовано здесь, и getSession (boolean). getSession(true) эквивалентно вызову getSession'( ). Причbна для булевского значения состоит в объявлении состояния, с которым вы хотите создать объект сессии, если он не найден.getSession(true) более желательный вызов, отсюда появилась версия getSession( ).

Объект Session, если он не новый, может дать нам детальную информацию о клиенте, взяв ее из предыдущих визитов. Если объект Session новый, то программа начнет собирать информацию об активности этого клиента в этом визите. Сбор этой информации о клиенте выполняется с помощью методовsetAttribute( ) и getAttribute( ) объекта сессии.

  
java.lang.Object getAttribute(java.lang.String)
void setAttribute(java.lang.String name,  java.lang.Object value)

Объект Session использует простые пары имя-значение для загрузки информации. Имя является строкой, а значение может быть любым объектом, наследованным от java.lang.Object. SessionPeekследит за там, сколько раз клиент возвращался назад во время этой сессии. Это выполняется с помощью объекта Integer, называемого sesspeek.cntr. Если имx не найдено, создается Integer со значением равным единице, в противном случае Integer создается с инкрементированным значением по сравнению с предыдущим сохраненным Integer. Новый Integer помещается в объект Session. Если вы используете тот же самый ключ в вызове setAttribute( ), то новый объект переписывает старый. Инкрементированный счетчик используется для отображения количества визитов клиента во время этой сессии.

getAttributeNames( ) имеет отношение к getAttribute( ) <code>и <code>setAttribute( ); он возвращает перечисление имен объектов, которые включены в объект Session. Цикл while в SessionPeek показывает этот метод в действии.

Вы можете удивиться, как долго хранится объект Session. Ответ зависит от контейнера сервлетов, который вы используете; обычно, по умолчанию, это 30 минут (1800 секунд), что вы можете увидеть из вызова метода getMaxInactiveInterval( ) в ServletPeek. Тесты могут воспроизводить разные результаты в зависмиости от контейнера сервлетов. Иногда объект Session может храниться всю ночь, но я никогда не видел случая, когда объект Session исчезал в течение времени меньшего, чем указано в интервале не активности. Вы можете попробовать путем установки интервала не активности с помощью метода setMaxInactiveInterval( )<code> в значение 5 секунд и посмотреть, останется ли ваш объект<code> Session или он будет очищен в соответствующее время. Это может стать атрибутом, который вы захотите исследовать при выборе контейнера сервлетов.

Запуск примеров сервлета

Если вы еще не работали с Сервером приложений, который обрабатывает сервлеты Sun и JSP технологию для вас, вы можете загрузить Tomcat реализацию Java -сервлетов и JSP, которая является бесплатной реализацией с открытыми исходниками, санкционированная Sun. Tomcat можно найти на  jakarta.apache.org.

Следуйте инструкциям для установки Tomcat реализации, затем отредактируйте файл server.xml, чтобы указать место в вашем дереве директорий, где будут размещены ваши сервлеты. Когда вы запустите программу Tomcat, вы сможете протестировать ваши сервлеты.

Источники