GroovyでVert.x - 1. インストールとHello, World! -
あれから
前回からそれなりに時間が経ち、Vert.xのWebページができ、Vert.xでGroovyが正式採用となりました。
そこで、チュートリアルやサンプルを試しながら、何回かに分けてVert.xをご紹介したいと思います。
今回はVert.xのインストールと、HTTPサーバのサンプルです。
実行環境
今回は、以下の環境で実行します。
- OS : Ubuntu 11.10 (32 bit) on VMware Fusion on OS X 10.6
- JDK : 1.7.0_04
- Vert.x : 1.0 beta 10
Vert.xのインストール
Vert.xのインストールは、ダウンロードページからダウンロードしたtar.gzファイルかzipファイルを展開して、展開したディレクトリ(以降、$VERTX_HOME)下のbinディレクトリを環境変数PATHに通すだけです。
ちゃんとインストールされたかどうかは、vertxコマンドで確認します。
$ vertx version vert.x 1.0.beta10
HTTPサーバで"Hello, World!"
HTTPサーバのサンプルとして、お約束の、リクエストが来たら"Hello, World!"を返すHTTPサーバを作成してみましょう。
HTTPサーバの作成
HTTPサーバのコードは次のとおりです:
vertx.createHttpServer().requestHandler { request -> request.response.end('<html><body><h1>Hello, World!</h1></body></html>') }.listen(8080, 'localhost')
HelloWorld.groovyに保存すれば、準備は完了です。
HTTPサーバの起動とアクセス
次に、HTTPサーバを起動します。起動は、vertxのrunコマンドで行います:
$ vertx run HelloWorld.groovy
メッセージは出力されませんが、エラーメッセージなど何も出力されなければ起動成功です。
その後、Webサーバでhttp://localhost:8080/にアクセスし、"Hello, World!"と表示されればOKです。
ソースの解説
では、先程のソースをざっと解説してみましょう。
verticle
Vert.xでは、Vert.xのライブラリを使って作成したサーバを"verticle"という名前で呼びます。前述のHTTPサーバも1つのverticleになります。
"vertx"って?
ソースの冒頭で"vertx"というオブジェクトが出てきますが、これは一体何でしょうか? import文でインポートしているわけでもないのに、エラーもなく使えています。
ここは、Vert.x自体のソースを漁る必要があります。ソースはここからgitで落とします。
$VERTX_HOME/bin/vertxを見ると、
java -Djava.util.logging.config.file=$DIRNAME/../conf/logging.properties -Djruby.home=$JRUBY_HOME \ -Dvertx.mods=$VERTX_MODS -Dvertx.install=$SCRIPTDIR/.. -cp $CLASSPATH org.vertx.java.deploy.impl.cli.VertxMgr "$@"
とありますので、src/main/java/org/vertx/java/deploy/impl/cli/VertxMgr.javaからソースをたどっていきます。
すると、src/main/java/org.vertx.java.deploy.impl.VerticleManager.javaのdoDeployメッソドの中に次のコードがありました(抜粋):
VerticleType type = VerticleType.JAVA; if (main.endsWith(".js")) { type = VerticleType.JS; } else if (main.endsWith(".rb")) { type = VerticleType.RUBY; } else if (main.endsWith(".groovy")) { type = VerticleType.GROOVY; } final VerticleFactory verticleFactory; switch (type) { case JAVA: verticleFactory = new JavaVerticleFactory(this); break; case RUBY: verticleFactory = new JRubyVerticleFactory(this); break; case JS: verticleFactory = new RhinoVerticleFactory(this); break; case GROOVY: verticleFactory = new GroovyVerticleFactory(vertx, this); break; default: throw new IllegalArgumentException("Unsupported type: " + type); }
runコマンドの後に指定するファイルの拡張子でverticleのファクトリを切り替えているようです。
さらにsrc/main/java/org/vertx/java/deploy/impl/groovy/GroovyVerticleFactory.javaを見ると、
import groovy.lang.Binding; import groovy.lang.GroovyClassLoader; import groovy.lang.GroovyCodeSource; import groovy.lang.Script; import org.vertx.groovy.core.Vertx; import org.vertx.groovy.deploy.Container; import org.vertx.java.core.impl.VertxInternal; import org.vertx.java.deploy.Verticle; import org.vertx.java.deploy.impl.VerticleFactory; import org.vertx.java.deploy.impl.VerticleManager; import java.lang.reflect.Method; import java.net.URL; public class GroovyVerticleFactory implements VerticleFactory { private final VerticleManager mgr; private final Vertx gVertx; public GroovyVerticleFactory(org.vertx.java.core.Vertx vertx, VerticleManager mgr) { this.mgr = mgr; this.gVertx = new Vertx((VertxInternal)vertx); } public Verticle createVerticle(String main, ClassLoader cl) throws Exception { URL url = cl.getResource(main); GroovyCodeSource gcs = new GroovyCodeSource(url); GroovyClassLoader gcl = new GroovyClassLoader(cl); Class clazz = gcl.parseClass(gcs); Method stop; try { stop = clazz.getMethod("vertxStop", (Class<?>[])null); } catch (NoSuchMethodException e) { stop = null; } final Method mstop = stop; Method run; try { run = clazz.getMethod("run", (Class<?>[])null); } catch (NoSuchMethodException e) { run = null; } final Method mrun = run; if (run == null) { throw new IllegalStateException("Groovy script must have run() method [whether implicit or not]"); } final Script verticle = (Script)clazz.newInstance(); // Inject vertx into the script binding Binding binding = new Binding(); binding.setVariable("vertx", gVertx); binding.setVariable("container", new Container(new org.vertx.java.deploy.Container((mgr)))); verticle.setBinding(binding); return new Verticle() { public void start() { try { mrun.invoke(verticle, (Object[])null); } catch (Throwable t) { reportException(t); } } public void stop() { if (mstop != null) { try { mstop.invoke(verticle, (Object[])null); } catch (Throwable t) { reportException(t); } } } }; }
と、お目当てのものがありました。
コメントに「Inject vertx into the script binding」にあるところがミソですね。バインド変数として"vertx","container"を定義し、verticle変数(引数で指定したGroovyファイルをインスタンス化したもの、型はgroovy.lang.Script)にバインディングすることで、引数で指定したGroovyファイルの中で"vertx","container"が宣言なしで使えるようです。
"vertx"変数の実体はorg.vertx.groovy.core.Vertxクラスのインスタンス、"container"変数の実体はorg.vertx.groovy.deploy.Containerクラスのインスタンスとなります。
それぞれのクラスの詳細は、GroovyDocを見て下さい。
HTTPサーバの作成
HTTPサーバは、vertxのcreateHttpServer()メソッドを呼び出して作成します。vertxには、この他にもcreateXxxx()系のメソッドが幾つかあり、目的のサーバの種類ごとに使い分けます。
createHttpServer()メソッドは戻り値として、org.vertx.groovy.core.http.HttpServerを継承したクラスのインスタンスを返します。
def server = vertx.createHttpServer()
リクエストハンドラの設定
リクエストの処理はハンドラで行います。org.vertx.groovy.core.http.HttpServer#requestHandler(groovy.lang.Closure hndlr)メソッドに、ハンドラとして引数が1つのクロージャを指定します。このハンドラが、リクエストごとに呼び出されます。
クロージャの引数はリクエストをあらわすオブジェクトで、org.vertx.groovy.core.http.HttpServerRequestクラスのインスタンスです。
def server = vertx.createHttpServer() server.requestHandler { request -> request.response.end('<html><body><h1>Hello, World!</h1></body></html>') }
requestHandler()メソッドも戻り値として、org.vertx.groovy.core.http.HttpServerを継承したクラスのインスタンスを返します。
サーバの起動・リクエストの受信準備
サーバの起動は、org.vertx.groovy.core.http.HttpServer#listen(int port)、またはorg.vertx.groovy.core.http.HttpServer#listen(int port, java.lang.String host)メソッドで、ポート番号のみ、あるいはポート番号とホスト名(またはIPアドレス)を指定して行います。
def server = vertx.createHttpServer() server.requestHandler { request -> request.response.end('<html><body><h1>Hello, World!</h1></body></html>') } server.listen(8080, 'localhost')
終わりに
今回は、HTTPサーバで"Hello, World!"を表示するところまでやってみました。次回は、リクエストハンドラを少し掘り下げるのと、別の書き方で"Hello, World!"を表示するところをやってみたいと思います。