Servlet. Создание динамического приложения.

В предыдущем примере мы сделали и запустили приложение, которое возвращает браузеру ответ в виде заранее созданного HTML файла.
Но что если мы хотим динамически менять содержимое страницы пользователя вместо того, чтобы отправлять заранее готовую страницу?
Давайте напишем небольшое веб-приложение, которое выводит имя пользователя в увеличенном регистре. На первой странице-приветствии мы предложим пользователю ввести свое имя и отправить его на сервер. Серверная часть выполнит преобразование имени и отправит готовый результат на клиентскую часть в виде отдельной HTML страницы.
Общая схема такая:



Разберем отличия этой схемы от схемы из первого примера.
Пользователь может использовать приложение, только используя браузер, возможности влиять на серверную логику напрямую у пользователя нет. Поэтому все, что происходит на стороне пользователя, назовем клиентской частью приложения. А сам браузер – клиентом. На клиентской стороне также могут выполняться определенные операции нашего приложения, например, скрипты на языке javascript.
Запрос от клиента (браузера) называется request. Ответ сервера – response. Браузер посылает запрос, а сервер – ответ, используя протокол передачи информации HTTP. Запрос может быть двух типов GET и POST.
В общем можно сказать, что любой запрос содержит в себе заголовок(header) со специальной информацией, такой как, например, кодировка передаваемого сообщения или IP адрес клиента, и само сообщение – та информация, которую мы посылаем в приложение.

В предыдущем примере в строке браузера мы вводили
http://localhost:8080/Test
Эта строка называется
URL (Unified Resource Locator). В дальнейшем будем использовать эту аббревиатуру для удобства.
Так вот при вводе данного URL браузер сформировал GET запрос и передал его на сервер. Содержимое GET запроса мы всегда можем увидеть в строке браузера – это и есть URL.
В отличие от GET запроса, POST запрос передается в скрытом виде и не отображается в браузере. С помощью него можно передавать большое количество информации, а также информацию, которую мы хотели бы оставить скрытой от пользователя, например пароль.

Я использую браузер FireFox и у него есть интересная возможность просматривать содержимое запросов и ответов (F12, монитор сети). Вот какой заголовок запроса был сформирован браузером когда мы вводили наш URL:


Вернемся к схеме.
Все что происходит на стороне сервера, назовем серверной частью приложения или серверной логикой.
Сервер передает запрос, полученный от клиента, в наше приложение Test. Как мы помним из первого примера точка входа в наше приложение – это файл web.xml, в котором есть список страниц-приветствий.
На схеме у нас появился новый элемент – Servlet(сервлет). Сервлет – это объект нашего приложения, который может обрабатывать приходящий запрос, производить какие-то действия и выдавать ответ для отображения на клиенте. Именно внутри сервлета мы получим введенное имя пользователя, обработаем его и вернем преобразованный ответ в браузер.
Внутри сервлета мы видим метод doGet(). Этот метод вызывается при получении GET запроса от браузера.


Создаем проект.

Создадим новый динамический веб-проект с именем TestServlet таким же образом, как и в первой главе.
 !Напомню, что мы условились работать на пятой версии java компилятора. Поэтому при создании проекта как и в первой главе необходимо указать значение версии Dynamic Web Module Version равным 2.3. Дело в том, что создание сервлета в java 1.7 отличается тем, что для его описания используется аннотация @WebServlet, а в пятой версии сервлет описывается в дескрипторе развертывания web.xml. И если вам в работе встретится более ранняя версия, то вы сможете без труда разобраться. Перейти на седьмую версию вам будет легко.
Создадим в папке WebContent файл index.html. Мы уже знаем, что этот файл также как и default.html входит в список файлов-приветствий приложения, расположенный в web.xml.
Изменим его содержимое следующим образом:

index.html
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Сервлет</title>
</head>
<body>

<form action="" method="GET">
    <p>Введите имя пользователя: <input type="text" name="username"></p>
    <input type="submit" value="Отправить" />
</form>

</body>
</html>


Тег <form action=”” method=”GET”> - это форма, содержимое которой будет отправляться на сервер. Параметр method – это типа запроса, также может быть POST. Параметр action указывает на адрес страницы, на которую мы будем передавать наши данные. В нашем случае он пустой, так как мы используем файл-приглашение index.html.
Тег <p>, </p> - определяет границы абзаца.
Тег <input type=”textname=”username”> - поле ввода текстовой информации. Мы также дали имя этому элементу для того, чтобы в дальнейшем получить значение этого поля из запроса. Если мы хотим получить значение введенное пользователем, то мы должны обязательно указать параметр name.
Тег <input type=”submitvalue=”Отправить”> - кнопка отправления формы. При нажатии на нее браузер сформирует и отправит запрос с именем пользователя на сервер.
Я также поменял содержимое тега <title>,</title> - текст, который отображается в названии вкладки браузера.
Добавим проект TestServlet на сервер и запустим его. Введем в строке браузера http://localhost:8080/TestServlet
 
Если ввести имя и нажать кнопку отправить, браузер создаст клиентский запрос, поместит в него значение поля username и передаст на сервер. Так как тип запроса GET, то мы увидим этот параметр в строке браузера сразу же, потому, что браузер изменит значение адресной строки:




Параметры GET запроса следуют после знака “?” и отделяются друг от друга знаком “&”.
Если бы мы передавали в форме два параметра, например, имя и возраст, то запрос мог быть таким:
Клиентский запрос попадает на сервер, и сервер возвращает содержимое страницы-приветствия index.html, то есть ту же самую страницу.

Сервлет

Теперь перед нами стоит задача написания серверной части.
Создадим в папке src пакет (Package).
Для этого кликнем правой кнопкой мыши по папке src во вкладке Package Explorer -> New -> Other -> Java -> Package -> Next



Назовем пакет com.testservlet, причем имена пакетов принято давать в малом регистре (маленькими буквами).
Это будет корневой пакет для java классов нашего проекта. Общепринято размещать классы проекта не в src, а создавать свою структуру пакетов внутри папки src. Причем если доменное имя вашего сайта такое www.mysite.com, то корневой пакет принято называть в обратной последовательности, то есть com.mysite.




Теперь создадим внутри нашего пакета сервлет. Правой кнопкой мыши кликаем по пакету com.testservlet -> New -> Web -> Servlet


 Назовем сервлет MyServlet и нажмем Finish.




Рассмотрим подробнее, что собой представляет созданный класс.
MyServlet.java


package com.testservlet.controller;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Servlet implementation class MyServlet
 */
public class MyServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;
       
    /**
     * @see HttpServlet#HttpServlet()
     */
    public MyServlet() {
        super();
        // TODO Auto-generated constructor stub
    }

    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // TODO Auto-generated method stub
    }

    /**
      * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
       // TODO Auto-generated method stub
    }

}

Как видно MyServlet наследует класс HttpServlet, который предоставлен библиотекой javax, входящей в пакет библиотек сервера Apache Tomcat.

Разберем построчно содержимое класса:


private static final long serialVersionUID = 1L;


Серийный номер версии класса. Значение сгенерировано eclipse автоматически. Используется для проверки при *сериализации/*десериализации объектов. Константа serialVersionUID может быть использована для синхронизации версий объектов класса MyServlet, если наше приложение будет расположено на *кластере серверов.

В нашем случае мы можем просто удалить эту строку, но тогда Eclipse будет выводить предупреждающее сообщение (warning message) о том, что в нашем классе MyServlet не определена константа, а это необходимо, так как иерархия класса HttpServlet реализует интерфейс java.io.Serializable. Если класс реализует этот интерфейс, то это является маркером для компилятора о возможности сериализовать его.

Если вас смущает предупреждающее сообщение среды разработки, и вы не хотите добавлять константу serialVersionUID в сервлет, можно просто перед строкой описания класса добавить аннотацию @SuppressWarnings(“serial”).
* Сериализация  - процесс преобразования состояния объекта в последовательность байт.
* Десериализация – процесс, обратный сериализации.
* Кластер серверов – объединение нескольких серверов в единую структуру для повышения производительности и масштабируемости приложения.


/**
* @see HttpServlet#HttpServlet()
*/
public MyServlet() {
    super();
    // TODO Auto-generated constructor stub
}

Это конструктор сервлета, и тут мы можем инициализировать какие-либо параметры, которые будут использоваться в процессе работы сервлета.


/**
 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
 */
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    // TODO Auto-generated method stub
}

Метод doGet вызывается в случае, если в сервлет поступает GET запрос. Именно этот метод мы будем использовать для обработки введенного пользователем имени и преобразования его. Здесь же мы сформируем страницу для вывода преобразованного значения имени.
Метод принимает два параметра request и response.
Request содержит в себе информацию, пришедшую со стороны клиента. В Response мы будем вносить информацию, которую отправим на клиента.




/**
 * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
 */
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    // TODO Auto-generated method stub
}



Метод doPost, как вы уже могли догадаться, вызывается в случае, если в сервлет поступает POST запрос. Его назначение точно такое же как и у предыдущего метода.
Кроме того в файл web.xml eclipse автоматически добавил описание сервлета:


<?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/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
  <display-name>TestServlet</display-name>
  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
    <welcome-file>default.html</welcome-file>
    <welcome-file>default.htm</welcome-file>
    <welcome-file>default.jsp</welcome-file>
  </welcome-file-list>
  
  <servlet>
    <description></description>
    <display-name>MyServlet</display-name>
    <servlet-name>MyServlet</servlet-name>
    <servlet-class>com.testservlet.MyServlet</servlet-class>
  </servlet>
  
  <servlet-mapping>
    <servlet-name>MyServlet</servlet-name>
    <url-pattern>/MyServlet</url-pattern>
  </servlet-mapping>
  
</web-app>


Нас здесь интересует параметр url-pattern. Его значение определяет адрес относительно базового URL, который используется для обращения к сервлету.
Если ранее мы обращались к странице приветствия по умолчанию так
http://localhost:8080/TestServlet/index.html или просто http://localhost:8080/TestServlet, так как index.html находится в списке страниц-приветствий, то для обращения к сервлету будем использовать такой URL: http://localhost:8080/TestServlet/MyServlet
Внесем изменения в код класса MyServlet.
1. Конструктор нам не понадобится, поэтому просто удалим его.
2. Метод doGet дополним таким образом:


protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
 String username = (String) request.getParameter("username");
 username = username.toUpperCase();
 response.getWriter().println("<!DOCTYPE HTML>");
 response.getWriter().println("<html><body><p>" + username + "</p></body></html>");
}


Мы извлекаем из поступившего в метод объекта request параметр с именем “username”. Напомню, в index.html именно это имя мы определили для поля ввода имени пользователя.
Затем преобразуем введенное имя в большой регистр.
Далее используем метод getWriter объекта response для вывода результата в виде html документа, который будет передан в браузер.
3. Добавим вызов метода doGet внутри метода doPost:


protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    doGet(request, response);
}

Это нам потребуется чуть позже, когда мы протестируем запрос POST.
Я также почистил код от неинформативных комментариев. Вот как выглядит класс MyServlet.java сейчас:


package com.testservlet;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@SuppressWarnings("serial")
public class MyServlet extends HttpServlet {

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String username = (String) request.getParameter("username");
        username = username.toUpperCase();
        response.getWriter().println("<!DOCTYPE HTML>");
        response.getWriter().println("<html><body><p>" + username + "</p></body></html>");
    }


    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
       doGet(request, response);
    }

}

Сделаем еще одно изменение в файле index.html – добавим значение параметра action, равное значению параметра url-pattern сервлета ( которое, как вы помните, описывается в web.xml) :


<form action="MyServlet" method="GET">

Запустим сервер или перезапустим, если он уже был запущен (Server->Restart, CTRL+ALT+R).
Возможно, вам также придется очистить кэш браузера для корректной работы.
Введем в строку браузера http://localhost:8080/TestServlet, затем в поле ввода - имя пользователя и нажмем кнопку ”Отправить”. Если все пройдет успешно, мы увидим исходный результат. У меня это выглядит так:

  Адресная строка содержит введенное мною имя “Алексей”, но на экране отображаются “кракозябры”. Все дело в кодировке. Наша страница index.html сконфигурирована для отображения информации в универсальной кодировке UTF-8. Но при поступлении GET запроса на сервер Tomcat информация о кодировке теряется, вернее сервер использует по умолчанию кодировку ISO-8859-1.
Для того чтобы правильно отображались русские буквы необходимо выполнить несколько шагов.

1. Найдем с помощью файлового менеджера конфигурационный файл сервера server.xml
и добавим в параметр Connector с используемым нами портом 8080 атрибут useBodyEncodingForURI=”true
2. Изменим также метод doGet() сервлета следующим образом:


protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    request.setCharacterEncoding("UTF-8");
    String username = (String) request.getParameter("username");
    username = username.toUpperCase();
    response.setContentType("text/html;charset=UTF-8");
    response.getWriter().println("<!DOCTYPE HTML>");
    response.getWriter().println("<html><body><p>" + username + "</p></body></html>");
}

Мы установили кодировку в начале метода для объекта request для того, чтобы получить значение параметра от клиента в правильном виде. То же самое сделали перед тем, как записать в объект response результат.

3. Для того чтобы сервер “подхватил” внесенные нами в конфигурацию изменения, придется удалить его и затем добавить заново. В контекстном меню сервера наживаем кнопку удалить (Delete)
И нажимаем “Ok”




Затем создаем сервер, добавляем в него наш проект TestServlet и запускаем.
На этот раз мы должны увидеть русский текст и результат работы нашего приложения:



Исходный код полученной страницы выглядит так:



Фактически внутри сервлета мы динамически создали html страницу, которую отобразил браузер.
Вообще передавать формы методом GET и используя русский шрифт неверный подход.
В данном случае – это лишь пример для понимания структуры запроса. Помимо прочего пользователь может видеть содержимое формы в строке браузера. Это не всегда нам удобно.
Данные форм правильней передавать в POST запросе. Что мы сейчас и сделаем.
Для этого в файле index.html заменим параметр action элемента form на POST:


<form action="MyServlet" method="POST">

И запустим приложение.


Результат:
Обратите внимание на то, что в строке браузера не отображается информация о заполненном нами поле формы в отличие от случая с GET запросом.


Скачать проект: TestServlet.zip


24 комментария:

  1. Почему нет продолжения очень полезные статьи

    ОтветитьУдалить
  2. у меня чего-то сервер не хочет запускаться после добавления параметра useBodyEncodingForURI=”true” в server.xml, версия AT 7.0.70

    ОтветитьУдалить
    Ответы
    1. Кавычки руками пропишите в атрибуте. Заработает.

      Удалить
  3. Этот комментарий был удален автором.

    ОтветитьУдалить
  4. решил проблему переустановкой Apache Tomcat, сразу внес изменения в web.xml

    ОтветитьУдалить
  5. Спасибо за полезную статью, особенно порадовала UML диаграмма в самом начале статьи, которая наглядно показала как генерируется запрос, и как он переходит в ответ) Спасибо автор!

    ОтветитьУдалить
  6. Одна из немногих понятных и доступных инструкций в интернете по сервлетам. Автору респект! Ждем продолжения. Желаю ему удачи!

    ОтветитьУдалить
  7. Этот комментарий был удален автором.

    ОтветитьУдалить
    Ответы
    1. Какого хрена нельзя просто изменять свой комментарий? ))

      Удалить
  8. Просто наикрутейшее пособие. Написано очень подробно, а главное доступно для понимания. Аффтар, жги есчо!

    ОтветитьУдалить
  9. Статья невероятно крутая!!! Огромнейшее вам спасибо! Я уж думала не разберусь!! У Вас талант))

    ОтветитьУдалить
  10. Этот комментарий был удален автором.

    ОтветитьУдалить
  11. Большое спасибо! Я никогда не встречал более удобного руководства, ни на одном другом сайте!!! Я не знаю, когда этот блог "закончился", но все равно желаю автору больших печенек и доброго здоровья! Эх, как бы хотелось продолжения...:)

    ОтветитьУдалить
  12. подскажите где это чудо можно расположить в сети без какого-либо оброка?

    ОтветитьУдалить
  13. как называеться приложение в котором розрабатывались сервлеты ?

    ОтветитьУдалить
  14. Все сделал как вы написали, всё отлично кракозябры убрались, на на их место пришни знаки вопросов. Не подскажите как их можно убрать ? Использую IntelliJ IDEA 2018.1.4 x64

    ОтветитьУдалить
    Ответы
    1. response.setContentType("text/html; charset=UTF-8");
      У меня после этого нормально вывело

      Удалить
  15. Все бы так подробно писали!

    ОтветитьУдалить
  16. Еще раз: СПАСИБО!
    Все получилось: жить стало веселей, "подлили воды на колесо НАДЕЖДЫ"!!!
    Надеюсь - все-таки осилить Java!

    ОтветитьУдалить
  17. Спасибо! Самое доходчивое и подробное объяснение по сервлетам. Все стало на свои места. Жаль, что нет продолжения.

    ОтветитьУдалить
  18. У Вас талант!!! Очень доступно описано!

    ОтветитьУдалить