CLOVER🍀

That was when it all began.

組み込みTomcatを使ってみる

Tomcat 7から、組み込み版のTomcat APIができていたのは知っていましたが、なんとなく気にはなるものの、触ってこなかったのでちょっと試してみました。

技術者が知っておきたいTomcat 7の新機能20連発 (2/3)
http://www.atmarkit.co.jp/ait/articles/1106/24/news113_2.html

http://dev-blog-eq-diary.blogspot.jp/2012/04/tomcat-jetty.html

http://www.copperykeenclaws.com/embedding-tomcat-7/

アプリケーションサーバとしてのTomcatを用意しなくても、簡単にサーブレットが使えるときっと便利だよね!という発想から始めたのですが、思った通りにいかない部分も…。

で、今回は、Groovy+Gradleでやりました。

build.gradle

apply plugin: 'groovy'
apply plugin: 'application'

mainClassName = 'EmbeddedTomcatServer'

ext {
    tomcatVersion = '7.0.42'
}

repositories {
    mavenCentral()
}

dependencies {
    compile "org.codehaus.groovy:groovy:2.1.6"
    compile "org.apache.tomcat:tomcat-catalina:$tomcatVersion"
    compile "org.apache.tomcat.embed:tomcat-embed-core:$tomcatVersion"
    compile "org.apache.tomcat:tomcat-jasper:$tomcatVersion"
}

依存関係の定義は、こんな感じです。

src/main/groovy/EmbeddedTomcatServer.groovy

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

import org.apache.catalina.startup.Tomcat

class EmbeddedTomcatServer extends HttpServlet {
    public static void main(String[] args) {
        def tomcat = new Tomcat()
        tomcat.port = 8080
        // tomcat.connector.useBodyEncodingForURI = true

        def context = tomcat.addWebapp('/', '.')
        Tomcat.addServlet(context, 'simpleServlet', new SimpleServlet())
        context.addServletMapping('/simpleServlet', 'simpleServlet')

        tomcat.start()
        tomcat.server.await()
    }
}

class SimpleServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) {
        response.writer.write("""|ServletName: [${this.class.simpleName}]
                                 |
                                 |RequestURL: [${request.requestURL}]
                                 |QueryString: [${request.queryString}]
                                 |RequestParameters: ${request.parameterMap}
                                 |""".stripMargin())
    }
}

使い方は、Tomcatクラスのインスタンスを作成して

        def tomcat = new Tomcat()

これに対して、設定をいくつかしていきます。

今回は、リッスンポートを8080にして
*デフォルトポートなので、この場合は別に指定しなくても大丈夫ですが

        tomcat.port = 8080

その他、Connectorの設定をすることもできます。

        // tomcat.connector.useBodyEncodingForURI = true

useBodyEncodingForURIとか設定した方がいいかなーと思ったのですが、動かしてみたらなんか設定しなくても大丈夫でした…。

続いて、Webアプリケーションの設定をします。

今回は、コンテキストパスを「/」にして、Webアプリケーションのディレクトリは「.」にしています。

        def context = tomcat.addWebapp('/', '.')

サーブレットは、ひとつ登録。web.xmlでいうservlet-nameは「simpleServlet」に。

        Tomcat.addServlet(context, 'simpleServlet', new SimpleServlet())

servlet-mappingのurl-patternは、「/simpleServlet」にしました。

        context.addServletMapping('/simpleServlet', 'simpleServlet')

ちなみに、この2つの指定ですが

        tomcat.port = [ポート番号]
        def context = tomcat.addWebapp('/', 'ベースディレクトリ')

カレントディレクトリ配下に

tomcat.[ポート番号]/webapps/[ベースディレクトリ]

がないとWebアプリケーションの起動に失敗しますので、ご注意を。

Webアプリケーションの登録方法はいくつかあるみたいですが、その他はAPIドキュメントを。
http://tomcat.apache.org/tomcat-7.0-doc/api/index.html

あとは、Tomcatを起動して待ち状態にします。

        tomcat.start()
        tomcat.server.await()

Servet#awaitを入れておかないと、すぐに終了してしまいます。この場合は、Ctrl-Cとかで止めてください。

その他、シャットダウンを受け付けるようなコードを書いて、

        tomcat.stop()

を書いてもOKです。

サンプルのサーブレットは、単に受け取ったリクエストの内容をそのまま出力するだけなので、端折ります。

では、起動。

$ gradle --daemon run
:compileJava UP-TO-DATE
:compileGroovy
:processResources UP-TO-DATE
:classes
:run
7 20, 2013 5:08:41 午後 org.apache.coyote.AbstractProtocol init
情報: Initializing ProtocolHandler ["http-bio-8080"]
7 20, 2013 5:08:41 午後 org.apache.catalina.core.StandardService startInternal
情報: Starting service Tomcat
7 20, 2013 5:08:41 午後 org.apache.catalina.core.StandardEngine startInternal
情報: Starting Servlet Engine: Apache Tomcat/7.0.42
7 20, 2013 5:08:42 午後 org.apache.catalina.startup.ContextConfig getDefaultWebXmlFragment
情報: No global web.xml found
7 20, 2013 5:08:44 午後 org.apache.coyote.AbstractProtocol start
情報: Starting ProtocolHandler ["http-bio-8080"]
> Building > :run

確認。

$ telnet localhost 8080
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
GET /simpleServlet?param=%E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%81%AF%E3%80%81%E4%B8%96%E7%95%8C HTTP/1.0

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Length: 228
Date: Sat, 20 Jul 2013 08:10:18 GMT
Connection: close

ServletName: [SimpleServlet]

RequestURL: [http://localhost:8080/simpleServlet]
QueryString: [param=%E3%81%93%E3%82%93%E3%81%AB%E3%81%A1%E3%81%AF%E3%80%81%E4%B8%96%E7%95%8C]
RequestParameters: [param:[こんにちは、世界]]
Connection closed by foreign host.

OKそうです。

なお、普段の自分ならこういうのはGroovy+Grapeで書くところなのですが、これで同じようなコードを書いて実行すると

Caused by: java.lang.NoSuchMethodError: javax.servlet.ServletContext.getSessionCookieConfig()Ljavax/servlet/SessionCookieConfig;
	at org.apache.catalina.deploy.WebXml.configureContext(WebXml.java:1374)
	at org.apache.catalina.startup.ContextConfig.webConfig(ContextConfig.java:1346)
	at org.apache.catalina.startup.ContextConfig.configureStart(ContextConfig.java:878)
	at org.apache.catalina.startup.ContextConfig.lifecycleEvent(ContextConfig.java:376)
	at org.apache.catalina.util.LifecycleSupport.fireLifecycleEvent(LifecycleSupport.java:119)
	at org.apache.catalina.util.LifecycleBase.fireLifecycleEvent(LifecycleBase.java:90)
	at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5322)
	at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
	... 7 more

と、どうもServlet API 2.5以降で追加されたメソッドが見えていないみたいだったので…ちょっと考えてGroovyにServlet APIがいるんじゃあ?と思って確認してみたら

$ ll ~/.gvm/groovy/current/lib/servlet-api-2.4.jar 
-rw-r--r-- 1 xxxxx xxxxx 97693 Feb 18 13:55 /xxxxx/.gvm/groovy/current/lib/servlet-api-2.4.jar

まあ、いますよね…。

念のため、ServletContextがどこからロードされたか確認してみると

println(ServletContext.class.getResource('/javax/servlet/ServletContext.class'))

あ、やっぱり?

jar:file:/xxxxx/.gvm/groovy/2.1.6/lib/servlet-api-2.4.jar!/javax/servlet/ServletContext.class

う〜ん、できればこういうのは、Grapeでやりたかったのですが…環境変えたりして頑張れば動きそうですけど、サクッと動かしたり人にお手軽スクリプトとして紹介…なんて使い方だと、そういうのは微妙。

となると、JDK 6から入っていたJava HTTP Serverの方がいいのかなぁー。

あ、組み込みTomcatは、単に使ってみたかっただけなので自分の目標は達成しました。