IT • archiv

rus / eng | Логин | Комментарий к колонке | Печать | Почта | Клуб




Колонки


HTTP протокол и работа с Web в Java. Сервлеты

 
( Константин Андрухин )

Сервлеты являются специализированным механизмом Java для создания Web ресурсов.

Сервлеты входят в пакеты javax.servlet, javax.servlet.http, javax.servlet.jsp; пакеты эти, в свою очередь, принадлежат набору Java Servlet API, который входит в архитектуру Java 2 Enterprise Edition. Для работы с сервлетами вам потребуется либо J2EE, либо Java Servlet API, поставляемый вместе с такими Web серверами, как Jakarta Tomcat.

Что такое сервлеты? Сервлеты, фактически, это модули обработки HTTP и FTP запросов, используемые для построения порталов (Web gates).

Основой этих порталов является собственно Web сервер — программа, которая держит сокет сервера, принимает и передаёт данные. Чаще всего, для ускорения работы, сервер бывает написан не на Java, а на каком-либо другом языке программирования (например на C++).

В связке с сервером работает базовый сервлет. Именно ему отправляет сервер данные и от него же получает ответ, отправляемый клиенту. Фактически, базовый сервлет является "мозгом" сервера. Основная функция этого сервлета — прочитать запрос клиента, расшифровать его и, в соответствиии с расшифровкой, передать работу сервлету, отвечающему за этот тип запрашиваемой информации. Зачастую, для достижения скорости, роль базового сервлета играет сам сервер. Именно по такой схеме работает, скажем, Jacarta Tomcat.

Схема работы сервлетов и сервера.
Рисунок 1. Схема работы сервлетов и сервера.

На рисунке изображена схема передачи вызовов (request) и ответов (response) между сервером и сервлетами. Данная схема изображает работу HTTP сервера, который имеет несколько JSP страниц и два ресурса "/sample1" и "/sample2", за обработку которых отвечает два сервлета — "Sample1 Servlet" и "Sample2 Servlet" соответственно.

Разберём пошагово то, что изображено на рисунке:

  1. клиент подсоединяется к серверу
  2. сервер передаёт запрос (request) базовому сервлету ("Basic Servlet")
  3. базовый сервлет вычеленяет из запроса URI ресурса
    • если URI указывает на "/sample1", то запрос целиком (без изменений) передаётся сервлету "Sample1 Servlet", который, в дальнейшем, и обрабатывает этот запрос
    • если URI указывает на "/sample2", сервер передаёт запрос сервлету "Sample2 Servlet"
    • во всех остальных случаях запрос передаётся модулю "JSP Servlet"
  4. сервлет, которому было передано управление, обрабатывает данные, создаёт ответ (response), после чего ответ отсылается обратно базовому сервлету.
  5. базовый сервлет, не обрабатывая полученные данные, тут же пересылает их обратно серверу
  6. сервер выдаёт данные клиенту

Таким образом достигается разбиение задачи обработки запроса на логические части, за каждую из которых отвечает свой модуль, свой "програмный кирпичик". На самом деле, ступеней в обработке запроса может быть гораздо больше. К примеру за методы "GET" и "POST" могут отвечать разные модули.

Интерфейс Servlet

Объединяет все эти модули то, что они сквозным образом связанны между собою с помощью интерфейса javax.servlet.Servlet

Посмотрим на этот интерфейс. В нём указано всего 5 методов:

public void init(ServletConfig config)
 throws ServletException

Этот метод вызывается, чтобы проинформировать сервлет о том, что он включён как модуль для обслуживания запросов клиента. Параметр config разделяет интерфейс javax.servlet.ServletConfig, несущий информацию об окружении сервера, имени сервлета, начальных параметрах и прочих плюшках. Об интерфейсе javax.servlet.ServletConfig будет рассказано чуть далее. Предполагается, что после вызова этой функции, сервлет аккуратно сохранит этот config у себя в переменной и будет выдавать его с помощью другого метода:

public ServletConfig getServletConfig()

Получив системную информацию с помощью "getServletConfig()", сервер может захотеть узнать имя автора, дату создания, прочую информацию о сервлете, что и достигается вызовом

public String getServletInfo()

Чтобы обработать запрос и получить результат его обработки, используется функция

public void service(ServletRequest request,
    ServletResponse response)
    throws ServletException, java.io.IOException

В этой функции коду, который будет обрабатывать данные, передаются два инструмента: один — для получения данных от сервера, другой — для отправки результата работы сервлета. Соответственно это параметры request и response, разделяющие интерфейсы javax.servlet.ServletRequest и javax.servlet.ServletResponse Вся работа с данными ведётся именно через эти интерфейсы, так что далее поговорим о них подробнее.

После того, как сервер перестал нуждаться в этом модуле вызывается метод

public void destroy()

который и завершает все операции с объектом сервлета.

Интерфейс ServletConfig

4 метода, имена которых говорят сами за себя, составляют суть интерфейса javax.servlet.ServletConfig:

public String getServletName()
public ServletContext getServletContext()
public String getInitParameter(String name)
public java.util.Enumeration getInitParameterNames()

Думаю, назначение всех функция понятно, кроме

public ServletContext getServletContext()

Этот метод возвращает ссылку на очень полезный инструмент для работы с сервером:

Интерфейс ServletContext

ServletContext — интерфейс, определяющий доступ к следующим полезнейшим функциям:

public Object getAttribute(String name)
public java.util.Enumeration getAttributeNames()
public void setAttribute(String name, Object object)
public void removeAttribute(String name)

Четыре метода для работы с аттрибутами. Роль аттрибутов выполняет любой объект любого класса. Цель данных функций — пересылать между несвязанными друг с другом сервлетами разные объекты.

public String getInitParameter(String name)
public java.util.Enumeration getInitParameterNames()

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

public int getMajorVersion()
public int getMinorVersion()

Возвращает версии Servlet API.

public String getMimeType(String file)

Возвращает MIME тип ассоциированный с файлом, путь до которого указан в переменной file. Вспомните, как пришлось определять MIME в программе SimpleWebServer и оцените удобство!

public java.util.Set getResourcePaths()
public java.net.URL getResource(String path)
	throws java.net.MalformedURLException
public InputStream getResourceAsStream(String path)

Возвращает пути к доступным для сервера ресурсам и сами ресурсы в виде URL и в виде потоков данных.

public RequestDispatcher getRequestDispatcher(path)
public RequestDispatcher getNamedDispatcher(name)

RequestDispatcher — это инструмент для того, чтобы переслать запрос другому ресурсу. Эти функции нужны, чтобы получить объект этого инструмента для указанных ресурсов. То бишь, скажем, для того, чтобы перенаправить запрос сервлету "sample1" из тела сервлета, можно сделать так:

getServletConfig().getServletContext().getNamedDispatcher(
 "sample1").forward(request, response);

Собственно класс RequestDispatcher включает в себя лишь два метода:

public void forward(ServletRequest request, ServletResponse response)
    throws ServletException, java.io.IOException

public void include(ServletRequest request, ServletResponse response)
    throws ServletException, java.io.IOException

Причём первый — для перенаправления запроса, а второй — для включения результата работы вызываемого сервлета в результат работы текущего. К примеру, сервлет 1 печатает слово "test 1", потом вызывает include для сервлета два, после чего печатает слово "test 2". Сервлет 2 же просто печатает слово " and ". Результатом работы сервлета 1 будет строка "test 1 and test 2".

public void log(String msg)

Записать что-то в лог сервера.

public void log(String message, Throwable throwable)

Определить исключение и фразу, которая будет записываться в лог по получении этого исключения.

public String getRealPath(String path)

Переводит путь типа "/" в "http://host/contextPath/"

public String getServerInfo()

Возвращает имя сервера.

public ServletContext getContext(String uripath)

Этот метод позволяет обмениваться ServletContext между разными ресурсами одного и того же сервера.

public String getServletContextName()

Возвращает имя сервлета, которому принадлежит данный объект интерфейса ServletContect.

Интерфейс ServletRequest

Интерфейс ServletRequest — это инструмент для получения параметров HTTP запроса. Этот интерфейс имеет некоторые методы, идентичные по названию и назначению с ServletContext:

public Object getAttribute(String name)
public java.util.Enumeration getAttributeNames()
public void setAttribute(String name, Object o)
public void removeAttribute(java.lang.String name)
public String getServerName()
public RequestDispatcher getRequestDispatcher(String path)

Оставшися методы позволяют с удобством работать с HTTP заголовком запроса:

public String getCharacterEncoding()
public void setCharacterEncoding(String env)
	throws java.io.UnsupportedEncodingException

Работа с кодировкой символов в полях HTTP заголовка. Функции задают метод расшифровки CGI запросов из формы %NN в обычные символы. К примеру, какой стандарт — KOI8-R, windows-1251 или UTF-8 нужно применить для расшифровки кириллических символов.

public int getContentLength()
public String getContentType()

Читает поля "Content-Length", "Content-Type" из HTTP запроса.

public jString getParameter(String name)
public java.util.Enumeration getParameterNames()
public String[] getParameterValues(String name)
public java.util.Map getParameterMap()

Функции для получения поля из HTTP заголовка и его значения.

public ServletInputStream getInputStream()
	throws java.io.IOException
public java.io.BufferedReader getReader()
	throws java.io.IOException

Получить входящий поток данных или его "читатель". Reader применяется для чтения текстовой информации — он автоматически расшифрует строки в соответствии с заданным charset. Внимание! В версии J2EE 1.3 имеется существенный баг: при расшифровке символа %25 (символ % в Post и Get запросах) Reader выдаёт ошибку (баг замечен на серверах Tomcat 4 и Resign). Возможно, что схожий баг есть и с другими символами.

public String getProtocol()

Получить версию HTTP протокола запроса (к примеру — "HTTP/1.1").

public String getScheme()

Возвращяет имя схемы запроса. Например "http", "https", или "ftp".

public int getServerPort()
public String getRemoteAddr()
public String getRemoteHost()
public boolean isSecure()

Порт сервера, IP адрес клиента, имя хоста клиента и является ли соединение скретным (по протоколу HTTPS)

public java.util.Locale getLocale()
public java.util.Enumeration getLocales()

Предпочитаемый клиентом язык документа (результат обработки поля "Accept-Language")

Интерфейс ServletResponse

Интерфейс ServletResponse — это инструмент для отправки данных клиенту. Все методы данного инструмента служат именно этой цели:

public java.lang.String getCharacterEncoding()
public void setLocale(java.util.Locale loc)
public java.util.Locale getLocale()

Первый метод возвращает MIME тип кодировки (к примеру — UTF8), в которой будет выдаваться информация. Вторые два метода тоже работают с charset. Они указывают на язык используемый в документе (например — русский).

public ServletOutputStream getOutputStream()
	 throws java.io.IOException

Возвращает поток вывода данных для сервлета. Этот поток используется, к примеру, для вывода бинарных файлов. Текстовые данные можно выводить с помощью java.io.Writer:

public java.io.PrintWriter getWriter()
	throws java.io.IOException

Этот метод автоматически конвертирует строки в тот charset, что указан в методе getCharacterEncoding() и getLocale().

public void setContentLength(int len)

Этим методом устанавливается значение поля HTTP заголовка "Content-Length"

public void setContentType(String type)

Метод для отправки MIME типа содержимого документа. Поле HTTP заголовка "Content-Type".

public void setBufferSize(int size)
public int getBufferSize()
public void flushBuffer() throws java.io.IOException
public void resetBuffer()

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

public boolean isCommitted()

Этим методом можно получить флаг, начата ли уже отправка данных клиенту. Флаг будет положительным, если HTTP заголовок ответа был уже отправлен.

public void reset()

Если HTTP заголовок ещё не отправлен, то этот метод "сбрасывает" HTTP заголовок к значениям "по умолчанию".

Предопределённые типы сервлетов

Java Servlet API, кроме собственно интерфейсов, так же содержит несколько классов сервлетов, которые могут служить основой для ваших программ.

GenericServlet

Базовым для всех этих классов является абстрактный класс javax.servlet.GenericServlet:

public abstract class GenericServlet
  implements Servlet, ServletConfig, java.io.Serializable

Как видно из определения этого класса, он имеет все методы интерфейсов Servlet и ServletConfig. Не реализованным методом остался только

public abstract void service(ServletRequest req, ServletResponse res)
                 throws ServletException, java.io.IOException

который и был объявлен абстрактным.

HttpServlet

На базе этого класса был создан другой абстрактный класс - javax.servlet.http.HttpServlet:

public abstract class HttpServlet
	extends GenericServlet implements java.io.Serializable

Создан этот класс был в соответствии с концепцией "ещё больше удобств для программиста" и имеет много полезных методов:

protected void doDelete(HttpServletRequest req, HttpServletResponse resp)
                 throws ServletException, java.io.IOException
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
                 throws ServletException, java.io.IOException
protected void doHead(HttpServletRequest req, HttpServletResponse resp)
                 throws ServletException, java.io.IOException
protected void doOptions(HttpServletRequest req, HttpServletResponse resp)
                 throws ServletException, java.io.IOException
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
                 throws ServletException, java.io.IOException
protected void doPut(HttpServletRequest req, HttpServletResponse resp)
                 throws ServletException, java.io.IOException
protected void doTrace(HttpServletRequest req, HttpServletResponse resp)
                 throws ServletException, java.io.IOException
protected void service(HttpServletRequest req, HttpServletResponse resp)
                 throws ServletException, java.io.IOException
protected void service(ServletRequest req, ServletResponse res)
                 throws ServletException, java.io.IOException

Различные варианты service(ServletRequest req, ServletResponse res) для разных HTTP методов от DELETE и GET до PUT и TRACE. А чтобы с удобством получать данные по CGI интерфейсу не расшифровывывая заголовок были созданы классы HttpServletRequest и HttpServletResponse, входящие вместе с HttpServlet в пакет javax.servlet.http

protected long getLastModified(HttpServletRequest req)

Этот метод возвращает время последней модификации объекта HttpServletRequest. Значение времени он берёт из поля "Date" HTTP заголовка запроса. Если же поле не обнаружено, то возвращает -1.

Соответственно разберём и интерфейсы HttpServletRequest и HttpServletResponse. Они являются наследниками соответственно ServletRequest и ServletResponse.

Интерфейс HttpServletRequest

HttpServletRequest помимо методов, унаследованных от ServletRequest, имеет так же следующие полезнейшие методы:

Cookie[] getCookies()

Возвращает набор куков, пересланных клиентом серверу.

Класс Cookie

Класс Cookie, входящий в тот же пакет javax.servlet.http, содержит всю возможную информацию о куке. Важнейшими методами этого класса являются

int getMaxAge()
String getName()
String getValue()

выдающие, соответственно, сколько ещё времени этому куку осталось жить, имя кука и его значение. Так же

Cookie(String name, String value)
void setValue(String newValue)
void setMaxAge(int expiry)

для создания кука, установки его значения и максимального возраста.

long getDateHeader(String name)

Возвращает дату из HTTP заголовка, если таковая есть.

int getIntHeader(java.lang.String name)

Возвращает численное значения поля с именем name из HTTP заголовка запроса

String getMethod()

Возвращает метод HTTP запроса.

String getQueryString()
String getRequestURI()
StringBuffer getRequestURL()

Возвращает строку, содержащуюся в URL документа после символа "?", URI документа и полный URL.

HttpSession getSession()
HttpSession getSession(boolean create)
boolean isRequestedSessionIdFromCookie()
boolean isRequestedSessionIdFromURL()
boolean isRequestedSessionIdValid()

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

Сессии

Сессии необходимы для того, чтобы таскать за пользователем данные из страницы в страницу. К примеру, пользователь заходит на страницу (1), где ему отправляются некоторые данные для страницы (2), а та сохраняет ещё какие-то вещи для страницы (3).

В принципе, на странице (1) можно выслать данные пользователю, потом получить их на странице (2), добавить что-то, выслать пользователю... Подобным образом придётся постоянно пересылать весь набор данных от клиента серверу и обратно, причём много раз. Кроме того, что такая пересылка не всегда удобна, она ещё и пожирает траффик.

Механизм передачи данных между страницами без сессий.
Рисунок 2. Механизм передачи данных между страницами без сессий.

Можно так же поступить иначе — использовать механизм сессий. Механизм этот работает следующим образом: данные, присланные пользователем, сервер сохраняет в отдельном файле - файле сессии. С содержимым этого файла и будет производиться вся работа по изменению данных. Клиенту же выдаётся "ключ сессии" (он же Session key, он же Sesseion ID) — уникальный указатель на файл, содержащий данные конкретно для этого пользователя. Теперь для того, чтобы получить все данные, касающиеся этого клиента, серверу необходимо знать лишь ключ сессии. Достоинством этого метода является удобство и скорость его использования.

Механизм передачи данных между страницами с помощью сессий.
Рисунок 3. Механизм передачи данных между страницами с помощью сессий.

Вот и все основные методы интерфейса HttpServletRequest. Полный список методов читайте в документации к Java Servlet API.

Интерфейс HttpServletRequest

Теперь об интерфейсе HttpServletRequest. Основное отличие классов, разделяющих данный интерфес, в том, что данные выводятся не сразу. Вначале происходит компановка всех данных в HTTP ответ. Ответ отправляется только после завершения работы HttpServlet.service().

И так, о методах:

void addHeader(String name, String value)
void addIntHeader(String name, int value)
void addDateHeader(String name, long date)

Методы добавляют в HTTP заголовок параметры. Последний метод устанавливает параметр "Date".

void addCookie(Cookie cookie)

Метод добавляет cookie в заголовок

boolean containsHeader(String name)

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

String encodeURL(String url)
String encodeRedirectURL(String url)

Первый метод кодирует символы с помощью замены %NN. Второй метод делает то же самое и вызывает

void sendRedirect(String location)
void setStatus(int sc)
void sendError(int sc)
void sendError(int sc, String msg)

Первый — устанавливает код возврата, вторые два — посылают сообщение об ошибке. В интерфейсе заданы следующие возможные ошибки для параметра sc, соответствующие кодам возврата протокола HTTP:

SC_CONTINUE — Status code (100)
SC_SWITCHING_PROTOCOLS — Status code (101)

SC_OK — Status code (200)
SC_CREATED — Status code (201)
SC_ACCEPTED — Status code (202)
SC_NON_AUTHORITATIVE_INFORMATION — Status code (203)
SC_NO_CONTENT — Status code (204)
SC_RESET_CONTENT — Status code (205)
SC_PARTIAL_CONTENT — Status code (206)

SC_MULTIPLE_CHOICES — Status code (300)
SC_MOVED_PERMANENTLY — Status code (301)
SC_MOVED_TEMPORARILY — Status code (302)
SC_SEE_OTHER — Status code (303)
SC_NOT_MODIFIED — Status code (304)
SC_USE_PROXY — Status code (305)

SC_BAD_REQUEST — Status code (400)
SC_UNAUTHORIZED — Status code (401)
SC_PAYMENT_REQUIRED — Status code (402)
SC_FORBIDDEN — Status code (403)
SC_NOT_FOUND — Status code (404)
SC_METHOD_NOT_ALLOWED — Status code (405)
SC_NOT_ACCEPTABLE — Status code (406)
SC_PROXY_AUTHENTICATION_REQUIRED — Status code (407)
SC_REQUEST_TIMEOUT — Status code (408)
SC_CONFLICT — Status code (409)
SC_GONE — Status code (410)
SC_LENGTH_REQUIRED — Status code (411)
SC_PRECONDITION_FAILED — Status code (412)
SC_REQUEST_ENTITY_TOO_LARGE — Status code (413)
SC_REQUEST_URI_TOO_LONG — Status code (414)
SC_UNSUPPORTED_MEDIA_TYPE — Status code (415)
SC_REQUESTED_RANGE_NOT_SATISFIABLE — Status code (416)
SC_EXPECTATION_FAILED — Status code (417)

SC_INTERNAL_SERVER_ERROR — Status code (500)
SC_NOT_IMPLEMENTED — Status code (501)
SC_BAD_GATEWAY — Status code (502)
SC_SERVICE_UNAVAILABLE — Status code (503)
SC_GATEWAY_TIMEOUT — Status code (504)
SC_HTTP_VERSION_NOT_SUPPORTED — Status code (505)

Вот и всё, что можно рассказать о HttpServletResponse

Использование сервлетов в Web приложениях

Поговорим теперь об использовании сервлетов в Web приложениях. Для этого я приведу два полезных примера, которые могут на практике пригодиться.

GZIP output

Первый пример показывает методы работы с HttpServlet и вывод содержимого HTML страницы в сжатом виде. По идее, HTML страница в ответе браузера выводится прямым текстом, но, чтобы сократить объём пересылаемых данных, можно использовать сжатие GZIP. Современные браузеры (по крайней мере браузеры 4 поколения и выше) поддерживают такой метод пересылки текстовой информации и выведут страницу так, как будто бы её и не сжимали.

Файл ZipServlet.java:

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.util.zip.*;

// сервлет является наследником HttpServlet
public class ZipServlet extends HttpServlet
{
    // функция обработки метода GET
    public void doGet(HttpServletRequest request,
                      HttpServletResponse response)
		      throws ServletException, IOException
    {
        // устанавливаем, что страничка является HTML документом
        response.setContentType("text/html");

        // берём параметр "Accept-Encoding" из HTTP заголовка
        String encodings = request.getHeader("Accept-Encoding");

        // берём параметр "encoding" — ранее заданная кодировка документа
        String encodeFlag = request.getParameter("encoding");

        // Куда будем выводить
        PrintWriter out;

        // если поле "Accept-Encoding" в запросе присутствует
        if(encodings != null)
        {
            // и если это поле содержит значение "gzip",
            // а кодировка ещё не была установлена,
            if((encodings.indexOf("gzip") != -1)
             &&!encodeFlag.equals("none"))
            {
                // то то, куда будем выводит, будет за одним
                // и сжимать текст с помощью GZIP
                out = new PrintWriter(new GZIPOutputStream(
                 response.getOutputStream()), false);

                // и устанавливаем флаг для браузера,
                // что документ будет сжат
                response.setHeader("Content-Encoding", "gzip");
            }
            else   // в противном случае выводить будем без сжатия
                out = response.getWriter();
        }
        else    // в противном случае выводить будем без сжатия
                out = response.getWriter();


        out.println("This a test!!!"); // пишем тело документа
        out.close(); // и закрываем вывод.

        //Всё, по завершению работы этой ф-ии,
        // документ будет отправлен
    }
}

Непрерывное соединение сервером

Второй пример показывает, как средствами сервлета можно осуществить непрерывный вывод страницы. Такой тип вывода страниц может использоваться, например, в чатах: чтобы прочитать новые сообщения, не нужно будет каждый раз обновлять страницу, новые сообщения будут просто со временем докачиваться. Нужно учесть, что некоторые прокси-серверы не поддерживают такой вид пересылки данных, но с этим — увы - ничего сделать нельзя.

Файл DoloadServlet.java:

import java.io.*;
import javax.servlet.*;

// программа реализует интерфейс Servlet
class DoloadServlet implements Servlet
{
    ServletConfig config; // объект ServletConfig

    public DoloadServlet() {} // ничего не делает

    // при инициализации сохраняем config
    public void init(ServletConfig config) throws ServletException
    {this.config = config;}

    // выдаёт сохранённый config
    public ServletConfig getServletConfig() {return config;}

    // информация о сервлете
    public String getServletInfo() {return "DoloadServlet";}

    public void destroy() {} // ничего не делает

    // обработка запроса
    public void service(ServletRequest request, ServletResponse response)
                  throws ServletException, java.io.IOException
    {
        // разбирать запрос мы не будем, просто срязу
        // создаём HTTP заголовок:

        String head = "HTTP/1.0 200 OK\n"+
                + "Server: DoloadServlet\n"
                + "Content-Type: text/html; charset=windows-1251\n"
                + "Connection: Keep-Alive\n"
                + "Content-Encoding: multipart/mixed\n"
                + "Transfer-Encoding: chunked"
                + "Pragma: no-cache\n\n";

        // теперь добавляем первоначальные данные
        // для этого примера — 20 тэгов "<br>" с переносом строки
        for(int i = 0; i < 20; i++) head = head + "<br>\n";

        // берём поток вывода
        ServletOutputStream os = response.getOutputStream();

        // пишем туда заголовок и первоначальные данные
        os.print(head);

        // отправляем всё записаное в буффер к клиенту
        response.flushBuffer();

        // начинаем добавлять новые строки:
        // эти строки будут выглядеть следующим образом:
        // номер строки, потом "<br>\n"
        // каждая новая строка будет появляться раз в 3 секунды
        int i = 0;
        while(true)
        {
            // инкримент счётчика
            i++;

            // пишем строку
            os.print(""+i+"<br>\n");

            // сброс буффера
            response.flushBuffer();

            // примораживаем поток на 3 секунды
            try {sleep(3000);}
            catch(Exception e){}
        }
    }
}

Проблемы использования

Осталось сказать, что механизм сервлетов очень пластичен и позволяет творить такие вещи, которые могли бы потребовать написания отдельного Web сервера (как, например, в случае сервлета докачки). Минусом работы сервлетов является низкая скорость первого запуска (сервлет просто компилируется JIT машиной), высокое поребление памяти и недостаток всех программ на Java — низкая скорость работы со строками. Последнее обстоятельство становится заметно при работе сервлетов, принимающих текстовые данные в POST запросах. POST запрос в HttpServlet размером в 50 кб при парсинге с помощью HttpServletRequest.getReader() может на пару минут парализавать работу сервера. То же относится и к другим программам на java.

Приведу два небольших примера:

// дана строка String text

// пример 1
// идёт работа со строкой с помощью операции "+" для String
String test1 = "";
for(int i = 0; i < text.length(); i++)
    test1 += text.charAt(i);

// пример 2
// идёт работа со строкой посредством буффера
char buf[] = new char[text.length()];
for(int i = 0; i < text.length(); i++)
    buf[i] = text.charAt(i);
String sample2 = new String(buf);

Если взять небольшие строки — до 2-3 кб, то отличия в работе примеров несущественны, если же взять строку text размером хотя бы в 10 кб, то в первом случае программа будет работать со строкой значительно медленнее. Это является особенностью java и является проблемой реализации функций класса String. Так что если вы хотите написать быстрый сервлет, избегайте работу с длинными строками посредством класса String, используйте, к примеру, класс StringBuffer. Это предупреждение относится прежде всего к получению больших текстов из сети и к обработке локальных файлов (к примеру, в случае текстовой базы данных для гостевой книги при большом количестве сообщений).

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

Дополнительно к мультизадачности хочу отметить, что с помощью методов "Object getAttribute(String name)" и "void setAttribute(String name, Object object)" интерфейса ServletContext вы можете обмениваться между сервлетами данными, в том числе и синхронизирующими.

Заключение

Статья эта не предполагалась такой длинной: она выросла из статьи для журнала "PAX-T", рассказывающей про HTTP протокол. Путём расширения статьи для сайта "Developer Resource", она выросла в некое руководство по низкоуровневому программированию для WEB.

Когда писался данный опус, я постарался не просто перевести соответствующую документацию или дать набор приёмов для "правильного" программирования, а объяснить, что, где, как и почему применяется. Так же постарался максимально подробно осветить затронутые темы, не увлекаясь, впрочем, излишними деталями. О них вы можете прочитать в документах, ссылками на которые пестрит данная статья.

Полный текст статьи, включая все примеры, вы можете взять тут — java-http-web.zip. Об ошибках в ссылках, в программах и в тексте рапортуйте автору, я постараюсь их исправить.




Справка | Условия Copyright © 1999 — 2010, IT • archiv.
В начало | Логин | Комментарий к колонке | Поиск | Почта