Основы сервлетов
Архитектура 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 — HttpServlet один из тех классов, которые вы будете использовать чаще всего.
Наиболее удобным инструментом сервлетного API является внешние объекты, которые идут вместе с классом HttpServlet для его поддержки. Если вы взглянете на метод service( ) из интерфейса Servlet, вы увидите, что он имеет два параметра: ServletRequest и ServletResponse. У класса HttpServlet эти два объекта расширяются на HTTP: HttpServletRequest и HttpServletResponse. Вот простой пример, который показывает использование HttpServletResponse:
//: c15:servlets:ServletsRule.java
import javax.servlet.*; import javax.servlet.http.*; import java.io.*;
public class ServletsRule extends HttpServlet { int i = 0; // Servlet "persistence"
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(); } } ///:~
ServletsRule настолько прост, насколько может быть прост сервлет. Сервлет инициализиуется только однажды путем вызова своего метода init( ) при загрузке сервлета после того, как сначала загрузиться контейнер сервлетов. Когда клиент делает запрос к URL, соответствующий представленному сервлету, контейнер сервлетов перехватывает этот запрос и делает вызов метода service( ), затем устанавливает объекты HttpServletRequest и HttpServletResponse.
Главная забота метода service( ) состоит во взаимодействии с HTTP запросом, посланным клиентом, и построение HTTP ответа, основываясь на аттрибутах, содержащихся в запросе. ServletsRule манипулирует объектом ответа не зависимот от того, что мог послать клиент.
После установки типа содержимого ответа (что всегда должно быть сделано перед созданием Writer или OutputStream), метод getWriter( ) объекта ответа производит объект PrintWriter, который используется для написания символьного ответа (другой подход: getOutputStream( ) производит OutputStream, используемы для бинарного ответа, который походит только для специальных решений).
Оставшаяся часть программы просто посылает клиенту HTML (тут предполагается, что вы понимаете HTML, так что эта часть не объясняется) в виде последователности String. Однако, обратите внимание на включение “счетчика посещений”, представленного переменной i. Здесь выполняется автоматическая конвертация в String в инструкции print( ).
Когда вы запустите программу, вы увидите, что значение i сохраняется между запросами к сервлету. Это важное свойство сервлетов: так как только один сервлет определенного класса загружается в контейнер, и он никогда не выгружается (только в случае, если контейнер сервлетов завершает работу, что обычно случается, если вы перезагружаете машину), любые поля сервлета этого класса загружаются в контейнер и становятся устойчивыми объектами! Это означает, что вы можете без особого труда сохранять значения между запросами к сервлету, а при использовании CGI вы должны записывать значения на диск, чтобы сохранить его, что требует некоторое количество искусственности, а в результате получаем ен кросс-платформенное решение.
Конечно иногда Web сервер и, соответственно, контейнер сервлетов должен быть перегружен как часть процесса поддержки или из-за проблем с питанием. Для предотвращения потери любой пристутствующей информации автоматически вызываются методы сервлета init( ) и destroy( ) при любой загрузке или выгрузке сервлета, что дает вам возможность сохранить данные при выключении и восстановить их после перезагрузки. Контейнер сервлетов вызывает метод destroy( ), как только он прекращает работу, так что вы всегда имеете удобный случай сохранить важные данные.
Есть еще одна проблема при использовании HttpServlet. Этот класс имеет методы doGet( ) и doPost( ), которые отличаются от метода “GET” CGI получения от клиента, и метода CGI “POST”. GET и POST отличаются только в деталях способами, которыми они передают данные, что лично я предпочитаю игнорировать. Однако чаще всего публикуется информация, из того, что видел я, которая одобряет создание отдельных методов doGet( ) и doPost( ) вместо единого общего метода service( ), который обрабатывает оба случая. Это предпочтение кажется достаточно общим, но я никогда не видел объяснения, которое заставило бы меня поверить, что это не просто интертность мышления CGI программистов, которые привыкли обращать внимание, используется ли GET или POST. Так что в духе “упрощения всего насколько это возможно”[75], я буду использовать метод service( ) в этих примерах, и пусть он сам заботиться о GET'ах и POST'ах. Однако держите в уме, что я что-то упустил, что, возможно, является хорошей причиной в польху использования doGet( ) и doPost( ).
В любое время, когда форма передается сервлету, HttpServletRequest предварительно загружает все данные формы, хранящиеся в виде пар ключ-значение. Если вы знаете имена полей, вы можете просто использовать их напрямую с помощью метода getParameter( ) для получения значения. Вы также можете получить Enumeration (старая форма Iterator) на имена полей, как показано в следующем примере. Этот пример также демонстрирует как один сервлет может быть использован для генерации страницы, которая содержит форму, и для ответа на страницу (более удобное решение будет показано позже, при рассмотрении JSP). Если Enumeration пустое, значит полей нет. Это значит, что никакой формы не было передано. В этом случае содается форма, а кнопка посылки повторно вызывает этот же сервлет. Если же поля существуют, они показываются.
//: c15:servlets:EchoForm.java
// Дамп пар имен-значений из любой формы HTML
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 плохо выполняет обработку строк. В этом случае для вас, вероятно, лучше будет использовать более подходящее решение (Python может быть вашим выбором. Есть версии, которые встраиваются в Java, называемые JPython) для генерации ответной страницы.