GroovyでVert.x - 1. インストールとHello, World! -

あれから

前回からそれなりに時間が経ち、Vert.xのWebページができ、Vert.xでGroovyが正式採用となりました。
そこで、チュートリアルやサンプルを試しながら、何回かに分けてVert.xをご紹介したいと思います。
今回はVert.xのインストールと、HTTPサーバのサンプルです。

実行環境

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

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!"を表示するところをやってみたいと思います。