On-Line Библиотека www.XServer.ru - учебники, книги, статьи, документация, нормативная литература.
       Главная         В избранное         Контакты        Карта сайта   
    Навигация XServer.ru


Лекарства для профилактики хронической экземы radevit.ru.




 

Гномы серверов.

Дмитрий Рамодин


Рис. 1

Если вам необходимо раcширить функциональные возможности сервера Web, можно написать CGI-сценарий. А можно разработать и установить расширение на основе ISAPI. Да мало ли чего еще можно придумать! А если нравится писать программы на Java, считайте, что вам повезло. Ознакомьтесь с несколькими классами из набора Java Servlet Development Kit (JSDK) - и вперед. С помощью JSDK можно разрабатывать так называемые сервлеты - специальные программы, выполняющиеся в рамках серверов, способные обрабатывать сложные клиентские запросы и динамически генерировать ответы на них. Примером использования сервлетов может служить расширение, читающее запрос на языке SQL, анализирующее его и делающее выборку данных из хранилища, а также пересылающее клиенту HTML-страницу, сгенерированную автоматически на основе полученных данных (рис. 1).

Преимуществом сервлетов можно считать то, что они пишутся на объектно-ориентированном языке высокого уровня, к которому имеется масса дополнений и программных интерфейсов, а это значительно увеличивает область применения расширений на основе сервлетов. Если у вас есть готовая серверная логика, написанная на Java, то превратить ее в сервлет легче легкого. Вся библиотека классов языка Java у вас в руках! Подключать и настраивать сервлеты также несложно.

Наибольшее распространение получили сервлеты, обрабатывающие запросы по протоколу HTTP - стандартному протоколу обмена данными WWW.

Как устроен сервлет



Рис. 2
Главным родителем всех сервлетов является интерфейс Servlet. Следующий уровень иерархии - абстрактный класс GenericServlet, частично реализующий методы Servlet и служащий базой для создания экзотических сервлетов. Скорее всего, вам никогда не доведется обращаться к нему. Разумнее будет воспользоваться в качестве базового абстрактным классом HttpServlet. Таким образом, главное, что нужно сделать для получения работоспособного сервлета, - это создать класс, наследующий HttpServlet. А уже потом следует заняться написанием логики работы. На рис. 2 представлена иерархическая диаграмма, показывающая взаимосвязь классов в Java Servlet Development Kit. Пунктирной рамкой отмечено место, отведенное сервлету пользователя.

Базовая часть классов JSDK помещена в пакет javax.servlet. Однако класс HttpServlet и все, что с ним связано, располагаются на один уровень ниже в пакете javax.servlet.http. Это следует учитывать при разработке. Также не забывайте, что основные сервлетообразующие классы находятся в архивном файле jsdk.jar, поэтому этот архив должен упоминаться в переменной среды CLASSPATH или же должен быть задан вручную в параметре командной строки -classpath при запуске компилятора javac.

Последовательность работы сервлета проста: инициализация, обслуживание запросов и завершение существования. В каждой из этих фаз сервер, на котором выполняется сервлет, вызывает соответствующий метод интерфейса Servlet.

Первым вызывается метод init. Он дает сервлету возможность инициализировать важные данные и подготовиться для обработки запросов. Чаще всего в этом методе программисты помещают исходный текст, кэширующий данные фазы инициализации и промежуточные результаты различных вычислений.

После этого сервер находится в ожидании запросов от клиентов. Появившийся запрос немедленно преобразуется в вызов метода service сервлета, а все параметры запроса упаковываются в объект класса ServletRequest, который передается в качестве первого параметра метода service. Второй параметр метода - объект класса ServletResponse. Туда упаковываются выходные данные в процессе формирования ответа клиенту. Каждый новый запрос приводит к новому вызову метода service. В соответствии со спецификацией JSDK, метод service должен уметь обрабатывать сразу несколько запросов, т. е. быть синхронизирован для выполнения в многопоточных средах. Это особенно критично, когда в нем происходит обращение к разделяемым данным. Если же нужно избежать множественных запросов, сервлет должен реализовать интерфейс SingleThreadModel. Последний не содержит ни одного метода и служит лишь меткой, говорящей серверу об однопоточной природе сервлета. При обращении к такому сервлету каждый новый запрос будет стопориться в очереди и ожидать, пока не завершится обработка предыдущего запроса.

Завершив выполнение сервлета, сервер вызывает его метод destroy, предполагая, что в нем сервлет "почистит" занятые ранее ресурсы. Программируя этот метод, разработчик должен помнить, что в многопоточных средах сервер может вызвать метод destroy в любой момент, даже когда еще не завершился метод service. Поэтому на передний план выходит обязательная синхронизация доступа к разделяемым ресурсам.

Интерфейсом Servlet предусмотрена реализация еще двух методов: getServletConfig и getServletInfo. Первый возвращает объект типа ServletConfig, содержащий параметры конфигурации сервлета, а второй - строку, кратко описывающую назначение сервлета, и прочую полезную информацию.

Сервлет HttpServlet, отвечающий за обработку запросов HTTP, устроен несколько сложнее. Он уже имеет реализованный метод service, служащий диспетчером для других методов, каждый из которых обрабатывает методы доступа к ресурсам. В спецификации HTML определены следующие методы: GET, HEAD, POST, PUT, DELETE, OPTIONS и TRACE. Наиболее часто употребляются GET - универсальный запрос ресурса по его универсальному адресу (URL) - и POST, с помощью которого на сервер передаются данные, введенные пользователем в поля интерактивных Web-страниц. Полное описание протокола HTTP 1.1 можно найти в Internet по адресу http://info.internet.isi.edu/in-notes/rfc/files/ rfc2068.txt.

Возвратимся к методу service HttpServlet. В его задачу входит анализ полученного через запрос метода доступа к ресурсам и вызов соответствующего метода, имя которого сходно с названием метода доступа к ресурсам, но в начале имени добавляется префикс do: doGet, doHead, doPost, doPut, doDelete, doOptions и doTrace. От разработчика же требуется переопределить нужный метод (чаще всего это doGet), разместив в нем функциональную логику.

Вспомогательные классы


Чтобы успешно использовать сервлеты, необходимо ознакомиться с рядом вспомогательных классов. Для начала узнаем, какие методы предлагает интерфейс ServletRequest, передающий сервлету запрос от клиента. Конечно же, запрос поступает не в том виде, в котором он приходит на сервер. Поток данных от клиента сортируется и упаковывается. Далее, вызывая методы интерфейса ServerRequest, можно получать определенный тип данных, посланных клиентом. Так, метод getCharacterEncoding определяет символьную кодировку запроса, а методы getContentType и getProtocol - MIME-тип пришедшего запроса, а также название и версию протокола соответственно. Информацию об имени сервера, принявшего запрос, и порте, на котором запрос был "услышан" сервером, выдают методы getServerName и getServerPort. Интересные данные можно узнать и о клиенте, от имени которого пришел запрос. Его IP-адрес возвращается методом getRemoteAddr, а его имя - методом getRemoteHost.

Если вас интересует прямой доступ к содержимому полученного запроса, самый надежный способ получить его - вызвать метод getInputStream или getReader. Первый возвращает ссылку на объект класса ServletInputStream, а второй - на BufferedReader. После этого можно читать любой байт из полученного запроса, используя технику работы с потоками Java.

Если, обращаясь к серверу, клиент помимо универсального адреса задал параметры, сервлету может понадобиться узнать их значение. Примером может служить электронная анкета, выполненная в виде Web-страницы с формой ввода, значения полей и кнопок которой автоматически преобразуются в параметры URL. Три специальных метода в интерфейсе ServletRequest занимаются разбором параметров и "выдачей на-гора" их значений. Первый из них, getParameter, возвращает значение параметра по его имени или null, если параметра с таким именем нет. Похожий метод, getParameterValues, возвращает массив строк, если задан сложный параметр, скажем, значения полей формы. И еще один метод, getParameterNames, возвращает энумератор, позволяющий узнать имена всех присланных параметров.

В классе HttpServletRequest имеются различные дополнительные методы, обеспечивающие программисту доступ к деталям протокола HTTP. Так, вы можете запросить массив cookies, полученный с запросом, используя метод getCookies. Узнать о методе доступа к ресурсам, на основе которого построен запрос, можно с помощью вызова getMethod. Строку запроса HTTP можно получить методом getQueryString. Даже имя пользователя, выполнившего запрос, не укроется от сервлета, если применить метод getRemoteUser.

Это, разумеется, лишь малая толика тех возможностей, которыми обладают вышеупомянутые классы ServletRequest и HttpServletRequest. Поэтому следует внимательно прочитать документацию по Servlet API.

Генерируемые сервлетами данные пересылаются серверу-контейнеру с помощью объектов, наследующих интерфейс ServletResponse, а сервер, в свою очередь, пересылает ответ клиенту, инициировавшему запрос. У интерфейса ServletResponse всего несколько методов, причем полезными оказываются не все. Чаще всего приходится задавать MIME-тип генерируемых данных методом setContentType и находить ссылки на потоки вывода двумя другими методами: getOutputStream возвращает ссылку на поток ServletOutputStream, а метод getWriter вернет ссылку на поток типа PrintWriter. Вы увидите, как ими пользоваться, когда мы будем рассматривать практический пример.

В классе HttpServletResponse, реализующем интерфейс ServletResponse, обнаруживаются еще несколько полезных методов. Например, вы можете переслать cookie на клиентскую станцию, вызвав метод addCookie. О возникших ошибках сообщается вызовом sendError, которому в качестве параметра передается код ошибки и при необходимости текстовое сообщение. Кроме того, по мере надобности в заголовок ответа можно добавлять параметры, для чего служит метод setDateHeader.

Ранее уже упоминался метод getServletConfig, но не было сказано об объекте ServletConfig, у которого имеются очень полезные методы. Инсталлируя сервлет на сервере, вы можете задавать параметры инициализации, о которых будет сказано в следующей части. Имена этих параметров можно получить через энумератор, возвращаемый методом getInitParameterNames. Значение же конкретного параметра получают вызовом getInitParameter. Также важен метод получения контекста сервлета getServletContext, через обращение к которому можно узнать много полезного о среде, в которой запущен и выполняется сервлет.

Контекст выполнения сервлета интересен тем, что дает примитивные средства для общения с сервером. Скажем, для выполнения задачи требуется узнать MIME-тип того или иного файла. Всегда пожалуйста, вызовите метод getMimeType контекста. Или нужно узнать истинный маршрут файла относительно каталога, в котором сервер хранит документы. Тоже нет проблем, метод getRealPath к вашим услугам. Информация же о самом сервере предоставляется по вызову getServerInfo().

Отдельно стоят метод, загружающий сервлет по имени getServlet, и метод, возвращающий энумератор с именами всех установленных сервлетов getServletNames. Однако не рекомендуется пользоваться ими, так как в будущей версии Java Servlet Development Kit их может уже и не быть.

И напоследок самый важный метод log. С его помощью нужные текстовые данные пишутся в протокол работы сервлетов.

Мы рассмотрели наиболее часто используемые программистами классы и методы JSDK. Однако, если ваши задачи сложны и требуют тонкого подхода, внимательно изучите справочник по API, имеющийся в составе Java Servlet Development Kit. Последний можно бесплатно загрузить с сервера http://www.javasoft.com.



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


Настало время остановиться на деталях настройки и запуска сервлетов. Для этого следует поговорить о файле свойств. Имя этого файла - servlet.properties. В нем в виде пар 'ключ-значение' хранятся свойства, используемые для конфигурации, создания и инициализации сервлетов. Изначально для любого из сервлетов предопределено два свойства. Первое, servlet.<имя сервлета>.code, определяет имя сервлета и ставит его в соответствие двоичному class-файлу сервлета. Например, если вы скомпилировали класс сервлета с именем MyServletClassName, то получите в результате компиляции файл с именем MyServletClassName. class и можете присвоить ему краткое имя, скажем, myservlet, следующим образом:

servlet.myservlet.code=MyServletClassName

Теперь, когда вы обратитесь к сервлету с именем myservlet, сервер найдет эту строку в файле свойств и, опираясь на найденное значение, загрузит класс MyServletClassName, инициализирует его и передаст ему ваш запрос. Следует помнить о том, что имя class-файла должно задаваться полностью, включая имя пакета, в котором определен класс. Второе свойство, servlet.<имя сервлета>.initargs, определяет передаваемые сервлету параметры инициализации. Значения такого рода параметров могут быть получены сервлетом методом getInitParameter. Если параметров несколько, они отделяются друг от друга запятыми. Пример задания параметров:

servlet.myservlet.initargs= \
someParameterName1=someValue, someParameterName2=otherValue

Файл servlet.properties помещают в определенный каталог на сервере, где хранятся class-файлы. Заметим, однако, что разные серверы допускают альтернативное местоположение файла свойств (по настройке администратора).

Теперь поговорим об утилите servletrunner, которая по сути является простейшим Web-сервером, специально предназначенным для работы с сервлетами. После запуска он 'слушает' порт 8080, и если произошел запрос, то, обратившись к сервлету, servletrunner получает от него ответ и пересылает последний программе-клиенту. Командная строка servletrunner имеет различные опции, но полезными могут оказаться лишь следующие:

  • p port - 'прослушиваемый' в ожидании запроса порт;
  • t timeout - время тайм-аута в мс;
  • d dir - каталог, где лежат сервлеты;
  • r root - корневой каталог, в котором хранятся документы;
  • s filename - альтернативное имя файла свойств servlet property file name
  • v - отображать выводимые в стандартные потоки данные.

Никаких дополнительных настроек вам не потребуется. Просто запустите утилиту servletrunner и обращайтесь к ней с помощью браузера или другой клиентской программы. Главное, чтобы servletrunner могла найти ваши сервлеты.

Теперь вкратце о том, как обратиться к сервлету из Web-браузера. По умолчанию адрес URL для отправки запроса состоит из нескольких частей: имени компьютера, номера порта, каталога servlet, имени сервлета и списка параметров. К примеру, обратиться к сервлету myservlet, находящемуся на локальном компьютере, можно так:

http://localhost:8080/servlet/myservlet?param=somevalue

Напомним, что, тоже по умолчанию, утилита servletrunner (и некоторые серверы Web) 'слушает' запросы к порту с номером 8080. Приведенный выше запрос обращается к сервлету myservlet, передавая ему параметр param со значением somevalue. Это весьма полезно, когда нужно задавать параметры запроса 'на лету' (в отличие от параметров инициализации).

Пример сервлета


Чтобы все рассказанное о сервлетах не осталось для вас пустыми словами, создадим собственный сервлет, который будет выводить в окне браузера список файлов в определенном каталоге компьютера-сервера, а заодно и показывать количество файлов и их местоположение. Готовый исходный текст показан в листинге. Мы не станем детально описывать теги HTML, которые сервлет вставляет в генерируемую страницу, - обратитесь самостоятельно к документации по этому языку. Весь вывод данных будет производиться методом println класса PrintWriter, что является наиболее удобным вариантом посылки текстовых данных.

Начнем с того, что опишем класс сервлета, наследующий HttpServlet, и импортируем необходимые классы. После чего займемся методом doGet - главной частью сервлета:

Методом getWriter сервлет получает доступ к потоку вывода, через который серверу посылается результат:

PrintWriter w = res.getWriter(); res.setContentType('text/html'); generateHeader('Directory Viewer Servlet', w);

После чего методом setContentType серверу сообщается, что возвращаемые данные - страница HTML (MIME-тип 'text/html'). Если этого не сделать, то сервер решит, что вы посылаете обычный текст. Метод generateHeader, который идет следом, убирает детали генерации стандартного заголовка Web-страницы, чтобы не загромождать исходный текст. Первый параметр этого метода - заголовок генерируемой страницы, а второй - ссылка на поток вывода, куда этот метод должен пересылать данные.

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

if((param = req.getParameter('dirToShow')) == null)
      if((param = getInitParameter('dirToShow')) == null)
      {
        w.println('<H1><FONT COLOR=RED>' +
          'The <EM>dirToShow</EM> parameter 
required!</FONT></H1>');
        return;
      }
Рис.1

Сначала сервлет пытается считать значение параметра dirToShow из адреса URL (метод getParameter). Если такого параметра в запросе нет, то предпринимается попытка получить параметр с этим же именем из файла свойств (метод getInitParameter). Если и там параметр не обнаружен, запрос прерывается, а в ответ клиенту посылается сообщение (рис. 1).

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

File root = new File(param);
    if(!root.isDirectory())
    {
      w.println('<H1><FONT COLOR=RED>' +
        'The parameter is not a directory or not exist!</FONT></H1>');
      return;
    }

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

    File[] fileList = root.listFiles();
    w.println('<H2><FONT COLOR=TEAL>' +
        'Total number of files in the choosen directory - ' +
        fileList.length + '</FONT></H2>');
    w.println('<H3><FONT COLOR=PURPLE>' +
        'Directory path - ' + param + '</FONT></H3><HR>');

Имена файлов выводятся методом printName, который приведен чуть ниже. Для форматирования списка применяется таблица, поэтому нужны две дополнительные строки, формирующие с помощью пары тегов <TABLE> и </TABLE> таблицу с невидимой рамкой:

w.println('<TABLE BORDER=0 CELLSPACING=5>');
    for(int i = 0; i < fileList.length; i++)
        printName(fileList[i], w);
    w.println('</TABLE><HR>');
    generateFooter(w);

Завершается вывод генерацией стандартного окончания HTML-страницы с помощью метода generateFooter.

Теперь о методе printName. Он чрезвычайно прост и состоит из строчки, которая проверяет, является ли выводимое имя каталогом или простым файлом. После этого в специально отведенную переменную записывается комментарий о типе. Далее в два столбца в таблицу выводятся тип файла (простой файл или каталог) и его имя:

  private void printName(File name, PrintWriter output)
  {
    String type = name.isDirectory()
        ? ' (Directory)' : ' (File)';
    output.println('<TR><TD>' + type + '</TD><TD><FONT COLOR=BLUE>'
        + name.getName() + '</FONT></TD></TR>');
  }

Остается лишь рассмотреть вспомогательные методы generateHeader, generateFooter getServletInfo. Они настолько просты, что вы без труда разберетесь с ними самостоятельно:

  private void generateHeader(String title, PrintWriter output)

  {
    output.println('<HTML>\n<HEAD>\n<TITLE>' +
        title + '</TITLE>\n</HEAD>\n<BODY>');
  }
  private void generateFooter(PrintWriter output)
  {
    output.println('</BODY>\n</HTML>');
    output.flush();
    output.close();
  }
  public String getServletInfo()
  {
    return 'This servlet shows a content of a directory' +
           'mentioned in dirToShow parameter or property.';
  }
}

Отлаживать подобного рода сервлеты не просто, а очень просто. Нужно только открыть в браузере режим просмотра исходного текста страницы - и все ваши ошибки сразу же становятся видны. Ниже приведен исходный текст файла свойств servlet.properties

:
# dirlist servlet
servlet.dirviewer.code=SampleServlet
servlet.dirviewer.initArgs=\
                  dirToShow=D:\\SUN\\SDK\\examples
Рис.2

Обратите внимание, что символ 'обратная косая' в пути к каталогу задается по правилам языка Java, т. е. двойными 'обратными косыми'. Можно, однако, использовать и одинарную 'прямую косую', как это принято в ОС UNIX.

На рис. 2 показано, как примерно будет выглядеть результат работы сервлета. В качестве запроса была введена строка:

 

http://mitrich/servlet/dirviewer?dirToShow=D:/Sun/SDK/examples

Запускается сервлет следующей строкой:

servletrunner -p 80 -d E:\PROJECTS\Java\Servlet -r E:\PROJECTS\Java\Servlet

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

Изготовление сервлетов в Borland JBuilder 2


Довольно хорошими возможностями по созданию сервлетов обладает пакет Borland JBuilder 2. Если заглянуть в список имеющихся у него мастеров, то можно обнаружить соответствующую пиктограмму, щелкнув на которой вы запускаете программу генерации исходного текста сервлета (рис. 3).

Рис.3

Сначала потребуется ввести исходные данные для создания нового проекта: название проекта, имя автора и т.д. (рис. 4).

Рис.4

Сам мастер генерации сервлета запускается чуть позже, когда создан проект. Диалоговая панель мастера (рис. 5) предложит создать те элементы сервлета, с которыми вы уже познакомились в предыдущей части статьи. Опция Implement SingleThreadModel добавляет в список наследования класса сервлета интерфейс SingleThreadModel, не разрешающий множественное обращение к сервлету. Generate HTML Page создает Web-страницу для сервлета. И наконец, вы можете включать генерацию нескольких основных методов-обработчиков для сервлета: service, doGet, doPost, doPut, doDelete.

Рис.5

Рис.6

 

 

 

На следующем этапе задаются параметры инициализации. Кроме имени параметра и его типа также задаются: описание параметра; переменная внутри сервлета, которой будет передано значение параметра после его считывания; значение параметра по умолчанию; имя метода сервлета, в котором должно происходить считывание параметра (рис. 6).

Если ввести параметры так, как это показано на предыдущих рисунках, то вы получите проект с двумя Java-классами. Первый представляет собой простейший сервер. Он настроен на порт 8080 и умеет распознавать две опции командной строки -p и -d, которыми можно задать порт сервера и каталог с сервлетами соответственно. Вот исходный текст такого сервера, сгенерированный JBuilder:===

Итак, вы получили представление о том, как пишутся сервлеты. К сожалению, без Java Web Server компании Sun трудно отладить сколь-нибудь сложные сервлетные классы, а утилита servletrunner обладает лишь простейшими возможностями по их запуску. Однако компания JavaSoft обещает исправить этот недостаток, включив в следующую версию Java Servlet Development Kit усовершенствованный маленький Web-сервер, с которым процесс отладки и проверки станет намного проще.

Листинг: Пример сервлета


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

public class SampleServlet extends HttpServlet
{ 
  public void doGet( HttpServletRequest req, HttpServletResponse res)
                                     throws ServletException, IOException
  {
    String param;
    PrintWriter w = res.getWriter();
    res.setContentType('text/html');
    generateHeader('Directory Viewer Servlet', w);
    if((param = req.getParameter('dirToShow')) == null)
      if((param = getInitParameter('dirToShow')) == null)
      {
        w.println('<H1><FONT COLOR=RED>' +
          'The <EM>dirToShow</EM> parameter 
            required!</FONT></H1>');
        return;
      }
    File root = new File(param);
    if(!root.isDirectory())
    {
      w.println('<H1><FONT COLOR=RED>' +
        'The parameter is not a directory or not exist!</FONT></H1>');
      return;
    }
    File[] fileList = root.listFiles();
    w.println('<H2><FONT COLOR=TEAL>' +
        'Total number of files in the choosen directory - ' +
        fileList.length + '</FONT></H2>');
    w.println('<H3><FONT COLOR=PURPLE>' +
        'Directory path - ' + param + '</FONT></H3><HR>');
    w.println('<TABLE BORDER=0 CELLSPACING=5>');
    for(int i = 0; i < fileList.length; i++)
        printName(fileList[i], w);
    w.println('</TABLE><HR>');
    generateFooter(w);
  }

  private void printName(File name, PrintWriter output)
  {
    String type = name.isDirectory()
        ? ' (Directory)' : ' (File)';
    output.println('<TR><TD>' + type + '</TD><
       TD><FONT COLOR=BLUE>'
        + name.getName() + '</FONT></TD></TR>');
  }

  private void generateHeader(String title, PrintWriter output)
  {
    output.println('<HTML>\n<HEAD>\n<TITLE>' +
        title + '</TITLE>\n</HEAD>\n<BODY>');
  }

  private void generateFooter(PrintWriter output)
  {
    output.println('</BODY>\n</HTML>');
    output.flush();
    output.close();
  }

  public String getServletInfo()
  {
    return 'This servlet shows a content of a directory' +
           'mentioned in dirToShow parameter or property.';
  }
}


Литература по CGI