Содержание


Путеводитель по Scala для Java-разработчиков

Scala и сервлеты

Работа с сервлетами в Scala

Comments

Серия контента:

Этот контент является частью # из серии # статей: Путеводитель по Scala для Java-разработчиков

Следите за выходом новых статей этой серии.

Этот контент является частью серии:Путеводитель по Scala для Java-разработчиков

Следите за выходом новых статей этой серии.

То, что Scala – это интересный язык, отлично приспособленный для демонстрации привлекательных инновационных идей в теории языков программирования, не вызывает сомнений. Однако этого недостаточно для того, чтобы его начали применять на практике. Для этого необходимо продемонстрировать применимость Scala для решения реальных задач, стоящих перед разработчиками программного обеспечения.

В предыдущих статьях были рассмотрены некоторые базовые возможности языка, продемонстрирована гибкость его синтаксиса, а также был приведен пример использования Scala для создания предметно-ориентированного языка (DSL). Теперь пришло время опробовать возможности Scala в средах, в которых работают реальные приложения. В этой статье, которая начинает новый этап серии, посвященный использованию Scala в "реальном мире", мы рассмотрим работу с технологией, занимающей центральное место во многих приложениях Java™. Речь пойдет об API сервлетов (Servlet API).

Обзор технологии сервлетов

Как вы помните, в основе технологии сервлетов лежит клиент-серверная модель взаимодействия через сокеты по протоколу HTTP (как правило, через 80-й порт). В качестве клиента может выступать любой пользовательский агент (см. определение "User-Agent" в спецификации HTTP), а в качестве сервера – контейнер сервлетов, который отвечает за поиск, загрузку и выполнение методов классов, реализующих интерфейс javax.servlet.Servlet.

На практике Java-разработчикам редко приходится создавать классы, напрямую реализующие данный интерфейс. Изначально считалось, что спецификация сервлетов должна предоставлять общий API, который можно было бы использовать при работе с протоколами, отличными от HTTP, поэтому было решено разделить пространство имен сервлетов на следующие две части:

  • общий пакет, не зависящий от протокола (javax.servlet);
  • пакет, ориентированный на HTTP (javax.servlet.http).

В результате в общий пакет был включен абстрактный класс javax.servlet.GenericServlet, реализующий базовую функциональность сервлета, а в пакет HTTP – класс-потомок javax.servlet.http.HttpServlet, реализующий функциональность, специфичную для протокола HTTP. Как правило, именно этот класс используется в качестве базового при создании сервлетов. Он полностью реализует интерфейс Servlet, преобразуя запросы типа GET и POST в вызовы методов doGet и doPut соответственно, которые должны быть перегружены в классах-потомках.

Scala и сервлеты

Поскольку знакомство с сервлетами для всех начинается с создания сервлета, выводящего строку "Здравствуй, мир!", мы не будем отступать от этой традиции и в Scala. Вспомните, как много лет назад вы читали свое первое руководство по написанию сервлетов, в котором было написано, что строку "Здравствуй, мир!" можно печатать непосредственно в HTML-ответ, как показано в листинге 1.

Листинг 1. Пример простейшего HTML-ответа
<HTML>
   <HEAD><TITLE>Hello, Scala!</TITLE></HEAD>
   <BODY>Hello, Scala! This is a servlet.</BODY>
</HTML>

В Scala подобный сервлет создается очень просто, причем он будет выглядеть практически так же, как его аналог в Java (листинг 2).

Листинг 2. Да здравствует первый сервлет на Scala!
import javax.servlet.http.{HttpServlet,
  HttpServletRequest => HSReq, HttpServletResponse => HSResp}

class HelloScalaServlet extends HttpServlet
{
  override def doGet(req : HSReq, resp : HSResp) =
    resp.getWriter().print("<HTML>" +
      "<HEAD><TITLE>Hello, Scala!</TITLE></HEAD>" +
      "<BODY>Hello, Scala! This is a servlet.</BODY>" +
      "</HTML>")
}

Обратите внимание на использование псевдонимов импортируемых классов. Это делается для того, чтобы сократить длину имен типов. В остальном этот класс Scala совершенно эквивалентен сервлету Java. При компиляции следует указать ссылку на файл servlet-api.jar (он обычно поставляется вместе с контейнером сервлетов, например, в случае Tomcat 6.0 он находится в подкаталоге lib). В противном случае компилятор не сможет найти типы, относящиеся к API сервлетов.

Пока наш скомпилированный сервлет не готов к работе. В соответствии со спецификацией он должен быть развернут внутри каталога соответствующего Web-приложения (либо в составе файла .war), включающего файл web.xml. Данный файл является дескриптором развертывания и описывает, какие запросы должен обрабатывать данный сервлет. В нашем примере для этого достаточно указать простой URL, как показано в листинге 3.

Листинг 3. Файл web.xml - дескриптор развертывания
<!DOCTYPE web-app 
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" 
    "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
  <servlet>
    <servlet-name>helloWorld</servlet-name>
    <servlet-class>HelloScalaServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>helloWorld</servlet-name>
    <url-pattern>/sayHello</url-pattern>
  </servlet-mapping>
</web-app>

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

Ни для кого не секрет, что правильно сформированные документы HTML выглядят очень похоже на XML, поэтому встроенная поддержка XML в Scala может существенно облегчить написание сервлетов (возможности Scala для работы с XML описаны в статье "Scala и XML", ссылка на которую приведена в разделе Ресурсы). Вместо того чтобы печатать сообщение непосредственно в HttpServletResponse, можно использовать встроенные XML-конструкции в Scala, добившись тем самым некоторого, пусть и примитивного, разделения логики и представления. В листинге 4 показан пример вывода сообщения в XML-фрагмент, который затем передается клиенту.

Листинг 4. Да здравствует первый сервлет на Scala!
import javax.servlet.http.{HttpServlet,
  HttpServletRequest => HSReq, HttpServletResponse => HSResp}

class HelloScalaServlet extends HttpServlet
{
  def message =
    <HTML>
      <HEAD><TITLE>Hello, Scala!</TITLE></HEAD>
      <BODY>Hello, Scala! This is a servlet.</BODY>
    </HTML>

  override def doGet(req : HSReq, resp : HSResp) =
    resp.getWriter().print(message)
}

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

{ Text(java.util.Calendar.getInstance().getTime().toString() )
}

Если же данное выражение кажется слишком громоздким, то возможен альтернативный вариант (листинг 5).

Листинг 5. Улучшенный сервлет, выводящий текущее время
import javax.servlet.http.{HttpServlet,
  HttpServletRequest => HSReq, HttpServletResponse => HSResp}

class HelloScalaServlet extends HttpServlet
{
  def message =
    <HTML>
      <HEAD><TITLE>Hello, Scala!</TITLE></HEAD>
      <BODY>Hello, Scala! It's now { currentDate }</BODY>
    </HTML>
  def currentDate = java.util.Calendar.getInstance().getTime()

  override def doGet(req : HSReq, resp : HSResp) =
    resp.getWriter().print(message)
}

При этом происходит следующее: компилятор Scala представляет XML-сообщение в виде экземпляра класса scala.xml.Node, который затем преобразуется к строковому виду и передается в метод print объекта Writer, формирующего ответ сервлета.

Не стоит недооценивать эту возможность Scala – она представляет собой гибкий способ отделения логики сервлета от представления информации, причем исключительно внутри класса сервлета. При этом синтаксическая корректность сообщения с точки зрения правил XML будет проверена на этапе компиляции сервлета. Этого невозможно добиться при использовании стандартных сервлетов Java или JSP-страниц. Кроме того, благодаря возможности вывода типов в Scala, можно опустить информацию о типе переменных message и currentDate, поэтому код сервлета выглядит так, как будто написан на динамическом языке, вроде Groovy/Grails. Для первого примера это совсем неплохо.

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

Scala и параметры запросов

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

Листинг 6. Задачка!
<HTML>
  <HEAD><TITLE>Who are you?</TITLE></HEAD>
  <BODY>
Who are you? Please answer:
<FORM action="/scalaExamples/sayMyName" method="POST">
Your first name: <INPUT type="text" name="firstName" />
Your last name: <INPUT type="text" name="lastName" />
<INPUT type="submit" />
</FORM>
  </BODY>
</HTML>

Конечно, эта форма вряд ли получит приз как образец пользовательского интерфейса, однако она выполняет свою функцию – передает необходимые данные Scala-сервлету, который обрабатывает запросы по относительному URL sayMyName. В соответствии со спецификацией сервлетов переданные данные будут представлены в виде коллекции пар типа "ключ-значение". Доступ к параметрам осуществляется при помощи вызовов метода HttpServletRequest.getParameter(), который принимает на вход имя параметра, соответствующее имени элемента формы.

Доступ к параметрам из Scala выглядит аналогично тому, как это делается в Java. Пример приведен в листинге 7.

Листинг 7. Решение (версия первая)
class NamedHelloWorldServlet1 extends HttpServlet
{
  def message(firstName : String, lastName : String) =
    <HTML>
      <HEAD><TITLE>Hello, {firstName} {lastName}!</TITLE></HEAD>
      <BODY>Hello, {firstName} {lastName}! It is now {currentTime}.</BODY>
    </HTML>
  def currentTime =
    java.util.Calendar.getInstance().getTime()

  override def doPost(req : HSReq, resp : HSResp) =
  {
    val firstName = req.getParameter("firstName")
    val lastName = req.getParameter("lastName")
  
    resp.getWriter().print(message(firstName, lastName))
  }
}

Однако при этом теряется часть преимуществ от разделения логики и представления сообщения, которые упоминались ранее, так как теперь необходимо явно передавать параметры firstName и lastName в сообщение. Такой подход будет выглядеть громоздким, если число параметров будет больше трех или четырех. К тому же, в методе doPost придется извлекать значение каждого параметра перед тем как передать его сообщению – в результате получится длинный и скучный фрагмент кода, в котором возможны ошибки.

Одним из вариантов решения этой проблемы является вынос кода, извлекающего параметры запроса и вызывающего метод doPost, в базовый класс, как показано в листинге 8.

Листинг 8. Решение (версия вторая)
abstract class BaseServlet extends HttpServlet
{
  import scala.collection.mutable.{Map => MMap}
  
  def message : scala.xml.Node;
  
  protected var param : Map[String, String] = Map.empty
  protected var header : Map[String, String] = Map.empty
  
  override def doPost(req : HSReq, resp : HSResp) =
  {
    // Extract parameters
    //
    val m = MMap[String, String]()
    val e = req.getParameterNames()
    while (e.hasMoreElements())
    {
      val name = e.nextElement().asInstanceOf[String]
      m += (name -> req.getParameter(name))
    }
    param = Map.empty ++ m
    
    // Repeat for headers (not shown)
    //
  
    resp.getWriter().print(message)
  }
}
class NamedHelloWorldServlet extends BaseServlet
{
  override def message =
    <HTML>
      <HEAD><TITLE>Hello, {param("firstName")} {param("lastName")}!</TITLE></HEAD>
      <BODY>Hello, {param("firstName")} {param("lastName")}! It is now {currentTime}.
      </BODY>
    </HTML>

  def currentTime = java.util.Calendar.getInstance().getTime()
}

В этом примере сервлет, занимающийся отображением сообщения, оказывается значительно проще своего предшественника. При этом дополнительным преимуществом является то, что ассоциативные массивы param и header являются неизменяемыми. Обратите внимание, что param мог бы быть объявлен как метод, обращающийся к объекту request, однако в этом случае request пришлось бы сделать полем данного метода. В условиях параллельного вызова сервлета это было бы очень рискованным решением, поскольку с точки зрения контейнера сервлетов все do-методы разрешают повторный вход.

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

Листинг 9. Решение (версия третья)
class NamedHelloWorldServlet extends BaseServlet
{
  override def message =
    if (validate(param))
    <HTML>
    <HEAD><TITLE>Hello, {param("firstName")} {param("lastName")}!
             </TITLE></HEAD>
    <BODY>Hello, {param("firstName")} {param("lastName")}! 
                It is now {currentTime}.</BODY>
    </HTML>
    else
      <HTML>
        <HEAD><TITLE>Error!</TITLE></HEAD>
        <BODY>How can we be friends if you don't tell me your name?!?</BODY>
      </HTML>

  def validate(p : Map[String, String]) : Boolean =
  {
    p foreach {
      case ("firstName", "") => return false
      case ("lastName", "") => return false
    //case ("lastName", v) => if (v.contains("e")) return false
    case (_, _) => ()
    }
    true
  }

  def currentTime = java.util.Calendar.getInstance().getTime()
}

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

Разумеется, в данный сервлет можно добавить множество других функций. Например, одной из классических проблем, затрудняющих разработку Web-приложений на Java, являются атаки с применением инъекции SQL-кода. Они заключаются в том, что в полях формы передаются неэкранированные команды SQL, которые затем могут необдуманно конкатенироваться с шаблонами SQL-запросов и выполняться над базой данных. В Scala проверка корректности полей формы может осуществляться на основе регулярных выражений (пакет scala.regex), либо при помощи комбинаторов парсеров, которые были рассмотрены в последних трех статьях серии. Более того, весь процесс проверки можно вынести в базовый класс, метод validate которого просто возвращал бы true по умолчанию (несмотря на то, что Scala – это функциональный язык, не стоит забывать о принципах объектно-ориентированного дизайна).

Заключение

Несмотря на то, что простенькая инфраструктура сервлетов Scala, описанная в этой статье, не может соперничать с многофункциональными Web-инфраструктурами, созданными на Java, она успешно выполняет две функции:

  • демонстрирует некоторые варианты использования возможностей Scala для облегчения задач разработки приложений под JVM;
  • играет вводную роль при демонстрации использования Scala для создания Web-приложений. Более подробно эта идея представлена в инфраструктуре lift, ссылка на которую приведена в разделе Ресурсы.

На этом мы заканчиваем эту часть серии, до новых встреч!


Ресурсы для скачивания


Похожие темы

  • Оригинал статьи: The busy Java developer's guide to Scala: Scala and servlets (Тед Ньювард, developerWorks, декабрь 2008 г.). (EN)
  • Прочитайте статью "Scala и XML" (Тед Ньювард, developerWorks, апрель 2008 г.), в которой рассказывается о разборе и обработке документов XML в Scala, а также подробно описываются встроенные средства Scala для работы с XML. (EN)
  • Прочитайте статью "Функциональное программирование на Java" (Абхиджит Белапуркар, Abhijit Belapurkar, developerWorks, июль 2004 г.), в которой обсуждаются преимущества и возможности применения функционального программирования с точки зрения разработчика Java. (EN)
  • Ознакомьтесь со статьей "Scala в примерах" (Мартин Одерски, Martin Odersky, декабрь 2007 г.), в которой приводится краткое введение в Scala, изобилующее примерами (формат PDF). (EN)
  • Прочитайте книгу Программирование на Scala (Мартин Одерски, Лекс Спун, Lex Spoon и Билл Веннерс; сигнальный экземпляр опубликован Artima в феврале 2008 г.) - первое подробное введение в Scala, написанное в соавторстве с Биллом Веннерсом. (EN)
  • Бьёрн Страуструп – автор и создатель языка C++, который он лично охарактеризовал как "улучшенный C". (EN)
  • В книге Java Puzzlers: Traps, Pitfalls, and Corner Cases (Addison-Wesley Professional, июль 2005 г.) на занимательных и интригующих задачах описывается ряд странных ситуаций, возникающих при программировании на Java. (EN)
  • Загрузите Scala и начните ее изучение с этой серии. (EN)
  • Загрузите SUnit - набор классов пакета scala.testing, входящего в стандартный дистрибутив Scala. (EN)
  • Сотни статей по всем аспектам программирования на Java можно найти на сайте developerWorks в разделе Технология Java.

Комментарии

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

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Технология Java
ArticleID=406385
ArticleTitle=Путеводитель по Scala для Java-разработчиков: Scala и сервлеты
publish-date=07032009