GroovyでVert.x - 2. HTTPリクエストハンドラ その1 -

さて

今回もHTTPサーバの続きで、HTTPリクエストハンドラを掘り下げたいと思います。

実行環境

今回は、以下の環境で実行します。

なお、1.0.1 finalにはJavaScript用のjarファイルが含まれていないため、

vertx run HelloWorld.groovy

を実行すると、

Exception in thread "main" java.util.ServiceConfigurationError: org.vertx.java.deploy.VerticleFactory: Provider org.vertx.java.deploy.impl.rhino.RhinoVerticleFactory could not be instantiated: java.lang.NoClassDefFoundError: org/mozilla/javascript/ContextFactory

という例外が発生し起動しません。
これに対処するためには、1.0 finalのtarボール、あるいはソースからjs.jarを取得し、${VERTX_HOME}/lib/jarsにコピーしてください。

前回のおさらい

前回は"Hello, World!"を返すHTTPサーバを実行しました。

vertx.createHttpServer().requestHandler { request ->
    request.response.end('<html><body><h1>Hello, World!</h1></body></html>')
}.listen(8080, 'localhost')

このコードでは、どのようなリクエストでも一律"Hello, World!"を返すことになり、リクエストに応じた内容を返すことができません。また、返す内容もハードコーディングされています。

今回は

今回と次回でリクエストハンドラの引数でありリクエストをあらわすHttpServerRequestと、レスポンスをあらわすHttpServerResponseを紹介し、前回のサンプルコードを改良したいと思います。

HttpServerRequest

HTTPサーバのリクエストハンドラは、リクエストをあらわすHttpServerRequestクラスのオブジェクトを引数に取ります。このHttpServerRequestクラスのオブジェクトから様々なリクエスト情報を取得することができます。

リクエストのHTTPメソッドの取得

リクエストのHTTPメソッドは、HttpServerRequest#getMethod()で取得できます。

vertx.createHttpServer().requestHandler { request ->
    request.response.end("<html><body><h1>Your method is ${request.method}</h1></body></html>")
}.listen(8080, 'localhost')

例えば、Webブラウザでhttp://localhost:8080/aaa/bbb/ccc/index.html?xxx=1&yyy=2&zzz=3にアクセスした場合、

Your method is GET

となります。

リクエストのURIの取得

リクエストのURIは、HttpServerRequest#getUri()で取得できます。

vertx.createHttpServer().requestHandler { request ->
    request.response.end("<html><body><h1>Your URI is ${request.uri}</h1></body></html>")
}.listen(8080, 'localhost')

例えば、Webブラウザでhttp://localhost:8080/aaa/bbb/ccc/index.html?xxx=1&yyy=2&zzz=3にアクセスした場合、

Your URI is /aaa/bbb/ccc/index.html?xxx=1&yyy=2&zzz=3

となります。

リクエストのパスの取得

リクエストのパスは、HttpServerRequest#getPath()で取得できます。

vertx.createHttpServer().requestHandler { request ->
    request.response.end("<html><body><h1>Your path is ${request.path}</h1></body></html>")
}.listen(8080, 'localhost')

例えば、Webブラウザでhttp://localhost:8080/aaa/bbb/ccc/index.html?xxx=1&yyy=2&zzz=3にアクセスした場合、

Your path is /aaa/bbb/ccc/index.html

となります。

リクエストのクエリの取得

リクエストのクエリは、HttpServerRequest#getQuery()でString型で取得できます。

vertx.createHttpServer().requestHandler { request ->
    request.response.end("<html><body><h1>Your query is ${request.query}</h1></body></html>")
}.listen(8080, 'localhost')

例えば、Webブラウザでhttp://localhost:8080/aaa/bbb/ccc/index.html?xxx=1&yyy=2&zzz=3にアクセスした場合、

Your query is xxx=1&yyy=2&zzz=3

となります。

リクエストヘッダの取得

リクエストヘッダは、HttpServerRequest#getHeaders()でjava.util.Map型で取得できます。

vertx.createHttpServer().requestHandler { request ->
    def sb = new StringBuilder()
    for (def h : request.headers) {
        sb << "<li>${h.key} : ${h.value}</li>"
    }
    request.response.end("<html><body><h1>Your headers are <ul>${sb.toString()}</ul></h1></body></html>")
}.listen(8080, 'localhost')

例えば、Webブラウザでhttp://localhost:8080/aaa/bbb/ccc/index.html?xxx=1&yyy=2&zzz=3にアクセスした場合、

Your headers are
* Accept-Language : ja,en-US;q=0.8,en;q=0.6
* Host : localhost:8080
* Accept-Charset : Shift_JIS,utf-8;q=0.7,*;q=0.3
* Accept-Encoding : gzip,deflate,sdch
* User-Agent : Mozilla/5.0 (X11; Linux i686) AppleWebKit/536.5 (KHTML, like Gecko) Chrome/19.0.1084.52 Safari/536.5
* Accept : text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
* Connection : keep-alive
* Cache-Control : max-age=0

となります。

リクエストパラメータの取得

リクエストパラメータは、HttpServerRequest#getQuery()でString型で取得できますが、HttpServerRequest#getParams()でjava.util.Map型で取得できます。

vertx.createHttpServer().requestHandler { request ->
    def sb = new StringBuilder()
    for (def h : request.params) {
        sb << "<li>${h.key} : ${h.value}</li>"
    }
    request.response.end("<html><body><h1>Your params are <ul>${sb.toString()}</ul></h1></body></html>")
}.listen(8080, 'localhost')

例えば、Webブラウザでhttp://localhost:8080/aaa/bbb/ccc/index.html?xxx=1&yyy=2&zzz=3にアクセスした場合、

Your params are
* xxx : 1
* yyy : 2
* zzz : 3

となります。
ただし、http://localhost:8080/aaa/bbb/ccc/index.html?xxx=1&yyy=2&zzz=3&xxx=4&yyy=5&zzz=6と、同じパラメータ名を複数指定した場合、

Your params are
* xxx : 1
* yyy : 2
* zzz : 3

となり、クエリの最初に指定された値が採用されるようです。

リクエストボディの取得 - dataHandler

POSTでデータを送信するような場合、送信されたデータはHttpServerRequest#getQuery()メソッドあるいはHttpServerRequest#getParams()メソッドでは取得できません。
どうやって取得するかというと、リクエストハンドラのHttpServerRequestオブジェクトに対してリクエストボディを対処するハンドラ(クロージャ)を設定し、そのハンドラ内で取得します。

vertx.createHttpServer().requestHandler { request ->
    request.dataHandler { buffer ->
        println "buffer = ${buffer.toString()}"
    }
    request.response.end("<html><body><h1>You send data.</h1></body></html>")
}.listen(8080, 'localhost')

リクエストボディを対処するデータハンドラをHttpServerRequest#dataHandler(groovy.lang.Closure)メソッドで設定します。
HttpServerRequest#dataHandlerに設定するデータハンドラは、リクエストボディ用のバッファクラスであるorg.vertx.groovy.core.buffer.Bufferクラスのオブジェクトを1つ引数にとります。
HttpServerRequest#dataHandlerに設定するデータハンドラは、リクエストボディのチャンクごとに呼び出されます。

リクエストボディの取得 - endHandler

1リクエストのボディが1チャンクであれば上記のコードで良いですが、複数のチャンクに分かれている場合は、チャンクごとにリクエストボディの内容を保持するようにします。

import org.vertx.groovy.core.buffer.Buffer

vertx.createHttpServer().requestHandler { request ->
    def body = new Buffer(0)
    request.dataHandler { buffer ->
        body << buffer
    }
    request.endHandler {
        request.response.end("<html><body><h1>You send data - ${body.toString()}</body></html>")
    }
}.listen(8080, 'localhost')

リクエストハンドラ内でBufferクラスのインスタンスを生成し、データハンドラ内で、引数で渡されたボディチャンクの内容をBufferクラスのインスタンスに追加します。
データハンドラはチャンクごとに呼ばれるため、チャンクの終わり(= ボディの受信の終わり)を知る必要があります。Vert.xのHTTPサーバの場合、チャンクの受信が完了した時に専用のイベントを発生させ、対応するイベントハンドラを実行します。この場合、エンドハンドラになります。
エンドハンドラをHttpServerRequest#endHandler(groovy.lang.Closure)メソッドで設定します。エンドハンドラに渡される引数はありません。エンドハンドラ内で、Bufferクラスのインスタンスに対応します。

リクエストボディの取得 - bodyHandler

HTTPチャンクごとではなく、全てのリクエストボディを受信した後で処理をしたい場合もあると思います。その場合は、ボディハンドラを設定することで対応できます。

vertx.createHttpServer().requestHandler { request ->
    request.bodyHandler { buffer ->
        request.response.end("<html><body><h1>You send data - ${buffer.toString()}</body></html>")
    }
}.listen(8080, 'localhost')

ボディハンドラは、すべてのリクエストボディを受信した後で一度だけ呼び出されます。

終わりに

今回は、受信したリクエストを処理するところを主に見てきました。次回は、レスポンス部分を見てみたいと思います。