CLOVER🍀

That was when it all began.

sbt 0.11.3とxsbt-web-plugin 0.2.11.1でサーブレットプログラミング

以前sbtでサーブレットプログラミングの環境構築に関するエントリを書いたのですが、xsbt-web-pluginのバージョンが0.1から0.2に変わった時にコマンドが変わったらしく、当時のエントリを参照してもうまく動かないという記述をちらほらと見かけるようになったので、ちょっと書き直すことにしました。

う〜ん、まさかこのマイナーバージョンの変化でコマンドが変わるとは…。

ま、気を取り直していきましょう!今回は使用するソフトウェアのバージョンを明記しておきます。

ソフトウェア名 バージョン
scala 2.9.2
sbt 0.11.3
xsbt-web-plugin 0.2.11.1
Jetty 8.1.0.v20120127

Jettyは、残念ながらこの時点の最新版ではありません…。

まずは、プロジェクト用に適当なディレクトリを作成します。ディレクトリ名は「hello-servlet」とします。

$ mkdir hello-servlet
$ cd hello-servlet

プロジェクト用のディレクトリ内に、project/plugins.sbtとbuild.sbt、そしてソース用のディレクトリなどを作成します。

$ mkdir project
$ emacs project/plugins.sbt &
$ emacs build.sbt &
$ mkdir -p src/main/scala/littlewings/servlet src/main/webapp/WEB-INF

それぞれの中身は、以下のページを参考に作成します。
https://github.com/siasia/xsbt-web-plugin/wiki/

build.sbt

name := "hello-servlet"

version := "0.0.1"

scalaVersion := "2.9.2"

organization := "littlewings"

seq(webSettings :_*)

libraryDependencies ++= Seq(
  "org.eclipse.jetty" % "jetty-webapp" % "8.1.0.v20120127" % "container",
  "javax.servlet" % "javax.servlet-api" % "3.0.1" % "provided"
)
// libraryDependencies += "org.eclipse.jetty" % "jetty-webapp" % "8.1.3.v20120416" % "container" // <= これは動かない…

最新版Jettyでは依存関係の解決に失敗するので、8.1.0にしました…。

project/plugins.sbt

libraryDependencies <+= sbtVersion(v => "com.github.siasia" %% "xsbt-web-plugin" % (v+"-0.2.11.1"))

web.xml。スキーマ定義がServlet 3.0の割には、サーブレットの定義が書いてあるという…。
src/main/webapp/WEB-INF/web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">
  <servlet>
    <servlet-name>helloServlet</servlet-name>
    <servlet-class>littlewings.servlet.HelloServlet</servlet-class>
  </servlet>

  <servlet-mapping>
    <servlet-name>helloServlet</servlet-name>
    <url-pattern>/hello</url-pattern>
  </servlet-mapping>
</web-app>

サンプルのサーブレット。Servlet API 3.0のJARにパスを通した割には、普通のサーブレット…。なんか、アノテーションでうまく動かなかったんですよ。
src/main/scala/littlewings/servlet/HelloServlet.scala

package littlewings.servlet

import java.io.IOException

import javax.servlet.ServletException
import javax.servlet.http.{HttpServlet, HttpServletRequest, HttpServletResponse}

class HelloServlet extends HttpServlet {
  @throws(classOf[IOException])
  @throws(classOf[ServletException])
  override protected def doGet(request: HttpServletRequest, response: HttpServletResponse): Unit = {
    val responseXml =
      <html>
        <head>
          <meta charset="UTF-8"/>
          <title>Hello Servlet</title>
        </head>
        <body>
          <h1>Hello Servlet</h1>
        </body>
      </html>

    response.getWriter.write(responseXml.toString)
  }
}

では、sbtを起動。

$ sbt
[info] Loading project definition from /xxxxx/hello-servlet/project
[info] Set current project to hello-servlet (in build file:/xxxxx/hello-servlet/)
[info] Updating {file:/xxxxx/hello-servlet/}default-bd20f9...
[info] Resolving org.scala-lang#scala-library;2.9.2 ...
[info] Resolving javax.servlet#javax.servlet-api;3.0.1 ...
[info] Resolving org.eclipse.jetty#jetty-webapp;8.1.0.v20120127 ...
[info] Resolving org.eclipse.jetty#jetty-xml;8.1.0.v20120127 ...
[info] Resolving org.eclipse.jetty#jetty-util;8.1.0.v20120127 ...
[info] Resolving org.eclipse.jetty#jetty-servlet;8.1.0.v20120127 ...
[info] Resolving org.eclipse.jetty#jetty-security;8.1.0.v20120127 ...
[info] Resolving org.eclipse.jetty#jetty-server;8.1.0.v20120127 ...
[info] Resolving org.mortbay.jetty#servlet-api;3.0.20100224 ...
[info] Resolving org.eclipse.jetty#jetty-continuation;8.1.0.v20120127 ...
[info] Resolving org.eclipse.jetty#jetty-http;8.1.0.v20120127 ...
[info] Resolving org.eclipse.jetty#jetty-io;8.1.0.v20120127 ...
[info] Done updating.
> 

上記は2回目以降の起動時のログです。ホントは、Jettyのダウンロードやらでもっとたくさんログが出ます。
では、コンパイル。

> compile
[info] Compiling 1 Scala source to /xxxxx/hello-servlet/target/scala-2.9.2/classes...
[success] Total time: 13 s, completed 2012/05/19 22:23:15

xsbt-web-pluginの0.2系では、Jettyの起動はjetty-runではなく、container:startらしいです。

> container:start
[info] jetty-8.1.0.v20120127
[info] NO JSP Support for /, did not find org.apache.jasper.servlet.JspServlet
[info] started o.e.j.w.WebAppContext{/,[file:/xxxxx/hello-servlet/src/main/webapp/]}
[info] started o.e.j.w.WebAppContext{/,[file:/xxxxx/hello-servlet/src/main/webapp/]}
[info] Started SelectChannelConnector@0.0.0.0:8080
[success] Total time: 1 s, completed 2012/05/19 22:25:10

この時、src/main/webappディレクトリが存在しない場合、起動に失敗するので注意してくださいね。

動作確認。

$ telnet localhost 8080
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
GET /hello
<html>
        <head>
          <meta charset="UTF-8"></meta>
          <title>Hello Servlet</title>
        </head>
        <body>
          <h1>Hello Servlet</h1>
        </body>
      </html>Connection closed by foreign host.

うん、動いてますね。

以前のprepare-webappは、container:reloadと思われます。

> container:reload /
[info] stopped o.e.j.w.WebAppContext{/,[file:/xxxxx/hello-servlet/src/main/webapp/]}
[info] NO JSP Support for /, did not find org.apache.jasper.servlet.JspServlet
[info] started o.e.j.w.WebAppContext{/,[file:/xxxxx/hello-servlet/src/main/webapp/]}
[info] started o.e.j.w.WebAppContext{/,[file:/xxxxx/hello-servlet/src/main/webapp/]}
[success] Total time: 0 s, completed 2012/05/19 22:26:44

container:reloadの引数は、コンテキストルート名っぽいですね。

ただ、これだけではScalaソースコードを変更した時に再コンパイルはしてくれないので、先にcompileしておく必要があります。
レスポンスのボディを以下のように変えてみて…

        <body>
          <h1>Hello Scala</h1>
        </body>

再コンパイルしてリロードします。

> compile
〜省略〜
> container:reload /
〜省略〜

こうすると、反映されます。

$ telnet localhost 8080Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
GET /hello
<html>
        <head>
          <meta charset="UTF-8"></meta>
          <title>Hello Servlet</title>
        </head>
        <body>
          <h1>Hello Scala</h1>
        </body>
      </html>Connection closed by foreign host.

ソースコードの変更をポーリングして反映したければ、こんな感じで。

> ~; container:start; container:reload /
[success] Total time: 0 s, completed 2012/05/19 22:37:27
[info] stopped o.e.j.w.WebAppContext{/,[file:/xxxx/hello-servlet/src/main/webapp/]}
[info] NO JSP Support for /, did not find org.apache.jasper.servlet.JspServlet
[info] started o.e.j.w.WebAppContext{/,[file:/xxxxx/hello-servlet/src/main/webapp/]}
[info] started o.e.j.w.WebAppContext{/,[file:/xxxxxhello-servlet/src/main/webapp/]}
[success] Total time: 0 s, completed 2012/05/19 22:37:27
1. Waiting for source changes... (press enter to interrupt)

Jettyを停止する場合は、container:stop。

> container:stop
[info] stopped o.e.j.w.WebAppContext{/,[file:/xxxxx/hello-servlet/src/main/webapp/]}
[success] Total time: 0 s, completed 2012/05/19 22:33:00

WARファイルを作成する場合は、package。

> package
[info] Packaging /xxxxx/hello-servlet/target/scala-2.9.2/hello-servlet_2.9.2-0.0.1.war ...
[info] Done packaging.
[success] Total time: 1 s, completed 2012/05/19 22:38:35

が、WARファイル名前にScalaのバージョンとかモジュールのバージョンとか書いていて、ちょっと微妙なのでbuild.sbtに以下を追加して

artifactName := { (config: String, module: ModuleID, artifact: Artifact) =>
  artifact.name + "." + artifact.extension
}

reload

> reload
[info] Loading project definition from /xxxxx/hello-servlet/project
[info] Set current project to hello-servlet (in build file:/xxxxx/hello-servlet/)

再度パッケージング。

> package
[info] Packaging /xxxxx/hello-servlet/target/scala-2.9.2/hello-servlet.war ...
[info] Done packaging.
[success] Total time: 1 s, completed 2012/05/19 22:42:10

今度は、期待通りの名前になりました。