Java Servletのサンプルコード、Java抜き。

JettyというJavaで書かれたWebサーバというかJava Servletコンテナを触っている。目的はWebSocketのサーバ側実装だ。候補としてNode.jsも考えたのだけど、全く未知の世界に足を踏み入れようと思ってJettyにした。

どのあたりが未知の世界かというと――Javaのコードは大昔にプログラミング未経験者向けの初歩の初ぐらいを触ったきりで今ではすっかり忘れてしまった、Java Servletは触ったことすらない、JREはインストール済みだけどJDKはインストールしていない、当然ながらTomcatを始めとするJava界隈のツールを使ったこともない。

最新版だと思われるJetty 8.1.5.v20120716をダウンロードし、色々弄って実験用のコンテンツを何となくそれっぽい感じに構築できるようになってきた気がするので、ここで試しに普通のServletを書いてみることにした。この経験を元にWebSocketのServletに挑戦するつもり。

ただ何しろJDKを入れていないし、それより何より最近Java VM上で動作する言語処理系を幾つかインストールしたばかりなので、それらの言語で実装することにした。

手抜きをしてstart.jarを使って単体のアプリとしてJettyを動かしているので、実装したServletのコードはJava VMバイトコードコンパイルしてJettyに組み込むことになる。

Scala 2.9.2

最初にScalaで実装した。まあ雛形となるサンプルがすぐに見つかったからなあ。

package org.trashbox.servlet

import javax.servlet.http.{
  HttpServlet,
  HttpServletRequest => HSReq,
  HttpServletResponse => HSRes
}

class HelloScala extends HttpServlet {
  override def doGet(request:HSReq, response:HSRes) {
    response.setContentType("text/html")
    response.setCharacterEncoding("UTF-8")
    response.getWriter().println(
      """<!DOCTYPE html>
        |<html>
        |  <head>
        |    <title>hello, scala</title>
        |  </head>
        |  <body>
        |    <h1>hello, scala</h1>
        |  </body>
        |</html>""".stripMargin)
  }

  override def doPost(request:HSReq, response:HSRes) =
    doGet(request, response)
}

Scalaオブジェクト指向プログラミングも関数プログラミングもどちらもOKな言語なので、「Javaのコードを真似つつ云々」的な使い方も結構いける気がする。

このコードをJetty用にコンパイルする場合はこんな感じ。

scalac -classpath /path/to/jetty/lib/servlet-api-3.0.jar HelloScala.scala

実際にServletとして動作させる際にはJettyのライブラリにscala-library.jarを追加すること。

Groovy 2.0.1

本来ならGroovletを使うのが筋のような気がするけど、後でWebSocketのServletを書く為の修行なのでGroovletを使わずに書いてみた。Groovletを使わないServletのサンプルコードが意外と見つからない。

package org.trashbox.servlet

import javax.servlet.http.HttpServlet
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse

class HelloGroovy extends HttpServlet {
  void doGet(HttpServletRequest request,
             HttpServletResponse response) throws IOException {
    response.setContentType("text/html")
    response.setCharacterEncoding("UTF-8")
    response.getWriter().println("""\
<!DOCTYPE html>
<html>
  <head>
    <title>hello, groovy</title>
  </head>
  <body>
    <h1>hello, groovy</h1>
  </body>
</html>""")
  }

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

Groovyの言語仕様はJavaベースらしいので、Javaのサンプルコードを真似て実装することにはScalaよりも長けているのかもしれない。

このコードをJetty用にコンパイルする場合はこんな感じ。Scalaの場合と結構似ている。

groovyc -classpath /path/to/jetty/lib/servlet-api-3.0.jar HelloGroovy.groovy

実際にServletとして動作させる際にはJettyのライブラリにgroovy-2.0.1.jarとasm-4.0.jarを追加すること。

Clojure 1.4.0

意外と人気があるのか、Clojureのサンプルもそこそこ見つかる。但し英語だけど……。

『プログラミングClojure』や『7つの言語 7つの世界』を参照してサンプルを解読しつつ、真似て書いてみたのがコレ。

(ns org.trashbox.servlet.HelloClojure
  (:gen-class :extends javax.servlet.http.HttpServlet))

(defn -doGet [_ request response]
  (.setContentType response "text/html")
  (.setCharacterEncoding response "UTF-8")
  (-> response
    .getWriter
    (.println (str "<!DOCTYPE html>\n"
                   "<html>\n"
                   "  <head>\n"
                   "    <title>hello, clojure</title>\n"
                   "  </head>\n"
                   "  <body>\n"
                   "    <h1>hello, clojure</h1>\n"
                   "  </body>\n"
                   "</html>"))))

(defn -doPost [_ request response]
  (-doGet nil request response))

それにしてもLispServletを記述できる日が来るとは……。

Jetty用にコンパイルする方法だけど、GroovyやScalaとは異なりClojureではコンソールから直接ソースをコンパイルすることができない模様。ドキュメントを見ても関数compileを使う方法しか分からなかった。そこでオプション `-e' を使ってコンソール上で関数compileを呼ぶようにしてみた。

java -cp '/path/to/clojure-1.4.0.jar;/path/to/jetty/lib/servlet-api-3.0.jar;.;./classes' \
     clojure.main \
     -e "(compile 'org.trashbox.servlet.HelloClojure)"

ソースの置き場所やビルドされたバイトコードの出力先については手探りの状態。上記のコマンドでビルドする場合の配置(カレントからの相対パス)はこんな感じ。

ソースコード ./org/trashbox/servlet/HelloClojure.clj
出力先 ./classes/org/trashbox/servlet/

うーん、いまいちルールがよく分からない。

なお実際にServletとして動作させる際にはJettyのライブラリにclojure-1.4.0.jarを追加すること。

Rhino 1.7R4

ちょっと変り種かもしれないRhino。どこが変り種か? コードを見れば分かる。

/*jslint maxerr: 50, indent: 2 */
function doGet(request, response) {
  'use strict';
  response.setContentType('text/html');
  response.setCharacterEncoding('UTF-8');
  response.getWriter().println([
    '<!DOCTYPE html>',
    '<html>',
    '  <head>',
    '    <title>hello, rhino</title>',
    '  </head>',
    '  <body>',
    '    <h1>hello, rhino</h1>',
    '  </body>',
    '</html>'
  ].join('\n'));
}

function doPost(request, response) {
  'use strict';
  doGet(request, response);
}

グローバルな関数の定義だけ……packageの指定やクラスの継承はどこに行った?

実はソースコード上でpackageを指定したりJavaのクラスを継承したりする方法が分からなかった。そこでJetty用にコンパイルする時にオプション `-package' と `-extends' を使用した。

java -cp '/path/to/rhino/js.jar;/path/to/jetty/lib/servlet-api-3.0.jar' \
     org.mozilla.javascript.tools.jsc.Main \
     -extends javax.servlet.http.HttpServlet \
     -package org.trashbox.servlet \
     HelloRhino.js

実際にServletとして動作させる際にはJettyのライブラリにjs.jarを追加すること。

Jython 2.5.2

おまけ。コードを書くだけ書いた。

from javax.servlet.http import HttpServlet

class HelloJython (HttpServlet):
    def doGet(self, request, response):
        response.setContentType("text/html")
        response.setCharacterEncoding("UTF-8")
        response.getWriter().println("""<!DOCTYPE html>
<html>
  <head>
    <title>hello, jython</title>
  </head>
  <body>
    <h1>hello, jython</h1>
  </body>
</html>""")

    def doPost(self, request, response):
        self.doGet(request, response)

しかしJava VMバイトコードコンパイルする方法が分からない……。あとパッケージ指定の方法も不明。

その他

Ceylon
Java VM用のバイトコードの吐かせ方が分からなかった*1。断念。
Kotlin
Kotlin単体をダウンロードしたけどkotlinc-jvmがエラーを吐く。IntelliJ IDEAは持ってない。断念。
Noop
処理系のビルド以前にソースの入手にすらたどり着けなかった。断念。
上記以外
多分幾つか言語があると思われるけど、不勉強のため知らない。

*1:そもそも可能なのか?