Java API マニュアル

  • 共有データ
  • バッファ
  • バッファの作成
  • バッファへの書き込み
  • バッファからの読み込み
  • その他のバッファメソッド
  • JSON
  • 遅延タスク・周期タスク
  • TCPサーバとクライアントを書く
  • ユーザデータグラムプロトコル(UDP)
  • フロー制御(ストリームとポンプ)
  • HTTP サーバとクライアントを書く
  • パターンマッチングを使って HTTP リクエストをルーティングする
  • WebSocket
  • SockJS
  • SockJS - イベントバス ブリッジ
  • ファイルシステム
  • DNSクライアント
  • バーティクルを書く


    メインマニュアルで述べた通り、バーティクルは Vert.x の実行単位です。

    言い替えると、Vert.x はバーティクルと呼ばれるコードのパッケージを実行するコンテナで、バーティクルを2つ以上のスレッドで並行実行しないことを確実とします。バーティクルは Vert.x がサポートする言語ならどれでも書くことができ、Vert.x は一つのインスタンスで多数のバーティクルインスタンスを並行実行します。

    あなたが書いた Vert.x アプリケーションのコードは全て、バーティクルインスタンスの中で実行されます。

    シンプルなプロトタイプや、ちょっとしたタスクであれば、「生の」バーティクルを書いてコマンドラインから直接実行できますが、ほとんどのケースで、バーティクルはVert.xモジュールに包むことになるでしょう。

    ここでは、シンプルな「生の」バーティクルを書いてみましょう。

    例として単純な TCP エコーサーバを書いてみます。サーバはコネクションを受け付け、全ての受信データをエコーバックします。

    テキストエディタで以下のコードをコピーして、Server.javaとして保存してください。

    import org.vertx.java.core.Handler;
    import org.vertx.java.core.net.NetSocket;
    import org.vertx.java.core.streams.Pump;
    import org.vertx.java.platform.Verticle;
    
    public class Server extends Verticle {
    
      public void start() {
        vertx.createNetServer().connectHandler(new Handler<NetSocket>() {
          public void handle(final NetSocket socket) {
            Pump.createPump(socket, socket).start();
          }
        }).listen(1234);
      }
    }
    

    実行します:

    vertx run Server.java
    

    サーバが起動しました。telnet で接続してみます:

    telnet localhost 1234
    

    送信データ(Enterキーで送信されます)がエコーバックされることが解ると思います。

    おめでとうございます!あなたは最初のバーティクルを書き終えました。

    あらかじめ、.javaファイルを.classファイルにコンパイルしておく必要が無いことに注意してください。Vert.x は.javaファイルを直接実行する方法を知っているのです。- 内部的に、オンザフライでコンパイルして実行しているのです(お好みでしたら、.class にしておいてこれを実行する、という手ももちろんあります)。

    どの Java バーティクルもorg.vertx.java.deploy.Verticleクラスを継承しなければいけません。また、そのstartメソッドをオーバライドしてください。これは、バーティクルの開始時に Vert.x により呼び出されます。

    以後、このマニュアルでは、コードの断片はバーティクル内部で実行されている前提で話を進めます。

    非同期スタート


    バーティクルが、start()メソッドで何か非同期で行なわなければならないことがあって(例えば他のバーティクルを開始するなど)、それが完了しない限りスタートしたと見なすべきでない場合もあるでしょう。

    こういうときは非同期バージョンのstart()メソッドを実装することができます:

    public void start(final Future<Void> startedResult) {
      // For example - deploy some other verticle
      container.deployVerticle("foo.js", new AsyncResultHandler<String>() {
        public void handle(AsyncResult<String> deployResult) {
          if (deployResult.succeeded()) {
            startedResult.setResult(null);
          } else {
            startedResult.setFailure(deployResult.cause());
          }
        }
      });
    }
    

    バーティクルのクリーンアップ


    サーバ、クライアント、イベントバスハンドラ、タイマは、バーティクル停止時に自動的にクローズまたはキャンセルされますが、もしバーティクル終了時に他のクリーンアップロジックを実行したいときは、バーティクルでstopメソッドを実装すると、バーティクルがアンデプロイされるときに呼び出されます。

    containerオブジェクト


    各バーティクルインスタンスはcontainerプロパティを持ちます。これは実行中のコンテナをバーティクルから見たものです。

    containerオブジェクトはバーティクルやモジュールをデプロイ・アンデプロイするためのメソッドと、設定、環境変数、アクセスされるロガーをもっています。

    vertxオブジェクト


    各バーティクルインスタンスはvertxと呼ばれるプロパティをもっています。これは Vert.x コア API へのアクセスを提供します。TCP、HTTP、ファイルシステムアクセス、イベントバス、タイマなど Vert.x で行うことのほとんどはコア API を使用するでしょう。

    バーティクルに設定を渡す


    モジュール、バーティクルにコマンドラインから設定を渡すには、-confオプションを使用します。例えば:

    vertx runmod com.mycompany~my-mod~1.0 -conf myconf.json
    

    「生の」バーティクルに対しては:

    vertx run foo.js -conf myconf.json
    

    -confオプションの引数は有効な JSON オブジェクトを含んだファイル名です。

    与えられた設定はバーティクル内部では、containerオブジェクト上のconfig()メソッドで参照することができます:

    JsonObject config = container.config();
    
    System.out.println("Config is " + config);
    

    返される設定は、JSON オブジェクトを表現する、org.vertx.java.core.json.JsonObjectのインスタンスです(あたりまえですね!)。これを使ってバーティクルを設定することができます。

    バーティクルをこのように一貫性のある方法で設定できるようにしておくと、プログラミング言語に関係なく簡単に設定を行うことができるようになります。

    バーティクルからのロギング


    各バーティクルは固有のロガーを与えられます。ロガーを使うためのリファレンスは、containerインスタンスのlogger()メソッドで取得できます:

    Logger logger = container.logger();
    
    logger.info("I am logging something");
    

    ロガーはorg.vertx.java.core.logging.Loggerクラスのインスタンスで、以下のメソッドをもちます:

    • trace
    • debug
    • info
    • warn
    • error
    • fatal

    どれもあなたが期待する通りの意味です。

    ログファイルはデフォルトではシステムの temp ディレクトリに、vertx.logという名前のファイルで保存されます。私の Linux 環境では/tmpです。

    ロギングに関して、これ以上はメインマニュアルを見てください。

    バーティクルから環境変数にアクセスする


    バーティクルから環境変数には、containerオブジェクトのenv()メソッドで、マップとしてアクセスできます。

    コンテナを終了させる


    バーティクルからコンテナのexit()メソッドを呼ぶことで、Vert.x インスタンスをクリーンシャットダウンすることができます。

    プログラムでバーティクルをデプロイ・アンデプロイする


    他のバーティクルからプログラムでバーティクルをデプロイ・アンデプロイすることができます。どのバーティクルもこの方法でデプロイされ、メインのバーティクルのリソース(クラス、スクリプト、その他のファイル)を見ることができます。

    シンプルなバーティクルをデプロイする


    プログラムでバーティクルをデプロイするためには、containerオブジェクトのdeployVerticleメソッドを使用します。

    バーティクルを1インスタンスデプロイするには:

    container.deployVerticle(main);
    

    ここで、mainはバーティクルの名前(例えば、Javaファイルやクラスの完全修飾名)です。

    main が何か?については、メインマニュアルのVert.xの実行の章を見てください。

    ワーカバーティクルをデプロイする


    deployVerticleメソッドは標準(ワーカでない)バーティクルをデプロイします。ワーカバーティクルのデプロイにはdeployWorkerVerticleメソッドを使用します。このメソッドはdeployVerticleと同じ意味をもつ同じ数の引数を取ります。

    プログラムでモジュールをデプロイする


    モジュールのデプロイにはdeployModuleを使ってください。例を挙げます:

    container.deployModule("io.vertx~mod-mailer~2.0.0-beta1", config);
    

    これは、io.vertx~mod-mailer~2.0.0-beta1モジュールのインスタンスを指定した設定でデプロイします。モジュールについての、これ以上の情報はモジュールマニュアルを参照してください。

    プログラムでバーティクルに設定を渡す


    JSON で書かれた設定は、プログラムでデプロイされたバーティクルに渡すことができます。バーティクル内ではこれにconfig()メソッドを使ってアクセスできます。例を挙げます:

    JsonObject config = new JsonObject();
    config.putString("foo", "wibble");
    config.putBoolean("bar", false);
    container.deployVerticle("foo.ChildVerticle", config);
    

    これで、ChildVerticleは、前述したconfigプロパティを通じてその設定にアクセスすることができます。

    アプリケーションのロードをコーディネイトするバーティクル


    複数のバーティクルから成り、起動時に全てのバーティクルが必要となるアプリケーションでは、アプリケーションの設定のメンテナンスと他の全てのバーティクルを起動するための別のバーティクルが使えます。これをアプリケーションのスタータバーティクルと考えても良いでしょう。

    例えば、以下のようなバーティクル(AppStarter.groovy)を作ります:

    // アプリケーションの設定
    
    JsonObject appConfig = container.config();
    
    JsonObject verticle1Config = appConfig.getObject("verticle1_conf");
    JsonObject verticle2Config = appConfig.getObject("verticle2_conf");
    JsonObject verticle3Config = appConfig.getObject("verticle3_conf");
    JsonObject verticle4Config = appConfig.getObject("verticle4_conf");
    JsonObject verticle5Config = appConfig.getObject("verticle5_conf");
    
    // アプリケーションを構成するバーティクルを起動する
    
    container.deployVerticle("verticle1.js", verticle1Config);
    container.deployVerticle("verticle2.rb", verticle2Config);
    container.deployVerticle("foo.Verticle3", verticle3Config);
    container.deployWorkerVerticle("foo.Verticle4", verticle4Config);
    container.deployWorkerVerticle("verticle5.js", verticle5Config, 10);
    

    そして設定を JSON で書いて 'config.json' として持たせておきます:

    {
        "verticle1_conf": {
            "foo": "wibble"
        },
        "verticle2_conf": {
            "age": 1234,
            "shoe_size": 12,
            "pi": 3.14159
        }, 
        "verticle3_conf": {
            "strange": true
        },
        "verticle4_conf": {
            "name": "george"
        },
        "verticle5_conf": {
            "tel_no": "123123123"
        }       
    }
    

    そしてAppStarter.groovyをあなたのモジュールのメインとすれば、以下のシンプルなコマンドでアプリケーションの全てを起動することができます:

    vertx runmod com.mycompany~my-mod~1.0 -conf config.json
    

    もしアプリケーションが大きく、バーティクルだけでなく複数のモジュールで構成されている場合も、同じテクニックが使えるでしょう。

    より一般的には、スタータバーティクルを JavaScript、Groovy、RubyまたはPythonのスクリプト言語で記述します。これらの言語は Java よりも JSON のサポートに関しては優れていますので、スタータバーティクルで全てのコンフィグをメンテナンスできるでしょう。

    インスタンス数を指定する


    デフォルトでは、バーティクルをデプロイすると、インスタンスが1つだけ生成されます。バーティクルインスタンスは厳密にシングルスレッドですので、インスタンスが生成されると少なくとも1つの CPU コアが使用されることになります。

    Vert.x はたくさんのバーティクルインスタンスを並行にデプロイすることでスケールします。

    1つのバーティクルまたはモジュールを複数インスタンス化したい場合は、このようにインスタンス数を指定します:

    container.deployVerticle("foo.ChildVerticle", 10);
    

    または

    container.deployModule("io.vertx~some-mod~1.0", 10);
    

    上記のサンプルはどちらもインスタンスを 10 デプロイします。

    デプロイ完了を通知させる


    実際のバーティクルのデプロイは非同期でdeployVerticleまたはdeployModuleがリターンしてから少々してから完了します。バーティクルのデプロイが完了したことを知りたければ、deployVerticleまたはdeployModuleにハンドラを渡します:

    container.deployVerticle("foo.ChildVerticle", new AsyncResultHandler<String>() {
        public void handle(AsyncResult<String> asyncResult) {
            if (asyncResult.succeeded()) {
                System.out.println("The verticle has been deployed, deployment ID is " + asyncResult.result());
            } else {
                asyncResult.cause().printStackTrace();
            }
        }
    });
    

    完了するとハンドラはAsyncResultのインスタンスを引数に渡します。AsyncResultsucceeded()またはfailed()メソッドを使用してデプロイが正常終了したかどうか確認できます。

    result()メソッドは非同期操作の結果(もしあれば)を参照するために用意されています。今回のケースではデプロイID(バーティクル/モジュールのデプロイを相次いで実行してる場合などに必要でしょう)が入っています。

    cause()メソッドは実行に失敗したときに受け取ったThrowableを参照するために用意されています。

    バーティクルまたはモジュールのアンデプロイ


    どのバーティクルもモジュールもバーティクル内のプログラムからデプロイでき、デプロイされた子バーティクル・子モジュールは親バーティクルがアンデプロイするときに自動的にアンデプロイされますので、多くの場合バーティクルの中で手動でアンデプロイする必要はありませんが、手動でアンデプロイする必要がある場合はデプロイIDを渡してundeployVerticleまたはundeployModuleを呼び出せばできます。

    container.undeployVerticle(deploymentID);
    

    アンデプロイが完了したかどうか知りたい場合は、デプロイ同様アンデプロイメソッドにハンドラを渡します。

    アプリケーションのスケーリング


    バーティクルインスタンスはだいたい常にシングルスレッドです(上級機能であり通常の開発での使用は想定していない、マルチスレッドのワーカバーティクルだけがこの例外です)。つまり単一インスタンスは高々一つの CPU コアを使います。

    コアを越えてスケールするには、バーティクルインスタンスを複数デプロイする必要があります。どの様なタイプか、いくつのバーティクルかなどはあなたのアプリケーションの仕様に依存します。

    バーティクルインスタンスの複数デプロイは、プログラムでもできますし、コマンドラインでデプロイするときに-instancesオプションで指定しても良いです。

    イベントバス


    イベントバスは Vert.x システムの神経です。

    イベントバスによってバーティクルは書かれた言語に関わらず、また同一の Vert.x インスタンスで動作していても、異なる Vert.x インスタンスで動作していても、お互い通信することができます。

    さらにイベントバスはクライアントのブラウザで動作する JavaScript とも同じイベントバスで通信できます(詳しくは後ほど)。

    イベントバスは、複数のサーバー·ノードと複数のブラウザにまたがる分散型ピア·ツー·ピア·ネットワークを形成します。

    イベントバス API は信じられないほど単純です。基本はハンドラの登録、ハンドラの登録解除、メッセージの送信(send)と配信(publish)です。 (訳注:ここでは、対1に送信するsendを「送信」、対多に送信するpublish「配信」としています)

    まずはいくつかのセオリーを:

    セオリー


    アドレッシング


    メッセージはイベントバス上のアドレスに対して送られます。

    Vert.x のアドレス方式は全くファンシーではありません。Vert.x ではアドレスは単なる文字列で、どんな文字列でも構いません。が、名前空間をピリオドで区切るなど、何らかのスキームを持たせたほうが良いでしょう。

    いくつか例を挙げておくと、europe.news.feed1, acme.games.pacman, sausages,Xなどなど。

    ハンドラ


    ハンドラはイベントバスからメッセージを受けとるものです。ハンドラはアドレスに対して登録します。

    同じ、または違うバーティクルにある複数のハンドラを、同じアドレスに登録することができます。1つのハンドラは複数の異なるアドレスに登録されることができます。

    配信(Publish) / メッセージをサブスクライブする


    イベントバスはメッセージの配信(publishing)をサポートしています。メッセージはアドレスに対して配信されます。配信(Publishing)とは、メッセージをアドレスに登録されている全てのハンドラに送ることを言います。これはpublish/subscribeメッセージパターンに似ています

    ポイントツーポイント、及びリクエスト-レスポンスメッセージング


    イベントバスはポイントツーポイントメッセージングもサポートしています。メッセージは1つのアドレスに送信されます。Vert.x はそのアドレスに登録されている唯一つのハンドラにメッセージを渡します。もしアドレスに複数のハンドラが宛先アドレスに登録されていた場合、そのうち1つが厳密ではないラウンドロビンアルゴリズムで選択されます。

    ポイントツーポイントメッセージングでは、リプライハンドラをメッセージ送信時に指定することもできます。メッセージが受信側に届いてハンドルされたときに、受信側はメッセージにリプライするかどうかを決めます(リプライするかどうかは任意です)。リプライするときはリプライハンドラが呼び出されます。

    リプライが送信側に届くと、さらにこれに対してリプライすることも可能です。これは無限に続けることができ、2つのバーティクルが対話してセットアップを行うといった用途に使えます。これはリクエスト-レスポンスメッセージと言われる一般的なメッセージパターンです。

    トランジェント


    イベントバス上の全てのメッセージはトランジェントで、イベントバスの一部または全部に異常が起きると、メッセージはおそらく失われます。メッセージのロストをケアする場合は、ハンドラを羃等にし、送信側はリカバリの後にリトライするように書かねばならないでしょう。

    メッセージを消さずに取って置きたい場合は、永続的なワークキューモジュールなどを使ってください。

    メッセージの型


    イベントバス上を送られるメッセージは単純な文字列、数字、またはブール値です。Vert.x のバッファやJSONデータを送ることもできます。

    バーティクル間の通信には JSON を使用することを強くお勧めします。JSON は Vert.x がサポートしているどの言語でも簡単に作成・パースできます。

    イベントバス API


    それでは API を見ていきましょう。

    ハンドラの登録と登録解除


    test.addressというアドレスにメッセージハンドラをセットするには、だいたい以下のようなことをします:

    EventBus eb = vertx.eventBus();
    
    Handler<Message> myHandler = new Handler<Message>() {
        public void handle(Message message) {
            System.out.println("I received a message " + message.body);
        }
    };
    
    eb.registerHandler("test.address", myHandler);
    

    これだけです。これで、このハンドラは、このアドレスに送られる全てのメッセージを受けとることになります。

    Messageクラスはジェネリクス(総称型)で、特定のメッセージ型であるMessage<Boolean>, Message<Buffer>, Message<byte[]>, Message<Byte>, Message<Character>, Message<Double>, Message<Float>, Message<Integer>, Message<JsonObject>, Message<JsonArray>, Message<Long>, Message<Short> and Message<String>を含んでいます。

    もし、受信するメッセージの型が常に特定の型であることが判っているのであれば、以下のように、特定の型を使っても構いません:

    Handler<Message<String>> myHandler = new Handler<Message<String>>() {
        public void handle(Message<String> message) {
            String body = message.body;
        }
    };
    

    registerHandlerの戻り値はイベントバス自身です。このフルエントな API は、これを連鎖的に呼び出すことができます。

    アドレスにハンドラを登録したとき、クラスタ全体に新しいハンドラが登録されたことが伝わるまでに少々時間がかかることがあります。registerHandlerメソッドの引数の第3引数にもう一つハンドラを渡すことで、登録が完全に終わったら知らせてくれるようにすることができます。このハンドラは、情報がクラスタ全体に到達したときに一度だけ呼ばれます。例を挙げると:

    eb.registerHandler("test.address", myHandler, new AsyncResultHandler<Void>() {
        public void handle(AsyncResult<Void> asyncResult) {
            System.out.println("The handler has been registered across the cluster ok? " + asyncResult.succeeded());
        }
    });
    

    ハンドラの登録解除は、とても解りやすいです。単にアドレスとハンドラを渡してunregisterHandlerを呼び出すだけです:

    eb.unregisterHandler("test.address", myHandler);
    

    一つのハンドラは同じ、または異なるアドレスに複数登録できます。ですので、登録解除するハンドラを一意に指定するにはアドレスとハンドラの両方を指定する必要があるのです。

    登録時同様、登録解除がクラスタ全体に伝わるにも少々時間がかかることがあります。登録解除が伝わり終わったことを知るには、やはりもう一つのハンドラを第3引数に渡します:

    eb.unregisterHandler("test.address", myHandler, new AsyncResultHandler<Void>() {
        public void handle(AsyncResult<Void> asyncResult) {
            System.out.println("The handler has been unregistered across the cluster ok? " + asyncResult.succeeded());
        }
    });
    

    バーティクルが存在している間中ずっとハンドラを有効にしておきたいのであれば、わざわざ登録解除する必要はありません。Vert.x はバーティクルが停止するときに、自動的にハンドラの登録を解除します。

    メッセージの配信(Publishing)


    メッセージを配信するのも同じく自明で非常に簡単です。アドレスを指定して配信するだけです。例を挙げると:

    eb.publish("test.address", "hello world");
    

    このメッセージは"test.address"に登録されている全てのハンドラに届けられます。

    メッセージの送信(Sending)


    「送信」は、アドレスに登録されたハンドラのうち、ただ1つのハンドラがメッセージを受け取る、ポイントツーポイントのメッセージングパターンです。ハンドラは厳密ではないラウンドロビンアルゴリズムで選ばれます。

    eb.send("test.address", "hello world");
    

    メッセージに返信する


    メッセージを送信した後、返信を受け取りたいこともあるでしょう。これはリクエスト-レスポンスパターンとして知られてます。

    これには、第3引数にリプライハンドラを指定してメッセージを送信します。受信側で受信し、返信する場合はこのメッセージのreplyメソッドで返信します。このメソッドが実行されると、送信側に返信が送り返されます。例を見たほうが解りやすいでしょうか:

    受信側はこうです:

    Handler<Message<String>> myHandler = new Handler<Message<String>>() {
        public void handle(Message<String> message) {
            System.out.println("I received a message " + message.body);
    
            // Do some stuff
    
            // 返信する
    
            message.reply("This is a reply");
        }
    };
    
    eb.registerHandler("test.address", myHandler);
    

    送信側はこうです:

    eb.send("test.address", "This is a message", new Handler<Message<String>>() {
        public void handle(Message<String> message) {
            System.out.println("I received a reply " + message.body);            
        }
    });
    

    空の返信やヌル(null)の返信も認められます。

    返信に対してさらに返信することもできますので、2つの異なるバーティクル間で複数ラウンドに渡る対話を構成することもできます。

    返信にタイムアウトを指定する


    リプライハンドラを指定してメッセージを送り、リプライが返ってこなかったら、デフォルトではハンドラは登録解除されないまま残されます。

    これを防ぐため、Handler<AsyncResult<Message>>をミリ秒単位のタイムアウトを指定したリプライハンドラとすることもできます。もしリプライがタイムアウトまでに受け取れたら、ハンドラはメッセージを含めたAsyncResultとともに呼び出されますが、タイムアウトまでにリプライが返ってこなかったらハンドラは自動的に登録解除され、「失敗」とともに呼び出されますので、これに対応することができます。

    例を見てみましょう:

    eb.sendWithTimeout("test.address", "This is a message", 1000, new Handler<AsyncResult<Message<String>>>() {
        public void handle(AsyncResult<Message<String>> result) {
            if (result.succeeded()) {
                System.out.println("I received a reply " + message.body);            
            } else {
    
                System.err.println("No reply was received before the 1 second timeout!");
            }
        }
    });
    

    送信タイムアウトの場合、cause()メソッドとともに例外が投げられ、AsyncResultReplyException型となります。ReplyExceptionインスタンスのfailureType()の戻り値は、ReplyFailure.TIMEOUTです。

    さらにデフォルトのタイムアウトをイベントバス自身に設定することもできます。このタイムアウトはリプライハンドラとともにsend(...)メソッドをそのイベントバスで実行するときに参照されます。デフォルト値は-1で、これは決してタイムアウトしないことを意味します。(これは以前の Vert.x との後方互換のために残されています)。

    eb.setDefaultReplyTimeout(5000);
    
    eb.send("test.address", "This is a message", new Handler<Message<String>>() {
        public void handle(Message<String> message) {
            System.out.println("I received a reply before the timeout of 5 seconds");            
        }
    });
    

    メッセージにリプライするときも、指定時間内に返信を得るように、Handler<AsyncResult<Message>>でタイムアウトを指定することができます。API は以前使用したものに似ています:

    message.replyWithTimeout("This is a reply", 1000, new Handler<AsyncResult<Message<String>>>() {
        public void handle(AsyncResult<Message<String>> result) {
            if (result.succeeded()) {
                System.out.println("I received a reply to the reply" + message.body);            
            } else {
                System.err.println("No reply to the reply was received before the 1 second timeout!");
            }
        }
    });
    

    リプライ失敗を知る


    メッセージをタイムアウトとリザルトハンドラとともに送信したとき、送り先ハンドラが全く無い場合には、リザルトハンドラが「失敗」のAsyncResultとともに呼び出されます。これのcause()メソッドを呼ぶとReplyExceptionが得られます。ReplyExceptionインスタンスのfailureType()を呼び出すと、ReplyFailure.NO_HANDLERSが得られます。

    例を挙げます

    eb.registerHandler("test.address", new Handler<Message<String>>() {
        public void handle(Message<String> message) {
            message.fail(123, "Not enough aardvarks");  
        }
    });
    
    eb.sendWithTimeout("test.address", "This is a message", 1000, new Handler<AsyncResult<Message<String>>>() {
        public void handle(AsyncResult<Message<String>> result) {
            if (result.succeeded()) {
                System.out.println("I received a reply " + message.body);            
            } else {
                ReplyException ex = (ReplyException)result.cause();
                System.err.println("Failure type: " + ex.failureType();  
                System.err.println("Failure code: " + ex.failureCode();                
                System.err.println("Failure message: " + ex.message();
            }
        }
    });
    

    メッセージの型


    以下のどの型(またはこれらの型を組み合わせて箱詰めした型)のメッセージでも送ることができます。:

    • boolean
    • byte[]
    • byte
    • char
    • double
    • float
    • int
    • long
    • short
    • java.lang.String
    • org.vertx.java.core.json.JsonObject
    • org.vertx.java.core.json.JsonArray
    • org.vertx.java.core.buffer.Buffer

    同じ JVM 内であっても Vert.x バッファと JSON オブジェクトはコピーが送られますので、異なるバーティクルは完全に同じオブジェクトにはアクセスできません。

    もう少し例を挙げます:

    数値を送る:

    eb.send("test.address", 1234);
    eb.send("test.address", 3.14159);
    

    ブーリアンを送る:

    eb.send("test.address", true);
    

    JSON オブジェクトを送る:

    JsonObject obj = new JsonObject();
    obj.putString("foo", "wibble");
    eb.send("test.address", obj);
    

    ヌルメッセージも送れます:

    eb.send("test.address", null);
    

    バーティクルが JSON をもちいて通信する、というのが良い習慣です。JSON は Vert.x がサポートしている全てのプログラミング言語で簡単に生成・パースできるからです。

    分散イベントバス


    ネットワーク上の各 Vert.x インスタンスを同じイベントバスに参加させるには、各 Vert.x インスタンスを-clusterオプションを付けて起動します。

    メインマニュアルのVert.x の実行の章により詳細な情報があります。

    一旦これを行うと、クラスタモードで起動されたどの Vert.x インスタンスも、分散型イベントバスを形成するように結合されます。

    共有データ


    異なるバーティクルが安全な方法でデータを共有することを許すことが理にかなう場合もあるでしょう。Vert.x ではjava.util.concurrent.ConcurrentMapjava.util.Setのデータ構造をバーティクル間で共有することができます。

    注意:変更可能なデータに起因する問題を防ぐため、vert.x は数値、ブーリアン、文字列またはバッファといったイミュータブル(変更不能)な型のみを共有可能としています。バッファは、共有データから取得するときに自動的にコピーされます。ですので、異なるバーティクルインスタンスは決して同一のオブジェクトインスタンスを参照することはありません。

    現状、データは同じ Vert.x インスタンスにあるバーティクル間でのみ共有できます。Vert.x の今後のバージョンでは、クラスタ内のどの vert.x インスタンスでもデータを共有できることを目指しています。

    共有マップ


    共有マップを使ってバーティクル間でデータを共有するには、第一にマップのリファレンスを取得します。あとは、java.util.concurrent.ConcurrentMapのインスタンスとして普通に使います。

    ConcurrentMap<String, Integer> map = vertx.sharedData().getMap("demo.mymap");
    
    map.put("some-key", 123);
    

    こうしておけば、異なるバーティクルから、これにアクセスすることができます:

    ConcurrentMap<String, Integer> map = vertx.sharedData().getMap("demo.mymap");
    
    // etc
    

    共有セット


    バーティクル間のデータ共有に共有セットを使うには、セットのリファレンスを取得します。

    Set<String> set = vertx.sharedData().getSet("demo.myset");
    
    set.add("some-value");
    

    続いて、異なるバーティクルにて:

    Set<String> set = vertx.sharedData().getSet("demo.myset");
    
    // etc
    

    バッファ


    Vert.x 内のほとんどのデータはorg.vertx.groovy.core.buffer.Bufferのインスタンスを使用して、あちこちに移動します。

    バッファは読み書き可能なゼロかそれ以上のバイトシーケンスを表わしたもので、バイトを書き込む際に、必要な長さ自動的に拡張されます。スマートなバイト配列とみなしても良いでしょう。

    バッファの作成


    空の、新規バッファの作成:

    Buffer buff = new Buffer();
    

    文字列からバッファを作ります。デフォルトでは、バッファ内の文字列は UTF-8 にエンコードされます。

    Buffer buff = new Buffer("some-string");
    

    エンコーディングを指定して文字列からバッファを作ることもできます。例えば:

    Buffer buff = new Buffer("some-string", "UTF-16");
    

    バイト配列(byte[])からバッファを作ります。

    byte[] bytes = new byte[] { ... };
    new Buffer(bytes);
    

    作成されたバッファの初期サイズについてのヒント。バッファに格納されるデータの量が解っているときは、サイズを指定することができます。サイズを指定しないときよりも多くのメモリをアロケートしますが、書き込みが行われるたびに何度もリサイズするより効率的と言えます。

    作成されたバッファはです。指定されたサイズをゼロで埋めたバッファが作成される訳ではありません。

    Buffer buff = new Buffer(100000);
    

    バッファへの書き込み


    バッファへの書き込みは二通りの方法があります:追加とランダムアクセスです。どちらのケースでもバッファは常にバイトが入り切るように拡張されます。バッファへの書き込みにおいて、IndexOutOfBoundsExceptionが発生することはありません。

    バッファに追加する


    バッファに追加書き込みするには、appendXXXメソッドを使います。XXXの部分は、書き込むデータの型、つまり他のバッファ・バイト配列(byte[])・文字列・及び他の全てのプリミティブ型ごとに変ります。

    appendXXXメソッドの戻り値はバッファ自身です。ですので、これらは連鎖的に呼び出すことが出来ます:

    Buffer buff = new Buffer();
    
    buff.appendInt(123).appendString("hello\n");
    
    socket.write(buff);
    

    ランダムアクセスによるバッファ書き込み


    setXXXメソッドを使うと、特定のインデクスを指定してバッファに書き込みを行うこともできます。XXXの部分は、書き込むデータの型により変わります。全てのセットメソッドはインデクスを第一引数に取ります - これは書き込みデータの書き込み開始位置を表します。

    バッファは常にデータが入るように拡張されます。

    Buffer buff = new Buffer();
    
    buff.setInt(1000, 123);
    buff.setBytes(0, "hello");
    

    バッファからの読み込み


    getXXXメソッドでバッファからデータを読み出すことができます。XXXの部分は読み込むデータ型により変わります。どのメソッドも第一引数はデータ取得を開始するインデクス番号です。

    Buffer buff = ...;
    for (int i = 0; i < buff.length(); i += 4) {
        System.out.println("int value at " + i + " is " + buff.getInt(i));
    }
    

    その他のバッファメソッド


    • length():バッファの長さを取得します。バッファの長さはバイトインデクスの最大値 + 1となります
    • copy(): バッファを完全にコピーします。

    メソッドレベルの詳細情報は JavaDoc を参照してください。

    JSON


    JSON を最もよくサポートしているのは JavaScript です。また、Ruby のハッシュリテラルは JSON をコード内で簡単に表現させます。Java では、そう簡単にはいきません。

    このため、Java バーティクルから JSON を使うために、Vert.x は JSON オブジェクトと JSON 配列を表現する、簡単な JSON クラスを用意しています。これらのクラスは、JSON オブジェクトまたは配列に対し、JSONでサポートされている全ての型のセット・ゲットメソッドを提供します。

    JSON オブジェクトはorg.vertx.java.core.json.JsonObjectのインスタンスとして表現されます。JSON 配列はorg.vertx.java.core.json.JsonArrayのインスタンスとなります。

    Java バーティクルから JSON メッセージをイベントバスに送信または受信するサンプルを見てみましょう。

    EventBus eb = vertx.eventBus();
    
    JsonObject obj = new JsonObject().putString("foo", "wibble")
                                     .putNumber("age", 1000);
    
    eb.send("some-address", obj);
    
    // ....
    // ハンドラをどこかに入れます:
    
    public void handle(Message<JsonObject> message) {
        System.out.println("foo is " + message.body.getString("foo");
        System.out.println("age is " + message.body.getNumber("age");
    }
    

    メソッドは、このオブジェクトをシリアライズされた JSON の形にコンバートする、及びその逆を行うためにも存在します。

    JSON API の全貌については、JavaDoc を参照してください。

    遅延タスク・周期タスク


    Vert.x で、遅延後または周期的に処理を実行したい、というのはよくある要望です。

    標準のバーティクルでは遅延を生じさせるためにスレッドをスリープさせることは、イベントループをブロックすることになってしまうので出来ません。

    その代わりに、Vert.x タイマを使って下さい。タイマはワンショットまたは周期的です。ここでその両方を見ていきます。

    ワンショットタイマ


    ワンショットタイマはミリ秒単位のディレイの後にイベントハンドラを呼び出します。

    タイマをセットし一度だけタイムアウトさせるには、setTimerメソッドに遅延時間とハンドラを渡します。

    long timerID = vertx.setTimer(1000, new Handler<Long>() {
        public void handle(Long timerID) {
            log.info("And one second later this is printed"); 
        }
    });
    
    log.info("First this is printed");
    

    戻り値はキャンセルする際に使用する一意のタイマIDです。ハンドラにもタイマIDを渡します。

    周期タイマ


    周期的にタイムアウトするタイマをセットするには、setPeriodicメソッドを使用します。最初に渡したディレイが周期となります。setPeriodicの戻り値も一意のタイマID(型はlongです)です。このIDは後にタイマをキャンセルするときに必要となります。イベントハンドラに渡す引数も一意のタイマIDとなります:

    long timerID = vertx.setPeriodic(1000, new Handler<Long>() {
        public void handle(Long timerID) {
            log.info("And every second this is printed"); 
        }
    });
    
    log.info("First this is printed");
    

    タイマのキャンセル


    周期タイマをキャンセルするには、タイマIDを指定してcancelTimerメソッドを呼び出します。例を挙げます:

    long timerID = vertx.setPeriodic(1000, new Handler<Long>() {
        public void handle(Long timerID) {            
        }
    });
    
    // 即座にタイマをキャンセルする
    
    vertx.cancelTimer(timerID);
    

    イベントハンドラ内でキャンセルすることもできます。以下は、タイマが10回満了したらキャンセルする例です。

    long timerID = vertx.setPeriodic(1000, new Handler<Long>() {
        int count;
        public void handle(Long timerID) {  
            log.info("In event handler " + count); 
            if (++count == 10) {
                vertx.cancelTimer(timerID);
            }          
        }
    });
    

    TCPサーバとクライアントを書く


    Vert.x を使えばTCPサーバとクライアントを書くのは簡単です。

    ネットサーバ


    ネットサーバの生成


    TCP サーバを生成するにはcreateNetServerメソッドをvertxインスタンス内で呼び出します。

    NetServer server = vertx.createNetServer();
    

    リッスンの開始


    ポートを指定してコネクションを待つにはこうします:

    NetServer server = vertx.createNetServer();
    
    server.listen(1234, "myhost");
    

    listenの第一引数はポートです。ワイルドカードを示す0も指定できます。これは、そのとき空いているポートからランダムに1つ選んで待ち受けます。待ち受けを開始すれば、サーバのportプロパティで実際にどのポートを使っているのかを知ることができます。

    第二引数はホスト名またはIPアドレスです。省略されるとデフォルトで0.0.0.0(全ての有効なインタフェース)を待ち受けます。

    実際のバインドは非同期になりますので、サーバはlistenメソッドから戻ったでも、実際にはまだ待ち受けていないこともあるでしょう。実際に待ち受け状態になっていることを知りたければ、listenメソッドを呼ぶときにハンドラを渡してください。例です:

    server.listen(1234, "myhost", new AsyncResultHandler<Void>() {
        public void handle(AsyncResult<NetServer> asyncResult) {
            log.info("Listen succeeded? " + asyncResult.succeeded());
        }  
    });
    

    コネクションの検出


    コネクションを受けたことを検出するには、サーバのconnectHandlerメソッドにハンドラを渡します。ハンドラはコネクションが確立したら呼び出されます:

    NetServer server = vertx.createNetServer();
    
    server.connectHandler(new Handler<NetSocket>() {
        public void handle(NetSocket sock) {
            log.info("A client has connected!");
        }
    });
    
    server.listen(1234, "localhost");
    

    これには、ちょっと興味深いものがあります。'A client has connected!'はクライアントから接続される度に表示されます。

    connectHandlerメソッドの戻り値はサーバ自身ですので、複数の実行文を続けて書くことが出来ます。つまり上記のプログラムは以下のように書き直すことができます:

    NetServer server = vertx.createNetServer();
    
    server.connectHandler(new Handler<NetSocket>() {
        public void handle(NetSocket sock) {
            log.info("A client has connected!");
        }
    }).listen(1234, "localhost");
    

    または

    vertx.createNetServer().connectHandler(new Handler<NetSocket>() {
        public void handle(NetSocket sock) {
            log.info("A client has connected!");
        }
    }).listen(1234, "localhost");
    

    これは Vert.x API の間で共通したパターンです。

    ネットサーバを停止する


    サーバを閉じるには、closeメソッドを呼ぶだけです。

    server.close();
    

    サーバの停止は実際には非同期なので、closeメソッドから戻った時点でサーバが完全に停止しているとは限りません。完全に停まったことを知りたければ、closeメソッドにハンドラを渡します。

    このハンドラはサーバが完全に停止したら呼び出されます。

    server.close(new AsyncResultHandler<Void>() {
        public void handle(AsyncResult<Void> asyncResult) {
            log.info("Close succeeded? " + asyncResult.succeeded());
        }  
    });
    

    ネットサーバをバーティクルが存在している間常に動作させるのであれば、明示的にcloseを呼び出す必要はありません。Vert.x コンテナはどのサーバでもアンデプロイされるときにクローズします。

    ネットサーバのプロパティ


    ネットサーバは、その動作を規定するプロパティのセットをもっています。まず、TCP パラメータを微調整するために使用されるプロパティ群があります。ほとんどのケースでは、セットする必要はないでしょう:

    • setTCPNoDelay(tcpNoDelay) true ならネーグルアルゴリズムは無効、false なら有効です。

    • setSendBufferSize(size) TCP の送信バッファサイズをバイト単位で指定します。

    • setReceiveBufferSize(size) TCP の受信バッファサイズをバイト単位で指定します。

    • setTCPKeepAlive(keepAlive) keepAliveが true ならTCP keep aliveは有効、falseなら無効です。

    • setReuseAddress(reuse) reuseがtrue なら、TIME_WAIT 状態のアドレスはクローズ後再利用されます。

    • setSoLinger(linger)

    • setTrafficClass(trafficClass)

    ネットサーバにはさらに、 SSL を設定するために使われるプロパティセットがあります。これについては後述します。

    データ操作


    ここまで、ネットサーバの生成・コネクションの受け付けを見てきましたが、接続してからの面白いことは何もしてません。では、それを見ていきましょう。

    コネクションが確立したら、NetSocketインスタンスに渡されたコネクションハンドラが呼び出されます。このソケットに似たインタフェースが実際のコネクションで、データの読み書きほか様々な、ソケットに似たことができます。

    ソケットからデータを読み出す


    データを読み出すには、dataHandlerをセットする必要があります。このハンドラはorg.vertx.java.core.buffer.Bufferのインスタンスとともに、データがソケットに着くために呼び出されます。以下のコードを試してみて、telnet で何かデータを送ってみて下さい:

    NetServer server = vertx.createNetServer();
    
    server.connectHandler(new Handler<NetSocket>() {
        public void handle(NetSocket sock) {
            sock.dataHandler(new Handler<Buffer>() {
                public void handle(Buffer buffer) {
                    log.info("I received " + buffer.length() + " bytes of data");
                }
            });
        }
    }).listen(1234, "localhost");
    

    ソケットにデータを書き込む


    データを書き込むには、writeメソッドを実行します。これには何通りかの方法があります:

    シングルバッファの場合:

    Buffer myBuffer = new Buffer(...);
    sock.write(myBuffer);
    

    文字列。この場合、文字列は UTF-8 でエンコードされてネットワークに書き込まれます。

    sock.write("hello");
    

    エンコードを指定した文字列。この場合文字列は指定されたエンコーディングでエンコードされ、ネットワークに書き込まれます。

    sock.write("hello", "UTF-16");
    

    writeメソッド・左シフト演算子はキューにデータを書き込んだらすぐにリターンします。

    実際の送信は非同期で、少し時間をおいてから実行されます。

    まとめましょう。

    単純に受信データを送り返す TCP エコーサーバです:

    NetServer server = vertx.createNetServer();
    
    server.connectHandler(new Handler<NetSocket>() {
        public void handle(final NetSocket sock) {
            sock.dataHandler(new Handler<Buffer>() {
                public void handle(Buffer buffer) {
                    sock.write(buffer);
                }
            });
        }
    }).listen(1234, "localhost");
    

    ソケットリモートアドレス


    remoteAddress()メソッドで、ソケットのリモートアドレス(すなわち、TCP IPコネクションの反対側のアドレス)をみつけることができます。

    ソケットローカルアドレス


    localAddress()メソッドで、ソケットのローカルアドレス(すなわち、TCP IPコネクションのこちら側のアドレス)をみつけることができます。

    ソケットをクローズする


    closeメソッドを実行することでソケットをクローズできます。これは基本的な TCP コネクションを閉じます。

    クローズハンドラ


    ソケットがクローズされたことを知りたければ、クローズハンドラをセットしましょう:

    NetServer server = vertx.createNetServer();
    
    server.connectHandler(new Handler<NetSocket>() {
        public void handle(final NetSocket sock) {
            sock.closedHandler(new VoidHandler() {
                public void handle() {
                    log.info("The socket is now closed"); 
                }
            });
        }
    }).listen(1234, "localhost");
    

    クローズハンドラは、切断がサーバにより行われたか、クライアントにより行われたかに関わらず呼び出されます。

    例外ハンドラ


    ソケットに対し例外ハンドラをセットすることもできます。ハンドラはコネクションで例外が発生すると非同期に呼び出されます:

    NetServer server = vertx.createNetServer();
    
    server.connectHandler(new Handler<NetSocket>() {
        public void handle(final NetSocket sock) {
            sock.exceptionHandler(new Handler<Throwable>() {
                public void handle(Throwable t) {
                    log.info("Oops, something went wrong", t);                      
                }
            });
        }
    }).listen(1234, "localhost");
    

    イベントバス書き込みハンドラ


    全てのネットソケットは自動的にイベントバス上のハンドラを登録し、任意のバッファがこのハンドラに受け取られた場合、それをハンドラ自身に書き込みます。これにより、ハンドラのアドレスとバッファを送ることで、ネットソケットへのデータの書き込みは、全く別のバーティクル、または違う Vert.x インスタンスから行うことが可能となります。

    ハンドラのアドレスはwriteHandlerID()メソッドで取得できます。

    全く異なるバーティクルから、何らかのデータをネットソケットに書き込む例です:

    String writeHandlerID = ... // E.g. retrieve the ID from shared data
    
    vertx.eventBus().send(writeHandlerID, buffer);
    

    ストリームの読み書き


    ネットソケットはorg.vertx.groovy.core.streams.ReadStreamorg.vertx.groovy.core.streams.WriteStreamを実装しています。これは、コネクションでのフロー制御を可能とし、HTTPリクエストと応答、WebSokect、非同期ファイルアクセスといった他のオブジェクトとの間のコネクションデータをポンプします。

    これについてはストリームとポンプの章でより詳しく見ていきます。

    TCP サーバのスケーリング


    バーティクルインスタンスは必ずシングルスレッドです。

    シンプルな TCP サーバを作り、それを単一のインスタンスにデプロイすると、そのサーバの全てのハンドラは常に同一のイベントループ(スレッド)で実行されます。

    これは、たくさんの CPU コアをもつサーバに、ただ1つのインスタンスをデプロイすると、利用するコアはたった1つ、ということを意味します。

    これを改善するには単に、サーバにモジュールのインスタンスをもっと多くデプロイします:

    vertx runmod com.mycompany~my-mod~1.0 -instances 20
    

    「生の」バーティクルに対しては…

    vertx run foo.MyApp -instances 20
    

    上記の例は 20 インスタンスのモジュールまたはバーティクルを同一の Vert.x インスタンスで実行します。

    これを実行しても、エコーサーバは機能的には以前と全く同様に動作することが見えると思います。しかし、あたかも魔法のように、サーバの全てのコアが使用可能となり、より多くの仕事をこなすことができるのです。

    この時点であなたは疑問に思うかも知れません。「ちょっと待て。どうやって複数のサーバが同じホストの同じポートを待ち受けることができるの?複数インスタンスをデプロイするやいなや、ポートコンフリクトして終わるの?」

    Vert.x はちょっとした魔法をここでかけます。.

    既存のサーバと同一ホスト・同一ポートで待ち受けるサーバをデプロイしても、実際に同一ホスト・同一ポートで待ち受ける新しいサーバの生成が試行される訳ではありません。

    代わりに、内部的には単一のサーバを維持しながら、バーティクルによって到着したコネクションをラウンドロビン方式でいずれかのコネクションハンドラに処理させます。

    このため、Vert.x の TCP サーバは、個々の Vert.x バーティクルインスタンスは厳密にシングルスレッドを維持したまま、使用可能なコアの数までスケールできます。使用するマルチコアマシンの規模に応じてロードバランサを書くような特別なトリックをユーザがする必要は何一つありません。

    ネットクライアント


    ネットクライントはサーバに対して TCP コネクションを確立するために使われます。

    ネットクライアントを作る


    TCP クライアントを作るには、vertxインスタンス上で、createNetClientメソッドを呼び出します。

    NetClient client = vertx.createNetClient();
    

    コネクションを確立する


    サーバとコネクションを確立するには、connectメソッドを実行します:

    NetClient client = vertx.createNetClient();
    
    client.connect(1234, "localhost", new AsyncResultHandler<NetSocket>() {
        public void handle(AsyncResult<NetSocket> asyncResult) {
            if (asyncResult.succeeded()) {
              log.info("We have connected! Socket is " + asyncResult.result());
        } else {
              asyncResult.cause().printStackTrace();
            }            
        }
    });
    

    connectメソッドはポート番号を第一引数に取り、接続先サーバのホスト名または IP アドレスが第二引数として続きます。第三引数はクロージャです。このクロージャは実際にコネクションが確立したときに呼び出されます。

    コネクションハンドラに渡される引数はAsyncResult<NetSocket>で、NetSocketresult()メソッドから受け取ることができます。ソケットへのデータの読み書きは、全くサーバ側と同様となります。

    さらにクロースハンドラをセットすることでクローズすることもできますし、これまたサーバ側のNetSocketと全く同じように例外ハンドラをセットしてReadStreamまたはWriteStreamのように使うこともできます。

    コネクション再確立の設定


    ネットクライアントは、サーバに接続できなかったり、コネクションが失なわれたといった際に自動的にリトライ・コネクションを再確立するするように設定できます。このためには、reconnectAttemptsreconnectIntervalの各プロパティをセットします:

    NetClient client = vertx.createNetClient();
    
    client.setReconnectAttempts(1000);
    
    client.setReconnectInterval(500);
    

    reconnectAttemptsはサーバへの接続を最大何回試行するかを指定します。-1を指定すると永久に試行します。デフォルトは0、すなわちコネクションの再確立するを行いません。

    reconnectIntervalはミリ秒単位で再確立を試行する際の間隔を指定します。デフォルトは1000です。

    ネットクライアントのプロパティ


    ネットサーバと同じく、NetClientも動作を規定するための TCP プロパティセットをもっています。これらの名前・意味はNetServerと同じです。

    NetClientは SSL 設定のためのプロパティをさらにもちます。これについては後に述べます。

    SSL サーバ


    ネットサーバは、Transport Layer Security(以前は SSL として知られていました)で動作するように設定することもできます。

    ネットサーバが SSL サーバとして動作するときも、ネットサーバおよびネットソケットの API は標準的なソケットを操作するときと同じです。サーバに SSL を使わせるには、listenメソッドを呼ぶ前にネットサーバを設定します。

    SSL を有効にするには、ネットサーバのsetSSL(true)メソッドを呼び出します。

    サーバはキーストア(key store)とともに設定される必要があります。また、オプションでトラストストア(trust store)も使用できます。

    キーストア・トラストストアはどちらも、JDK に同梱されているkeytoolユーティリティを使って管理できるJava キーストアです。

    keytool コマンドはキーストアの生成、証明書のインポート・エクスポートをサポートします。

    キーストアはサーバ証明書を含む必要があります。これは必須です - サーバが証明書を持っていない限り、クライアントはサーバに SSL を通じてコネクションを確立することはできません。

    キーストアはkeyStorePathkeyStorePasswordプロパティを使ってサーバに設定できます。

    トラストストアはオプションであり、信頼すべき任意のクライアントの証明書を含んでいます。これはクライアント認証が必要な場合にのみ使用されます。

    サーバを、サーバ証明書を使用するように設定するには、これだけで十分です:

    NetServer server = vertx.createNetServer()
                   .setSSL(true)
                   .setKeyStorePath("/path/to/your/keystore/server-keystore.jks")
                   .setKeyStorePassword("password");
    

    server-keystore.jksが、サーバ証明書を含んでいるとします。

    クライアント認証も必要とするように設定するには:

    NetServer server = vertx.createNetServer()
                   .setSSL(true)
                   .setKeyStorePath("/path/to/your/keystore/server-keystore.jks")
                   .setKeyStorePassword("password")
                   .setTrustStorePath("/path/to/your/truststore/server-truststore.jks")
                   .setTrustStorePassword("password")
                   .setClientAuthRequired(true);
    

    server-truststore.jksの中に、サーバが信頼すべきクライアントの証明書を含んでいます。

    clientAuthRequiredが true にセットされている状態で、証明書を提供しないクライアントまたはサーバが信頼できない証明書を提供するクライアントによる接続は成功しません。

    SSL クライアント


    ネットクライアントは簡単に SSL を使用するように設定できます。SSL を使用しているときでも、通常のソケットを使用しているときと全く同じ API をもちます。

    ネットクライアントで SSL を有効にするには、ssl(true)ファンクションを呼び出します。

    setTrustAll(true)メソッドが実行されていると、クライアントは全てのサーバの証明書を信頼します。 コネクションは暗号化されますが、このモードでは「マンインザミドル(中間者)攻撃」に対し脆弱です。つまり、あなたは接続相手を確認することはできません。このモードを使うには注意が必要です。デフォルトはfalseです。

    setTrustAll(false)が実行されていると(これがデフォルトです)、クライアントのトラストストアが設定され、クライアントが信頼するサーバの証明書が含まれている必要があります。

    クライアントのトラストストアはサーバ側同様、標準的な Java キーストアです。クライアントのトラストストアへのファイルパスはNetClientsetTrustStorePathメソッドを実行して指定します。接続時にサーバが示す証明書がクライアントのトラストストアにない場合、接続は失敗します。

    サーバがクライアント認証を必要とする場合、クライアントは接続時に自分の証明書をサーバに渡さなければなりません。この証明書はクライアントのキーストアに存在している必要があります。繰り返しになりますが、これは通常の Java キーストアです。クライアントのキーストアの位置は、NetClientsetKeyStorePathメソッドを実行して指定します。

    クライアントを、全てのサーバ証明書を信頼するように設定するには(これは危険ですよ):

    NetClient client = vertx.createNetClient()
                   .setSSL(true)
                   .setTrustAll(true);
    

    クライアントを、自分のトラストストアにある証明書のみを信頼するように設定するには:

    NetClient client = vertx.createNetClient()
                   .setSSL(true)
                   .setTrustStorePath("/path/to/your/client/truststore/client-truststore.jks")
                   .setTrustStorePassword("password");
    

    クライアントを、自分のトラストストアにある証明書のみを信頼し、さらにクライアント証明書を提供するように設定するには:

    NetClient client = vertx.createNetClient()
                   .setSSL(true)
                   .setTrustStorePath("/path/to/your/client/truststore/client-truststore.jks")
                   .setTrustStorePassword("password")
                   .setClientAuthRequired(true)
                   .setKeyStorePath("/path/to/keystore/holding/client/cert/client-keystore.jks")
                   .setKeyStorePassword("password");
    

    ユーザデータグラムプロトコル(UDP)


    ユーザデータグラムプロトコル(UDP)を Vert.x で扱うことは朝飯前です。

    UDP はコネクションレスの転送、つまり基本的にリモートピアへの持続的な接続はできないことを意味します。

    その代わり、各々にリモートアドレスが含まれたパケットを送受信することになります。

    これにより UDP では、送信データグラムパケッドが受信エンドポイントに確実に届く保証がなく、その意味で TCP のように安全ではありません。

    唯一の保証は、完全に受信されたか、全く受信されないか、ということになります。

    また、各データを1パケットで送らねばなりませんので、ネットワークインタフェースの MTU サイズを越えるデータは送信できません。

    なお、パケットサイズを MTU 以下にしたとしても送信に失敗することがあることに注意してください。

    どのくらいのサイズで失敗となるかはオペレーティングシステム他に依存します。ですので、経験則では、とにかくできるだけ小さいパケットを送信するようにすると良いです。

    性質上、UDP はパケットドロップが起きても構わないようなアプリケーション(例えば、モニタリングアプリケーションのような)にベストフィットします。

    UDP のメリットは TCP と比較してとてもオーバヘッドが少ないことです。UDP は、NetServer と NetClient で扱うことができます(上を参照してください)。

    データグラムソケットを生成する


    UDP を使うには、まずDatagramSocketを作らなければいけません。単にデータを送りたいだけなのか、送受信したいかはここでは関係ありません。

    DatagramSocket socket = vertx.createDatagramSocket(InternetProtocolFamily.IPV4);
    

    戻されたDatagramSocketはまだ特定のポートにバインドされていません。

    これは、(クライアントとして)単にデータを送りたいだけなら問題になりませんが、これ以上は次の章でお話しします。

    データグラムパケットを送信する


    前述の通り、ユーザデータグラムプロトコル(UDP)はデータをパケットにしてリモートピアに送信しますが、持続的な形でリモートピアに接続するわけではありません。

    これは、各パケットを異なるリモートピアに送信することもできるということを意味します。

    パケットの送信は以下のように簡単にできます:

    DatagramSocket socket = vertx.createDatagramSocket(InternetProtocolFamily.IPV4);
    Buffer buffer = new Buffer("content");
    // バッファを送る
    socket.send(buffer, "10.0.0.1", 1234, new AsyncResultHandler<DatagramSocket>() {
        public void handle(AsyncResult<DatagramSocket> asyncResult) {
            log.info("Send succeeded? " + asyncResult.succeeded());
        }  
    });
    // 文字列を送る
    socket.send("A string used as content", "10.0.0.1", 1234, new AsyncResultHandler<DatagramSocket>() {
        public void handle(AsyncResult<DatagramSocket> asyncResult) {
            log.info("Send succeeded? " + asyncResult.succeeded());
        }  
    });
    

    データグラムパケットを受信する


    パケットを受信したい場合は、DatagramSocketを、そのlisten(...)メソッドを呼び出してポートにバインドする必要があります。

    これで、DatagramSocketで待ち受けるアドレス・ポートに送られるDatagramPacketを受信することができます。

    また、DatagramPacketを受信するたびに呼び出されるHandlerをセットすることもできます。

    DatagramPacketは以下のメソッドを持ちます:

    • sender(): パケットの送信者を表わす InetSocketAddress を取得します。
    • data(): 受信データを格納したバッファを取得します。

    特定のアドレス・ポートで待ち受けるには、以下のようなコードになるでしょう:

    final DatagramSocket socket = vertx.createDatagramSocket(InternetProtocolFamily.IPV4);
    socket.listen("0.0.0.0", 1234, new AsyncResultHandler<DatagramSocket>() {
        public void handle(AsyncResult<DatagramSocket> asyncResult) {
            if (asyncResult.succeeded()) {
               socket.dataHandler(new Handler<DatagramPacket>() {
                    public void handle(DatagramPacket packet) {
                        // Do something with the packet
                    }
                });
            } else {
                log.warn("Listen failed", asyncResult.cause());
            }
        }  
    });
    

    たとえAsyncResultが成功、となっても、それはデータがネットワークスタックに書き込まれたことを意味するだけで、本当にリモートピアに届いたかどうかは全く保証されないことに注意してください。

    このような保証が必要であれば、最上位で TCP を使ったハンドシェイキングのロジックを組む必要があるでしょう。

    マルチキャスト


    マルチキャストパケットを送信する


    マルチキャストは複数のソケットに同じパケットを受信することを許します。これは、パケットを送信できる同じマルチキャストグループに参加することで動作します。

    次の章で、マルチキャストグループへの参加方法とパケットの受信方法について記述します。

    ここでは、まずは送信するかを見ておきましょう。マルチキャストパケットの送信は、通常のデータグラムパケットの送信と変わりません。

    ただ一つの違いは、send メソッドにマルチキャストグループアドレスを渡す必要があることです。

    例を見てみましょう:

    DatagramSocket socket = vertx.createDatagramSocket(InternetProtocolFamily.IPV4);
    Buffer buffer = new Buffer("content");
    // Send a Buffer to a multicast address
    socket.send(buffer, "230.0.0.1", 1234, new AsyncResultHandler<DatagramSocket>() {
        public void handle(AsyncResult<DatagramSocket> asyncResult) {
            log.info("Send succeeded? " + asyncResult.succeeded());
        }  
    });
    

    マルチキャストグループ 230.0.0.1 に属する全てのソケットがパケットを受信します。

    マルチキャストパケットを受信する


    特定のマルチキャストグループ宛てのパケットを受信したい場合は、DatagramSocketlisten(...)を呼び出してマルチキャストグループに所属させる必要があります。

    これで、DatagramSocketが待ち受けるアドレス・ポートに送られたパケットと、マルチキャストグループ宛てに送られたDatagramPacketsを受信することができます。

    DatagramPacketを受信するたびに呼び出されるハンドラをセットすることもできます。

    DatagramPacketは以下のメソッドをもちます:

    • sender(): パケットの送信者を表わす InetSocketAddress を取得します。
    • data(): 受信データを格納したバッファを取得します。

    特定のアドレス・ポートを待ち受け、さらに 230.0.0.1 マルチキャストグループ宛てのパケットも受信するには、以下のようなコードとなるでしょう:

    final DatagramSocket socket = vertx.createDatagramSocket(InternetProtocolFamily.IPV4);
    socket.listen("0.0.0.0", 1234, new AsyncResultHandler<DatagramSocket>() {
        public void handle(AsyncResult<DatagramSocket> asyncResult) {
            if (asyncResult.succeeded()) {
                socket.dataHandler(new Handler<DatagramPacket>() {
                    public void handle(DatagramPacket packet) {
                        // 受信パケットで何かをする
                    }
                });
    
                // マルチキャストグループに参加する
                socket.listenMulticastGroup("230.0.0.1", new AsyncResultHandler<DatagramSocket>() {
                    public void handle(AsyncResult<DatagramSocket> asyncResult) {
                        log.info("Listen succeeded? " + asyncResult.succeeded());
                    }
                });
            } else {
                log.warn("Listen failed", asyncResult.cause());
            }
        }  
    });
    

    マルチキャストグループの待ち受けを止める/グループから離脱する


    一時的に、マルチキャストグループのパケットを受信したい、という状況もあるかもしれません。

    このような状況のために、最初にマルチキャストグループのパケットを待ち受け、後に待ち受けるのをやめる、ということもできます。

    例を見てみましょう:

    final DatagramSocket socket = vertx.createDatagramSocket(InternetProtocolFamily.IPV4);
    socket.listen("0.0.0.0", 1234, new AsyncResultHandler<DatagramSocket>() {
        public void handle(AsyncResult<DatagramSocket> asyncResult) {
            if (asyncResult.succeeded()) {
                socket.dataHandler(new Handler<DatagramPacket>() {
                    public void handle(DatagramPacket packet) {
                        // 受信パケットで何かする
                    }
                });
    
                // join the multicast group
                socket.listenMulticastGroup("230.0.0.1", new AsyncResultHandler<DatagramSocket>() {
                    public void handle(AsyncResult<DatagramSocket> asyncResult) {
                        if (asyncResult.successed()) {
    
                            // マルチキャストグループのパケットを受信する
                            ...
                            ...
                            // 何か処理する
                            ..
                            ..
                            socket.unlisten("230.0.0.1", new AsyncResultHandler<DatagramSocket>() {
                                public void handle(AsyncResult<DatagramSocket> asyncResult) {
                                    log.info("Unlisten succeeded? " + asyncResult.succeeded());
                                }
                            });
    
                        } else {
                           log.warn("Listen failed", asyncResult.cause());
                        }
    
                    }
                });
            } else {
                log.warn("Listen failed", asyncResult.cause());
            }
        }  
    });
    

    マルチキャストをブロックする


    マルチキャストアドレスの待ち受け解除とともに、特定の送信アドレスのパケットをブロックする、ということもできます。

    これは、一部のオペレーティングシステム、一部のカーネルバージョンでしか動作しないことに注意してください。オペレーティングシステムでサポートされているかどうか確認願います。

    これはエキスパート向け機能です。

    特定のアドレスからのマルチキャストパケットをブロックするには、以下のように、DatagramSocket のblockMulticastGroup(...)メソッドを呼び出します:

    final DatagramSocket socket = vertx.createDatagramSocket(InternetProtocolFamily.IPV4);
    ...
    ...
    // これで 10.0.0.2 から送信されるパケットをブロックする
    socket.blockMulticastGroup("230.0.0.1", "10.0.0.2", new AsyncResultHandler<DatagramSocket>() {
        public void handle(AsyncResult<DatagramSocket> asyncResult) {
            log.info("block succeeded? " + asyncResult.succeeded());
        }
    });
    

    データグラムソケットのプロパティ


    DatagramSocketには複数のプロパティがあり、これをセットすることで動作を変更することができます。ここにリストします:

    • setSendBufferSize(size)送信バッファのサイズをバイト(byte)で指定します。

    • setReceiveBufferSize(size)受信バッファのサイズをバイト(byte)で指定します。

    • setReuseAddress(reuse)reuseが true であれば、TIME_WAIT 状態にあるアドレスはクローズ後に再利用されます。

    • setTrafficClass(trafficClass)

    • setBroadcast(broadcast)ソケットオプション SO_BROADCAST をセットまたはクリアします。セットされていた場合はデータグラム(UDP)パケットはローカルインタフェースのブロードキャストアドレスに送られます。

    • setMulticastLoopbackMode(loopbackModeDisabled)ソケットオプション IP_MULTICAST_LOOP をセットまたはクリアします。このオプションがセットされている場合、マルチキャストパケットはローカルインタフェースでも受信されます。

    • setMulticastTimeToLive(int ttl)IP_MULTICAST_TTL ソケットオプションをセットします。TTL は「生存期間(Time to Live)」ですが、ここでは、特にマルチキャスト通信でパケットが転送される IP ホップ数を指します。各ルータまたはゲートウェイがパケットを転送するときに TTL を減算します。ルータにて TTL が 0 まで減算されたら、そのパケットはそれ以上転送されません。

    データグラムソケットのローカルアドレス


    ソケットのローカルアドレス(すなわち、UDP ソケットのこちら側のアドレス)はlocalAddress()メソッドを呼び出せば得られます。このメソッドはDatagramSocketlisten(...)メソッドでアドレスにバインドされている場合はInetSocketAddressを返し、それ以外は null を返します。

    データグラムソケットを閉じる


    closeメソッドを実行することでデータグラムソケットを閉じることができます。これはソケットを閉じて全てのリソースを解放します。

    フロー制御(ストリームとポンプ)


    Vert.x には、バッファの形でデータを読み書きするためのオブジェクトが複数あります。

    Vert.x では、データ書き込みメソッドはすぐにリターンしますが、書き込みは内部でキューイングされます。

    データ書き込みメソッドの呼び出しが、データが実際に対象となるリソースに書き込まれるより前に行われ続けると、書き込みキューは無制限に拡張されていき、最後は利用可能なメモリが枯渇する結果になることを予見するのは難しくありません。

    この問題を解決するために、vert.x API のいくつかのオブジェクトにはシンプルなフロー制御の機能を備えています。

    org.vertx.java.core.streams.ReadStreamを実装したオブジェクトに対する書き込み、そしてorg.vertx.java.core.streams.WriteStreamを実装したオブジェクトからの読み出しはフロー制御されます。

    ReadStreamから読み出したい、及びWriteStreamに書き込みたい、という場合の例を示しましょう。

    とてもシンプルな例はサーバのNetSocketからデータを読み出し、同じNetSocketへ書き戻すものでしょう - なにせNetSocketReadStreamWriteStreamとの両方を実装していますので。とはいえ、HTTP リクエストとレスポンス、非同期ファイル、WebSocket などなど、任意のReadStreamと任意のWriteStreamの間のデータ読み書きでフロー制御は行われます。

    これを行う素朴な方法は、直接読み出したデータを、すぐさまソケットに書き戻すことです。例えば:

    NetServer server = vertx.createNetServer();
    
    server.connectHandler(new Handler<NetSocket>() {
        public void handle(final NetSocket sock) {
    
            sock.dataHandler(new Handler<Buffer>() {
                public void handle(Buffer buffer) {
                    // Write the data straight back
                    sock.write(buffer);
                }
            });
    
        }
    }).listen(1234, "localhost");
    

    この例には問題があります:もしソケットからのデータの読み出しがソケットへのデータの書き戻しより速く行われると、NetSocketの書き込みキューにデータが蓄積されていき、最終的には RAM を使い果します。例えばソケットの接続先クライアントがデータを読み出すのが遅いときなどにこれは起きる可能性があります。コネクションに「バックプレッシャ」を入れると効果的です。

    NetSocketWriteStreamを実装しているので、書き込みを行う前にWriteStreamがフルかどうかを確認できます:

    NetServer server = vertx.createNetServer();
    
    server.connectHandler(new Handler<NetSocket>() {
        public void handle(final NetSocket sock) {
    
            sock.dataHandler(new Handler<Buffer>() {
                public void handle(Buffer buffer) {
                    if (!sock.writeQueueFull()) {
                        sock.write(buffer);
                    }
                }
            });
    
        }
    }).listen(1234, "localhost");
    

    この例ならば RAM を使い切ることなく動作しますが、書き込みキューがフルだった場合データを失うでしょう。真に必要なのは書き込みキューがフルのときはNetSocketを一時停止することです。ではそうしてみましょう:

    NetServer server = vertx.createNetServer();
    
    server.connectHandler(new Handler<NetSocket>() {
        public void handle(final NetSocket sock) {
    
            sock.dataHandler(new Handler<Buffer>() {
                public void handle(Buffer buffer) {
                    if (!sock.writeQueueFull()) {
                        sock.write(buffer);
                    } else {
                        sock.pause();
                    }
                }
            });
    
        }
    }).listen(1234, "localhost");
    

    ここまで来ましたが、まだまだです。ファイルがフルのとき、NetSocketは一時停止するようになりましたが、書き込みキューが蓄積データを処理したら一時停止を解除する必要があります:

    NetServer server = vertx.createNetServer();
    
    server.connectHandler(new Handler<NetSocket>() {
        public void handle(final NetSocket sock) {
    
            sock.dataHandler(new Handler<Buffer>() {
                public void handle(Buffer buffer) {
                    if (!sock.writeQueueFull()) {
                        sock.write(buffer);
                    } else {
                        sock.pause();
                        sock.drainHandler(new VoidHandler() {
                            public void handle() {
                                sock.resume();
                            }
                        });
                    }
                }
            });
    
        }
    }).listen(1234, "localhost");
    

    これでよし、です。イベントハンドラdrainHandlerは、書き込みキューがデータを受け付ける準備ができたときに呼び出されます。ここでNetSocketをレジュームすると、NetSocketはデータの読み出しを再開していきます。

    Vert.x アプリケーションを書いていく上でこれはよくあることですので、Vert.x ではこの面倒な仕事を全て請け負うポンプ(Pump)と呼ばれるヘルパクラスを用意しています。ポンプにReadStreamWriteStreamを渡してスタートします:

    NetServer server = vertx.createNetServer();
    
    server.connectHandler(new Handler<NetSocket>() {
        public void handle(NetSocket sock) {        
            Pump.create(sock, sock).start();        
        }
    }).listen(1234, "localhost");
    

    これは、これまで見た冗長な例と全く同じことをします。

    それでは、ReadStreamWriteStreamのメソッドを、より詳しく見ていきましょう:

    ReadStream


    ReadStreamは、AsyncFileHttpClientResponseHttpServerRequestWebSocketNetSocketSockJSSocketで実装されています。

    ファンクションは以下の通りです:

    • dataHandler(handler): ReadStreamからデータを受信するハンドラをセットします。データが到着すると、ハンドラにはバッファが渡されます。
    • pause(): ハンドラを一時停止します。一時停止中はdataHandlerはデータを受信しません。
    • resume(): ハンドラを再開します。ハンドラはデータが到着すると呼ばれるようになります。
    • exceptionHandler(handler): ReadStreamで例外が発生したら呼び出されるハンドラをセットします。
    • endHandler(handler): ストリームが終端に到達したら呼び出されるハンドラをセットします。終端とは、ReadStreamがファイルの場合は、EOF に達したとき、HTTP リクエストの場合はリクエストの終端に達したとき、TCP ソケットであればソケットが閉じられたときです。

    WriteStream


    WriteStreamAsyncFileHttpClientRequestHttpServerResponseWebSocketNetSocketSockJSSocketで実装されています。

    ファンクションは以下の通りです:

    • write(buffer): WriteStreamにバッファを書き込みます。このメソッドはブロックされません。バッファは内部的にキューイングされ、対象となるリソースに非同期に書き込まれます。
    • setWriteQueueMaxSize(size): 書き込みバッファをフルとみなすバイト数を指定し、同時にwriteQueueFull()メソッドがtrueを返せるようにします。ただし、たとえキューがフルであっても、writeが呼び出されると、それは受け付けられ、データはキューイングされることに注意してください。
    • writeQueueFull(): 書き込みキューがフルとみなされたらtrueを返します。
    • exceptionHandler(handler): WriteStreamで例外が発生したら呼び出されるハンドラをセットします。
    • drainHandler(handler): WriteStreamがフルでなくなったら呼び出されるハンドラをセットします。

    ポンプ(Pump)


    Pumpのインスタンスは以下のメソッドをもちます:

    • start(): ポンプを開始します。
    • stop(): ポンプを停止します。開始されていたポンプは停止モードとなります。
    • setWriteQueueMaxSize(): これはWriteStreamにおけるsetWriteQueueMaxSizeと同じ意味となります。
    • getBytesPumped(): ポンプしたトータルバイト数を返します。

    ポンプは何度でも開始・停止できます。

    ポンプを生成しても、それは開始されません。開始するにはstart()メソッドを呼び出す必要があります。

    HTTP サーバとクライアントを書く


    HTTP サーバを書く


    Vert.x で、ハイパフォーマンスでスケーラブルなフル機能の HTTP サーバを簡単に書くことができます。

    HTTP サーバの生成


    HTTP サーバを生成するには、vertxインスタンス内でcreateHttpServerメソッドを呼びます。

    HttpServer server = vertx.createHttpServer();
    

    待ち受けを開始する


    サーバにリクエストの着信を待ち受けさせるには、listenメソッドを使用します:

    HttpServer server = vertx.createHttpServer();
    
    server.listen(8080, "myhost");
    

    第一引数は待ち受けポート番号です。

    第二引数はホスト名または IP アドレスです。省略された場合、デフォルトの0.0.0.0が使われます。これは使用可能な全てのインタフェースで待ち受けることを意味します。

    実際のアドレスバインドは非同期で行われ、listenメソッドから戻ってきたでも、実際にはまだ待ち受けが行われていないことがあり得ます。実際にサーバが待ち受けを開始したことを知りたければ、listenメソッドの呼び出しにハンドラを渡してください。以下がその例です:

    server.listen(8080, "myhost", new AsyncResultHandler<Void>() {
        public void handle(AsyncResult<HttpServer> asyncResult) {
            log.info("Listen succeeded? " + asyncResult.succeeded());
        }  
    });
    

    リクエストの着信を知る


    リクエストの受信を知るには、リクエストハンドラをセットする必要があります。これはサーバのrequestHandlerメソッドに、ハンドラを渡して呼び出すことで出来ます:

    HttpServer server = vertx.createHttpServer();
    
    server.requestHandler(new Handler<HttpServerRequest>() {
        public void handle(HttpServerRequest request) {
            log.info("A request has arrived on the server!");
            request.response().end();
        }
    });
    
    server.listen(8080, "localhost");
    

    サーバにリクエストが届く度に、ハンドラが呼び出され、org.vertx.groovy.core.http.HttpServerRequestインスタンスが渡されます。

    バーティクルの実行を開始した後、ブラウザでhttp://localhost:8080にアクセスすることで、これを試せます。

    ネットサーバ同様、requestHandlerメソッドの戻り値はサーバ自身なので、複数の実行文を続けて書くことが出来ます。つまり上のコードはこのように書けます:

    HttpServer server = vertx.createHttpServer();
    
    server.requestHandler(new Handler<HttpServerRequest>() {
        public void handle(HttpServerRequest request) {
            log.info("A request has arrived on the server!");
            request.response().end();
        }
    }).listen(8080, "localhost");
    

    または、

    vertx.createHttpServer().requestHandler(new Handler<HttpServerRequest>() {
        public void handle(HttpServerRequest request) {
            log.info("A request has arrived on the server!");
            request.response().end();
        }
    }).listen(8080, "localhost");
    

    Handling HTTP Requests


    HTTP リクエストを扱う


    これまで、HttpServerの生成方法、リクエストを知る方法を見てきました。それではどうやってリクエストを扱うかを見て、何か使えることをしてみましょう。

    リクエストが到着すると、HttpServerRequestのインスタンスが渡されてリクエストハンドラが呼び出されます。このオブジェクトはサーバにおける HTTP リクエストを表します。

    ハンドラはリクエストヘッダが完全に読み出されたときに呼び出されます。もしリクエストがボディを含んでいる場合、ボディはリクエストハンドラが呼び出されてから少し経った後に到着するかもしれません。

    オブジェクトは URI、パス、リクエストヘッダ、リクエストパラメータを取得するメソッドを含みます。さらにサーバにおける HTTP レスポンスを表現するオブジェクトへのリファレンスを返すresponse()メソッドも含みます。

    リクエストメソッド


    リクエストオブジェクトは、どの HTTP メソッドがリクエストされたかを文字列で返すmethod()メソッドをもっています。method()メソッドが返し得る値は以下の通りです:GET, PUT, POST, DELETE, HEAD, OPTIONS, CONNECT, TRACE, PATCH

    リクエストバージョン


    リクエストオブジェクトは HTTP バージョンを enum で返すversion()メソッドをもっています。

    リクエスト URI


    リクエストオブジェクトには、このリクエストのフル URI (Uniform Resource Identifier) を返すuri()メソッドがあります。リクエスト URI が以下の場合:

    /a/b/c/page.html?param1=abc&param2=xyz
    

    リクエスト URI はクライアントがどう送ってくるかに依存して、相対パスであったり絶対パス(ドメインを含んでいる)であったりします。多くの場合は相対パスでしょう。

    リクエスト URI に含まれる値はRFC2616のSection 5.1.2 of the HTTP specification - Request-URIに定義されています。

    リクエスト パス


    リクエストオブジェクトには、リクエストのパスを返すpath()メソッドがあります。例えば、リクエスト URI が以下の場合:

    a/b/c/page.html?param1=abc&param2=xyz
    

    request.path()/a/b/c/page.htmlという文字列を返します。

    リクエスト Query


    リクエストオブジェクトには、リクエストのクエリを返すquery()メソッドがあります。例えば、リクエスト URI が以下の場合:

    a/b/c/page.html?param1=abc&param2=xyz
    

    request.query()param1=abc&param2=xyzという文字列を返します。

    リクエスト ヘッダ


    リクエストヘッダはリクエストオブジェクトのheaders()メソッドによって取得できます。

    メソッドで返されるオブジェクトはorg.vertx.java.core.MultiMapのインスタンスです。マルチマップは通常のマップとは異なり、複数の値を同じキーに割り当てることを許したマップです。

    ここで、レスポンスとして受け取ったリクエストのヘッダをおうむ返しする例をお見せします。実行したら、ブラウザでhttp://localhost:8080にアクセスしてヘッダを見てください。

    HttpServer server = vertx.createHttpServer();
    
    server.requestHandler(new Handler<HttpServerRequest>() {
        public void handle(HttpServerRequest request) {
            StringBuilder sb = new StringBuilder();
            for (Map.Entry<String, String> header: request.headers().entries()) {
                sb.append(header.getKey()).append(": ").append(header.getValue()).append("\n");
            }
            request.response().putHeader("content-type", "text/plain");
            request.response().end(sb.toString());  
        }
    }).listen(8080, "localhost");
    

    リクエスト パラメータ


    ヘッダと同じように、リクエストパラメータはリクエストオブジェクトのparams()メソッドで取得できます。

    戻されるオブジェクトはorg.vertx.java.core.MultiMapのインスタンスです。

    リクエスト パラメータは送られたリクエスト URI でパスの後に続きます。以下の URI があるとすると:

    /page.html?param1=abc&param2=xyz
    

    パラメータマルチマップは以下のエントリをもつでしょう:

    param1: 'abc'
    param2: 'xyz
    

    リモート アドレス


    remoteAddress()メソッドを使って、HTTP コネクションの接続相手を知ることができます。

    絶対 URI


    absoluteURI()メソッドはリクエストに対応する絶対 URIを返します。

    リクエスト ボディからデータを読み出す


    HTTP リクエストには、読み出すべきボディが含まれることがあります。前に注意したとおり、リクエストハンドラはリクエストのヘッダを受信したときに呼び出されますので、HttpServerRequestオブジェクトはボディを含むことができません。ボディは非常に大きくなる可能性があるので、利用可能なメモリを越えてしまうことによる問題を起こさないようにしているのです。

    ボディを受けとるには、リクエストオブジェクトに対しdataHandlerをセットします。このハンドラはリクエストボディのチャンクを受信する度に毎回呼び出されます。例を見てみましょう:

    HttpServer server = vertx.createHttpServer();
    
    server.requestHandler(new Handler<HttpServerRequest>() {
        public void handle(HttpServerRequest request) {
            request.dataHandler(new Handler<Buffer>() {
                public void handle(Buffer buffer) {
                    log.info('I received ' + buffer.length() + ' bytes');
                }
            });
    
        }
    }).listen(8080, "localhost");
    

    dataHandlerデータハンドラはボディのサイズによっては複数回呼び出されるかもしれません。

    これはNetSocketからデータを読み出す方法に良く似ていることに注意してください。

    リクエストオブジェクトはReadStreamインタフェースの実装ですので、リクエストボディをWriteStreamにポンプすることができます。詳しくはフロー制御(ストリームとポンプ)の章を参考にしてください。

    多くのケースで、ボディはそれほど大きくなく、1回で受信したいことでしょう。だいたい以下のようなことをすればこれができます:

    HttpServer server = vertx.createHttpServer();
    
    server.requestHandler(new Handler<HttpServerRequest>() {
        public void handle(HttpServerRequest request) {
    
            final Buffer body = new Buffer(0);
    
            request.dataHandler(new Handler<Buffer>() {
                public void handle(Buffer buffer) {
                    body.appendBuffer(buffer);
                }
            });
            request.endHandler(new VoidHandler() {
                public void handle() {
                  // The entire body has now been received
                  log.info("The total body received was " + body.length() + " bytes");    
                }
            });
    
        }
    }).listen(8080, "localhost");
    

    他のReadStreamと同じようにエンドハンドラはストリームの終端に達したら呼び出されます。上の場合はリクエストの終端、となります。

    HTTP リクエストが HTTP チャンキングを使用している場合、リクエストボディの各 HTTP チャンクが、1回のデータハンドラの呼び出しに対応します。

    ボディデータを処理する前に、ボディデータを全て読み出しておきたい、というのは非常に一般的なユースケースですので、vert.x はリクエストオブジェクトにbodyHandlerをセットできるようにしています。

    ボディハンドラはリクエストボディをすべて読み出したときに1回だけ呼び出されます。

    読み出されたリクエストボディはメモリに格納されますので、巨大なリクエストに対してこれを実行するには注意して下さい。

    bodyHandlerの使用例です:

    HttpServer server = vertx.createHttpServer();
    
    server.requestHandler(new Handler<HttpServerRequest>() {
        public void handle(HttpServerRequest request) {        
            request.bodyHandler(new Handler<Buffer>() {
                public void handle(Buffer body) {
                  // The entire body has now been received
                  log.info("The total body received was " + body.length() + " bytes");   
                }
            });            
        }
    }).listen(8080, "localhost");
    

    マルチパートのアップロードフォームを扱う


    Vert.x はブラウザからの HTML フォームによるファイルアップロードを理解できます。ファイルアップロードを扱うためには、リクエストのuploadHandlerをセットする必要があります。ハンドラは、フォームの各アップロードについて1回呼び出されます。

    request.expectMultiPart(true);
    
    request.uploadHandler(new Handler<HttpServerFileUpload>() {
        public void handle(HttpServerFileUpload upload) {
        }
    });
    

    HttpServerFileUploadクラスはReadStreamを実装しているので、ポンプを使ってデータやストリームをWriteStreamを実装したオブジェクトに読み出すことができます。

    また、streamToFileSystem()メソッドを使って、ストリームを直接ファイルに落すこともできます。

    request.expectMultiPart(true);
    
    request.uploadHandler(new Handler<HttpServerFileUpload>() {
        public void handle(HttpServerFileUpload upload) {
            upload.streamToFileSystem("uploads/" + upload.filename());
        }
    });
    

    マルチパートのフォームアトリビュートを扱う


    リクエストが、サブミットされた HTML フォームであれば、フォームのアトリビュートを得るためにformAttributesプロパティが使えます。これは全てのリクエストが読み出された後にだけ呼び出されます。というのは、フォームアトリビュートはリクエストヘッダではなく、リクエストボディの中にエンコードされているからです。

    request.endHandler(new VoidHandler() {
        public void handle() {
            // The request has been all ready so now we can look at the form attributes
            MultiMap attrs = request.formAttributes();
            // Do something with them
        }
    });
    

    HTTP サーバレスポンス


    以前お話した通り、HTTP リクエストオブジェクトはresponseプロパティをもっています。これはリクエストに対する HTTP レスポンスを返します。これにクライアントに返すレスポンスを書いて送り返すことができます。

    ステータスコードとメッセージをセットする


    レスポンスの HTTP ステータスコードをセットするには、statusCode()メソッドを使います。

    HttpServer server = vertx.createHttpServer();
    
    server.requestHandler(new Handler<HttpServerRequest>() {
        public void handle(HttpServerRequest request) {        
            request.response().setStatusCode(739).setStatusMessage("Too many gerbils").end();       
        }
    }).listen(8080, "localhost");
    

    さらにステータスメッセージをセットするためにstatusMessage()メソッドが使えます。ステータスメッセージをセットしないと、デフォルトのメッセージが使われます。

    statusCodeのデフォルト値は200です。

    HTTP レスポンスを書き込む


    HTTPレスポンスにデータを書き込むには、writeメソッドを実行するか、左シフト(<<)演算子を使います(どちらでも同じです)。このメソッドはレスポンスが終わるまで何度も実行することができます。実行法はいくつかあります:

    単一のバッファなら:

    Buffer myBuffer = ...
    request.response().write(myBuffer);
    

    文字列。この例では文字列は UTF-8 でエンコードされ、ネットワークに送られます。

    request.response().write("hello");
    

    エンコードを指定した文字列。この例では文字列は指定されたコードでエンコーディグされネットワークに送られます。

    request.response().write("hello", "UTF-16");
    

    writeメソッドは非同期で、データをキューイングしたら直にリターンします。

    単一の文字列またはバッファを HTTP レスポンスに書き込んだのち、endメソッド呼び出すだけで、リクエストは書き込まれ終了します。

    writeメソッドの最初の呼び出しはレスポンスに書き込まれるヘッダとなります。

    ですので、HTTP チャンキングを使ってなければ、レスポンスを書く前にContent-Lengthヘッダをセットしなければなりません。そうしないと Length を書き込むタイミングが無くなります。HTTP チャンキングを使っていれば何も心配する必要はありません。

    HTTP レスポンスを終わらせる


    HTTP レスポンスを書き終えたら、そのレスポンスのend()メソッドを呼び出さねばなりません。

    このメソッドはいくつかの実行法があります:

    引数なしの場合、レスポンスは単に終了されます。

    request.response().end();
    

    writeメソッドのように、文字列またはバッファを渡して呼び出すこともできます。この場合、文字列またはバッファを指定して write を呼び出した後にendを引数無しで呼び出すのと同じになります。例を挙げると:

    request.response().end("That's all folks");
    

    コネクションを閉じる


    HTTP 接続の基になっている TCP コネクションを閉じるには、closeメソッドを呼びます。

    request.response().close();
    

    レスポンスヘッダ


    HTTP レスポンスヘッダは、headersマルチマップに加えることでレスポンスに書き足すことができます。

    request.response().headers().set("Cheese", "Stilton");
    request.response().headers().set("Hat colour", "Mauve");
    

    個々の HTTP レスポンスヘッダはputHeaderメソッドで書くこともできます。putHeaderの呼び出しをつなげることができるので、こんな風に流れるようにコードを書けます:

    request.response().putHeader("Some-Header", "elephants").putHeader("Pants", "Absent");
    

    レスポンスヘッダは、レスポンスボディのパーツが一つでも書かれる前に全て書き足しておかねばなりません。

    チャンクされた HTTP レスポンスとトレーラ


    Vert.x はHTTP Chunked Transfer Encodingをサポートしています。これは一般に、前もってサイズを知ることができない大きなレスポンスボディがクライアントに対して送られるときに使われる手法で、 HTTP レスポンスボディをチャンクに分けて書くことを許します。

    以下のようにすれば HTTP レスポンスをチャンクドモードで送れます:

    req.response().setChunked(true);
    

    デフォルトはチャンクしません。チャンクドモードでは、response.write(...)の各呼び出しは新しい HTTP チャンクの書き出しを行います。

    チャンクドモードでは、 HTTP レスポンストレーラをレスポンスに書き込むことができます。トレーラは実際にはレスポンスの最終チャンクに書き込まれます。

    トレーラをレスポンスに加えるには、trailersマルチマップに書き足します。:

    request.response().trailers().add("Philosophy", "Solipsism");
    request.response().trailers().add("Favourite-Shakin-Stevens-Song", "Behind the Green Door");
    

    ヘッダ同様、個々の HTTP レスポンストレーラはputTrailer()メソッドを使っても書き込めます。putTrailerもつなげてフルエントなコードを書けます:

    request.response().putTrailer("Cat-Food", "Whiskas").putTrailer("Eye-Wear", "Monocle");
    

    ディスクにあるファイルを直接送り出す


    Web サーバを書くうえで、ディスクにあるファイルを送り出すための方法として、AsyncFileメソッドを使ってファイルをオープンし、HTTP レスポンスにポンプする方法があります。または、ファイルシステム API を使用してファイルをロードし、HTTP レスポンスに書き込む方法もあります。

    また、これらの代わりに Vert.x は1操作でファイルをディスクから読み出し HTTP レスポンスとして送信するメソッドを用意しています。これは Vert.x が動作するオペレーティングシステムによりサポートされており、OS内部でファイルからバイト列を読み出しユーザランドにデータをコピーせず直接ソケットに書き込みます。

    sendFileメソッドは大きなファイルに対してより効果的ですが、手動でreadFileを使用してバッファとしてファイルを読み込みレスポンスに書き込むより遅いかもしれません。

    これを行うには、HTTP レスポンスのsendFileメソッドを使います。単純な HTTP Web サーバが、ローカルのwebディレクトリにあるスタティックなファイルを送り出す例を挙げます:

    HttpServer server = vertx.createHttpServer();
    
    server.requestHandler(new Handler<HttpServerRequest>() {
        public void handle(HttpServerRequest req) {        
          String file = "";
          if (req.path().equals("/")) {
            file = "index.html";
          } else if (!req.path().contains("..")) {
            file = req.path();
          }
          req.response().sendFile("web/" + file);              
        }
    }).listen(8080, "localhost");
    

    sendFileは、指定されたファイルが見つからなかった場合に代わりに送るファイルを指定することもできます:

    req.response().sendFile("web/" + file, "handler_404.html");
    

    注意: HTTPS を使用しているときは、sendFileはユーザランドにファイルをコピーします。カーネルにファイルから内容を読み出してソケットに直接書き込ませてしまうと、データを暗号化する機会がありませんから。

    Vert.x を使って Web サーバを書く場合、ユーザは、サーバがファイルを提供するディレクトリ外にあるファイルにアクセスするようにパスを利用する(例えば、パスに/../を使うなど)ことができないことに注意してください。

    レスポンスをポンプする


    HTTP レスポンスはWriteStreamを実装しているので、AsyncFileNetSocketHttpServerRequestといったReadStreamからなら何でもデータをポンプしてこれます。

    では HTTP リクエストヘッダとボディを HTTP レスポンスにエコーする例を見てみましょう。ここでは、ポンプを使ってますので、HTTP リクエストのボディが一時に使えるメモリサイズより非常に大きかったとしても、正しく動作するでしょう:

    HttpServer server = vertx.createHttpServer();
    
    server.requestHandler(new Handler<HttpServerRequest>() {
        public void handle(final HttpServerRequest req) {        
          req.response().headers().set(req.headers());      
          Pump.createPump(req, req.response()).start();
          req.endHandler(new VoidHandler() {
            public void handle() {
                req.response().end();
            }
          });           
        }
    }).listen(8080, "localhost");
    

    HTTPコンプレッション


    Vert.x は HTTP コンプレッションをサポートします。つまり、レスポンス内のボディをクライアントに送り返す前に自動的に圧縮ことが可能です。クライアントが HTTP コンプレッションをサポートしていなければ、レスポンスはボディを圧縮せずに送り返されます。つまり HTTP コンプレッションをサポートしているクライアントもサポートしていないクライアントも同時に扱えます。

    これだけでコンプレッションは有効となります:

    HttpServer server = vertx.createHttpServer();
    server.setCompressionSupported(true);
    

    デフォルトは false です。

    HTTP コンプレッションはHttpServerにより、クライアントの送ってくる 'Accept-Encoding' ヘッダをチェックして可能にします。一般にはデフレートと gzip が使用され、どちらも Vert.x でサポートされています。一度でもこのようなヘッダをHttpServerが見つけたら、自動的にサポートされている圧縮方法でレスポンスのボディを圧縮しクライアントに送り返します。

    圧縮はネットワークトラフィックを減らしますが、CPUに負担をかけることに注意してください。

    HTTPクライアントを書く


    HTTPクライアントの生成


    HTTPクライアントを生成するには、vertxインスタンスでcreateHttpClientメソッドを呼び出します:

    HttpClient client = vertx.createHttpClient();
    

    setHost()及びsetPort()メソッドを使って、ポート及びホスト名(またはIPアドレス)を指定することもできます:

    HttpClient client = vertx.createHttpClient();
    client.setPort(8181);
    client.setHost("foo.com");
    

    これはもちろんつなげて書けます:

    HttpClient client = vertx.createHttpClient()
        .setPort(8181)
        .setHost("foo.com");
    

    単一のHttpClientは常に同じホストの同じポートに接続します。違うサーバに接続したいときは、インスタンスを増やしてください。

    デフォルトポートは80で、デフォルトのホストはlocalhostですので、localhostのデフォルトポートに接続するのであれば、ホスト・ポートを明示的に指定する必要はありません。

    ポーリングとキープアライブ


    デフォルトではHttpClientは HTTP コネクションをプールします。接続リクエストはプールから借り、HTTP レスポンスの終了とともに返されます。

    コネクションをプールしたくない場合は、setKeepAlive()メソッドを引数をfalseにして呼び出します:

    HttpClient client = vertx.createHttpClient()
                   .setPort(8181)
                   .setHost("foo.com").
                   .setKeepAlive(false);
    

    こうすると、HTTP リクエストの度に新しいコネクションが生成され、レスポンスの終了とともにクローズされます。

    以下のように、クライアントがプールする最大コネクション数を指定することも可能です:

    HttpClient client = vertx.createHttpClient()
                   .setPort(8181)
                   .setHost("foo.com").
                   .setMaxPoolSize(10);
    

    最大コネクション数のデフォルトは1です。

    クライアントを閉じる


    バーティクル内に生成された HTTP クライアントはどれも、バーティクルが停止するときにクローズされますが、明示的にクローズすることもできます:

    client.close();
    

    リクエストを作る


    リクエストを作るには、クライアントの実行したい HTTP メソッド名と同じ名前のメソッドを実行します。

    例えば、POST リクエストを作るには:

    HttpClient client = vertx.createHttpClient().setHost("foo.com");
    
    HttpClientRequest request = client.post("/some-path/", new Handler<HttpClientResponse>() {
        public void handle(HttpClientResponse resp) {
            log.info("Got a response: " + resp.statusCode());
        }
    });
    
    request.end();
    

    PUT リクエストを作るにはputメソッド、GET リクエストを作るにはgetメソッド、という感じです。

    メソッドは:get, put, post, delete, head, options, connect, trace,そしてpatchとなります。

    一般的なやりかたは、リクエスト URI を最初の引数として渡して適切なメソッドを実行することです。第二引数は対応するレスポンスが到着したときに呼ばれるイベントハンドラです。レスポンスハンドラはクライアントレスポンスオブジェクトを一つ、引数に取ります。

    リクエスト URI に指定される値は、Request-URIとして5.1.2 Request-URIで定義されています。ほとんどのケースでは相対 URI でしょう。

    クライアントが接続するドメイン/ポートはsetPortsetHostで決められ、uri から読み取られるものではないことに注意してください。

    適切なリクエストメソッドからの戻り値はorg.vertx.java.core.http.HTTPClientRequestのインスタンスです。これに対し、ヘッダを追加したりリクエストボディを書き込んだりすることができます。リクエストオブジェクトはWriteStreamを実装しています。

    リクエストを終了したら、endメソッドを呼び出さないといけません。

    前もって実行したいメソッドが解っていないときは、引数に HTTP メソッドをとる汎用のrequestメソッドがあります:

    HttpClient client = vertx.createHttpClient().setHost("foo.com");
    
    HttpClientRequest request = client.request("POST", "/some-path/",
        new Handler<HttpClientResponse>() {
            public void handle(HttpClientResponse resp) {
                log.info("Got a response: " + resp.statusCode());
            }
        });
    
    request.end();
    

    さらにgetと同じですが、自動的にリクエストを終了するgetNowという名のメソッドもあります。これはリクエストボディを必要としない単純な GET メソッドに便利です:

    HttpClient client = vertx.createHttpClient().setHost("foo.com");
    
    client.getNow("/some-path/", new Handler<HttpClientResponse>() {
        public void handle(HttpClientResponse resp) {
            log.info("Got a response: " + resp.statusCode());
        }
    });
    

    例外を扱う


    HttpClientクラスに例外ハンドラをセットすると、クライアントが受けとる例外のうち、HttpClientRequestオブジェクトに指定された例外ハンドラ以外の全ての例外を受けとります。

    リクエストボディを書く


    クライアントのリクエストボディを書くのはサーバ側のレスポンスボディを書くのに非常に良く似た API を使用します。

    HttpClientRequestオブジェクトにデータを書くためには、writeメソッドを使用するか、左シフト演算子を使用します(どちらも同じことです)。このメソッドはリクエストが終わるまでなら何回でも呼び出せます。実行方法は何通りかあります:

    単一のバッファの場合は:

    Buffer myBuffer = ...
    request.write(myBuffer);
    

    文字列。この場合、文字列は UTF-8 でエンコードされネットワークに書き出されます。

    request.write("hello");
    

    エンコーディングを指定した文字列。この場合、文字列は指定されたエンコーディグでエンコードされネットワークに書き出されます。

    request.write("hello", "UTF-16");
    

    writeメソッドは非同期で、キューイングしたらすぐにリターンします。実際の書き込みは少し後になるでしょう。

    文字列またはバッファを HTTP リクエストに書き込んだら、endメソッドを呼び出すだけで、リクエストは書き込まれ終了します。

    最初のwriteメソッドの最初の呼び出しはリクエストに書き込まれるヘッダとなります。

    ですので、HTTP チャンキングを使っていなければ、リクエストを書く前にContent-Lengthヘッダをセットしなければなりません。そうしないと Length を書き込むタイミングが無くなります。HTTP チャンキングを使っていれば何も心配する必要はありません。

    Ending HTTP requests


    HTTP リクエストを終わらせる


    HTTP リクエストを書き終えたら、そのリクエストのendメソッドを呼び出さねばなりません。

    このメソッドはいくつかの実行法があります:

    引数なしの場合、リクエストは単に終了されます。

    request.end();
    

    writeメソッドのように、文字列またはバッファを渡して呼び出すこともできます。この場合、文字列またはバッファを指定して write を呼び出した後にendを引数無しで呼び出すのと同じになります。

    リクエストヘッダを書く


    リクエストにヘッダを書き込むには、headers()メソッドで返されるマルチマップにそれらを加えます:

    HttpClient client = vertx.createHttpClient().setHost("foo.com");
    
    HttpClientRequest request = client.post("/some-path/", new Handler<HttpClientResponse>() {
        public void handle(HttpClientResponse resp) {
            log.info("Got a response: " + resp.statusCode());
        }
    });
    
    request.headers().set("Some-Header", "Some-Value");
    request.end();
    

    putHeaderメソッドを使っても同じことができます。これは呼び出しをつなげてよりフルエントなコーディングができます。たとえば:

    request.putHeader("Some-Header", "Some-Value").putHeader("Some-Other", "Blah");
    

    これらは vert.x API の共通パターンである、互いにつなげる呼び出しかたで書けます:

    client.setHost("foo.com").post("/some-path/", new Handler<HttpClientResponse>() {
        public void handle(HttpClientResponse resp) {
            log.info("Got a response: " + resp.statusCode());
        }
    }).putHeader("Some-Header", "Some-Value").end();
    

    リクエストタイムアウト


    setTimeout()メソッドを使って、特定の HTTP リクエストにタイムアウトを指定することもできます。もしリクエストが指定された時間以内に全くデータを返さなかった場合、例外ハンドラに例外が投げられ(もしハンドラが登録されていれば)、リクエストは閉じられます。

    HTTP チャンクを使ったリクエスト


    Vert.x はリクエストについてもHTTP Chunked Transfer Encodingをサポートしています。これは一般に、前もってサイズを知ることができない大きなリクエストボディをサーバに送るときに使われる手法で、HTTP リクエストボディをチャンクに分けて書くことを許します。

    以下のようにすれば HTTP リクエストをチャンクドモードで送れます:

    request.setChunked(true);
    

    デフォルトはチャンクしません。チャンクドモードでは、request.write(...)の各呼び出しは新しい HTTP チャンクの書き出しを行います。

    HTTPクライアントレスポンス


    クライアントレスポンスは HTTP クライアントのメソッドの一つから渡される、レスポンスハンドラの引数として受け取られます。

    レスポンスオブジェクトはReadStreamを実装していますので、他のReadStreamと同じようにWriteStreamに対してポンプできます。

    レスポンスのステータスコードを知るためには、statusCode()メソッドを使ってください。また、statusMessage()メソッドでステータスメッセージが得られます。例を挙げますと:

    HttpClient client = vertx.createHttpClient().setHost("foo.com");
    
    client.getNow("/some-path/", new Handler<HttpClientResponse>() {
        public void handle(HttpClientResponse resp) {
            log.info('server returned status code: ' + resp.statusCode());   
            log.info('server returned status message: ' + resp.statusMessage());   
        }
    });
    

    レスポンスボディからデータを読み出す


    HTTP クライアントボディの読み出しのための API は、HTTPサーバのリクエストボディを読み出す API に非常によく似ています。

    HTTP レスポンスは読み出すべきボディを含んでいることがあります。HTTP リクエスト同様、クライアントのレスポンスハンドラは全てのボディまで到着したときではなく、レスポンスヘッダが全て揃った時点で呼び出されます。

    レスポンスボディを受け取るには、到着した HTTP レスポンスの一部であるレスポンスオブジェクトにdataHandlerをセットします。例を見てみましょう:

    HttpClient client = vertx.createHttpClient().setHost("foo.com");
    
    client.getNow("/some-path/", new Handler<HttpClientResponse>() {
        public void handle(HttpClientResponse resp) {
            resp.dataHandler(new Handler<Buffer>() {
                public void handle(Buffer data) {
                    log.info('I received ' + buffer.length() + ' bytes');
                }
            });  
        }
    });
    

    レスポンスオブジェクトはReadStreamインタフェースを実装しているので、レスポンスボディはWriteStreamにポンプできます。詳しくはストリームとポンプの章を見てください。

    dataHandlerは1度の HTTP レスポンスに対して複数回呼び出すことができます。

    サーバリクエストのときと同じく、何か処理をする前に全てのリクエストボディを読み出したいときは以下のようなことをします:

    HttpClient client = vertx.createHttpClient().setHost("foo.com");
    
    client.getNow("/some-path/", new Handler<HttpClientResponse>() {
        public void handle(HttpClientResponse resp) {
    
            final Buffer body = new Buffer(0);
    
            resp.dataHandler(new Handler<Buffer>() {
                public void handle(Buffer data) {
                    body.appendBuffer(data);
                }
            }); 
            resp.endHandler(new VoidHandler() {
                public void handle() {
                   // The entire response body has been received
                   log.info('The total body received was ' + body.length() + ' bytes');
                }
            }); 
        }
    });
    

    他のReadStreamと同じで、エンドハンドラはストリームが終端に達したとき呼ばれます。このケースではレスポンスの終わりで呼ばれます。

    HTTP レスポンスが HTTP チャンキングを使用している場合は、レスポンスボディの各チャンクが、1回のdataHandlerの呼び出しに対応します。

    ボディを一度に全部読み出しておきたい、というのはよくあるユースケースなので、Vert.x はbodyHandlerをレスポンスオブジェクトにセットする方法を用意しています。

    ボディハンドラはレスポンスボディが全て読み出されたときに一度だけ呼び出されます。

    読み出されたレスポンスボディはメモリに格納されますので、巨大なレスポンスに対してこれを実行するには注意して下さい。

    ではbodyHandlerの使用例を見てみましょう:

    HttpClient client = vertx.createHttpClient().setHost("foo.com");
    
    client.getNow("/some-path/", new Handler<HttpClientResponse>() {
        public void handle(HttpClientResponse resp) {       
            resp.bodyHandler(new Handler<Buffer>() {
                public void handle(Buffer body) {
                   // The entire response body has been received
                   log.info("The total body received was " + body.length() + " bytes");
                }
            }); 
        }
    });
    

    クッキーを読む


    cookiesプロパティを使うとレスポンスからクッキーのリストを読み出すことができます。

    100-Continueを扱う


    HTTP 1.1の仕様によれば、クライアントはヘッダにExpect: 100-Continueを含めることができ、リクエストボディの残りを送る前にリクエストヘッダを送ります。

    このとき、サーバはStatus: 100 (Continue)の暫定レスポンスステータスで応答することができ、クライアントにボディの残りを送ってもよいと指示します。

    これは、大量のデータをサーバに送る前に、サーバがリクエストを受け入れるか、拒否するか承認をもらおうという考えからきています。受け入れられないリクエストによる大量のデータ送信は帯域の浪費となり、また、サーバに、どうせ破棄するデータを読むことを強いてしまいます。

    Vert.x では、クライアントリクエストオブジェクトのcontinueHandlerプロパティをセットできます。これはサーバがStatus: 100 (Continue)を返してきたとき、OK として残りのリクエストを送ります。

    これは、リクエストヘッダを送るsendHeadメソッドとともに使用されます。

    この例がそれを示すでしょう:

    HttpClient client = vertx.createHttpClient().setHost("foo.com");
    
    final HttpClientRequest request = client.put("/some-path/", new Handler<HttpClientResponse>() {
        public void handle(HttpClientResponse resp) {       
            log.info("Got a response " + resp.statusCode());
        }
    });
    
    request.putHeader("Expect", "100-Continue");
    
    request.continueHandler(new VoidHandler() {
        public void handle() {
    
            // OK to send rest of body            
            request.write("Some data").end();
        }
    });
    
    request.sendHead();
    

    HTTP コンプレッション


    Vert.x は HTTP コンプレッションをサポートします。つまり HTTPClient はリモートの HTTP サーバにコンプレッションをサポートしていることを伝え、圧縮されたレスポンスボディを扱うことができます。HTTP サーバは、クライアントがサポートしているアルゴリズムでボディを圧縮して送り返しても良いですし、全く圧縮せずに送り返しても、どちらでも構いません。ですので、ここではサーバ a hint for the Http server which it may ignore at all.

    To tell the Http server which compression is supported by the HttpClient it will include a 'Accept-Encoding' header with the supported compression algorithm as value. Multiple compression algorithms are supported. In case of Vert.x this will result in have the following header added:

    Accept-Encoding: gzip, deflate
    

    HTTP サーバはこのうち一つの圧縮方法を選びます。クライアントは、送り返されたレスポンスから、HTTP サーバがボディを圧縮しているかどうかを 'Content-Encoding' ヘッダを確認して知ることができます。

    レスポンスボディが gzip で圧縮されている場合は、ヘッダにこのようなものが含まれるでしょう:

    Content-Encoding: gzip
    

    これだけでコンプレッションは有効となります:

    HttpClient client = vertx.createHttpClient();
    client.setTryUseCompression(true);
    

    デフォルトは false です。

    リクエストとレスポンスをポンプする


    HTTP クライアントとサーバ、リクエストとレスポンスはどれもReadStreamまたはWriteStreamです。つまり、これら、そして他のリードストリーム・ライトストリームをポンプでつなげることができるということです。

    HTTPS サーバ


    Vert.x を使えば、HTTPS サーバを書くのはとても簡単です。

    HTTPS サーバは標準の HTTP サーバと同じ API をもちます。サーバに HTTPS を使わせるにはlistenメソッドを呼び出す前に設定をすれば良いだけです。

    HTTPS サーバの設定はNetServerを SSL 対応にするのと全く同じやりかたで出来ます。詳しい方法についてはSSL サーバの章を見てください。

    HTTPS クライアント


    HTTPS クライアントもまた Vert.x で書くのはとても簡単です。

    HTTP クライアントを HTTPS に対応させるには、NetClientを SSL 対応にするのと全く同じやりかたで出来ます。詳しい方法についてはSSL クライアントの章を見てください。

    HTTP サーバのスケーリング


    HTTP または HTTPS サーバの複数コア上でのスケーリングは、単純に複数のバーティクルインスタンスをデプロイすることでできます。以下に例を示します:

    vertx runmod com.mycompany~my-mod~1.0 -instance 20
    

    または、「生の」バーティクルであれば:

    vertx run foo.MyServer -instances 20
    

    スケーリングはNetServerのそれと同様に動作します。詳しい説明は、TCP サーバのスケーリングを参照してください。

    パターンマッチングを使って HTTP リクエストをルーティングする


    Vert.x では、リクエストパスのパターンマッチングに基づいてリクエストを異なるハンドラに渡します。さらに、パスから値を抽出してそれらをリクエストのパラメータとして使うことも可能です。

    特に REST スタイルの web アプリケーションを開発するのにこれは役立つでしょう。

    org.vertx.java.core.http.RouteMatcherのインスタンスを作って、これを HTTP サーバのハンドラとして使うだけでこれらを実現できます。HTTP ハンドラのセットに関する詳しい情報は、HTTP サーバを書くの章を見てください。ここでは例を示します:

    HttpServer server = vertx.createHttpServer();
    
    RouteMatcher routeMatcher = new RouteMatcher();
    
    server.requestHandler(routeMatcher).listen(8080, "localhost");
    

    マッチを指定する


    ここで、違うマッチをルートマッチャに加えることもできます。例えば、パスが/animals/dogsである全ての GET リクエストを1つのハンドラに送り、パスが/animals/catsである全てのリクエストをもう1つのハンドラに送るには:

    HttpServer server = vertx.createHttpServer();
    
    RouteMatcher routeMatcher = new RouteMatcher();
    
    routeMatcher.get("/animals/dogs", new Handler<HttpServerRequest>() {
        public void handle(HttpServerRequest req) {
            req.response().end("You requested dogs");
        }
    });
    routeMatcher.get("/animals/cats", new Handler<HttpServerRequest>() {
        public void handle(HttpServerRequest req) {
            req.response().end("You requested cats");
        }
    });
    
    server.requestHandler(routeMatcher).listen(8080, "localhost");
    

    各 HTTP メソッドに、対応するメソッドがあります。- get, post, put, delete, head, options, trace, connect そして patchです。

    さらに、どんな HTTP リクエストにもマッチするallというメソッドもあります。

    メソッドに指定するハンドラは通常の HTTP サーバのリクエストハンドラですので、HTTP サーバでrequestHandlerメソッドを使うのと同じように使えます。

    マッチは好きなだけ用意することができ、加えた順に評価され、最初にマッチしたものがそのリクエストを受け取ります。

    リクエストはたかだか1つのハンドラに送られることになります。

    パスからパラメータを抽出する


    パスからパラメータを抽出したいときは、: (コロン)を使用してパラメータの名前を示します。例えば:

    HttpServer server = vertx.createHttpServer();
    
    RouteMatcher routeMatcher = new RouteMatcher();
    
    routeMatcher.put("/:blogname/:post", new Handler<HttpServerRequest>() {
        public void handle(HttpServerRequest req) {
            String blogName = req.params().get("blogname");
            String post = req.params().get("post");
            req.response().end("blogname is " + blogName + ", post is " + post);    
        }
    });
    
    server.requestHandler(routeMatcher).listen(8080, "localhost");
    

    パターンマッチングにより抽出された任意のパラメータは、リクエストパラメータのマップに追加されます。

    上記の例では、/myblog/post1 に対する PUT リクエストは変数blogNameに文字列値myblogが、変数postに文字列値post1が格納されることになります。

    パラメータ名は、アルファベットから始まり、2文字目以降はアルファベットまたは数字の任意の文字が続くものでなければなりません。

    正規表現を使ってパラメータを抽出する


    より複雑なマッチを抽出するために、正規表現を使うこともできます。この場合、グループ化された正規表現が任意のパラメータを取り込むために使われます。

    グループ化された正規表現には名前が着けられないので、param0, param1, param2というような名前で参照させます。

    メソッドは、各HTTP メソッドに対応して用意されています。getWithRegEx, postWithRegEx, putWithRegEx, deleteWithRegEx, headWithRegEx, optionsWithRegEx, traceWithRegEx, connectWithRegEx そして patchWithRegExです。

    ここでも、任意の HTTP リクエストメソッドにマッチするallWithRegExメソッドがあります。

    例を見てみましょう:

    HttpServer server = vertx.createHttpServer();
    
    RouteMatcher routeMatcher = new RouteMatcher();
    
    routeMatcher.allWithRegEx("\\/([^\\/]+)\\/([^\\/]+)", new Handler<HttpServerRequest>() {
        public void handle(HttpServerRequest req) {
            String first = req.params().get("param0");
            String second = req.params().get("param1");            
            req.response.end("first is " + first + " and second is " + second);   
        }
    });
    
    server.requestHandler(routeMatcher).listen(8080, "localhost");
    

    上のコードを実行し、ブラウザでhttp://localhost:8080/animals/catsにアクセスしてみてください。

    どれにもマッチしなかったリクエストを扱う


    どれにもマッチしなかったリクエストに対するハンドラを指定するために、noMatchメソッドを使うこともできます。もし noMatch ハンドラが指定されなかった場合、マッチしなかったリクエストには 404 が返されるでしょう。

    routeMatcher.noMatch(new Handler<HttpServerRequest>() {
        public void handle(HttpServerRequest req) {
            req.response().end("Nothing matched");'
        }
    });
    

    WebSocket


    WebSocketは HTTP サーバと HTTP クライアント(類型的にはブラウザ)との間でソケットに似た全二重のコネクションを張れる web テクノロジです。

    サーバ側のWebSocket


    WebSocket を使うためには、まず HTTP サーバを1つ、普通に、ただしrequestHandlerの代わりにwebsocketHandlerをセットして作ります。

    HttpServer server = vertx.createHttpServer();
    
    server.websocketHandler(new Handler<ServerWebSocket>() {
        public void handle(ServerWebSocket ws) {  
            // A WebSocket has connected!                        
        }
    }).listen(8080, "localhost");
    

    WebSocketへの読み書き


    ハンドラに渡されるwebsocketインスタンスはReadStreamWriteStreamとを両方実装してますので、普通のやりかた、つまりdataHandlerをセットして・writeメソッドを呼び出して、データを読書きできます

    詳しくはフロー制御(ストリームとポンプ)の章を参考にしてください。

    以下に、WebSocket で受信したデータを全てエコーする例を挙げます。:

    HttpServer server = vertx.createHttpServer();
    
    server.websocketHandler(new Handler<ServerWebSocket>() {
        public void handle(ServerWebSocket ws) {  
            Pump.createPump(ws, ws).start();     
        }
    }).listen(8080, "localhost");
    

    websocketインスタンスはバイナリデータを書き込むためのwriteBinaryFrameメソッドもまた用意しています。これはwriteメソッドを呼び出したのと同じ効果が得られます。

    さらに、テキストデータを書き込むためのwriteTextFrameメソッドというものもあります。これは、以下を呼び出すのと同等の意味となります。

    websocket.write(new Buffer("some-string"));
    

    WebSocketを拒否する


    ある特定のパスに接続してきた WebSocket だけ受け付けたいこともあるかもしれません。

    パスをチェックするには、websocketpathプロパティを確認します。そしてwebsocketを拒否するにはrejectメソッドを実行します。

    HttpServer server = vertx.createHttpServer();
    
    server.websocketHandler(new Handler<ServerWebSocket>() {
        public void handle(ServerWebSocket ws) {  
            if (ws.path().equals("/services/echo")) {
                Pump.createPump(ws, ws).start();                        
            } else {
                ws.reject();
            }
        }
    }).listen(8080, "localhost");
    

    Websocket のヘッダ


    headersプロパティを使うと、Websocket の基となったクライアントからのリクエストのヘッダ部分を受け取ることができます。

    クライアント側のWebSocket


    HTTP クライアントから WebSocket を使うには、まず普通に HTTP クライアントを作ります。そして接続したいサーバの URI とハンドラを渡してconnectWebsocketメソッドを呼びます。

    これで WebSocket に無事接続できたら、ハンドラが呼び出されます。接続できなかったら(たぶんサーバに拒否されたのでしょう)、HTTPクライアント上の例外ハンドラが呼び出されることになります。

    ここで WebSocket 接続の例を見て見ましょう。

    HttpClient client = vertx.createHttpClient().setHost("foo.com");
    
    client.connectWebsocket("/some-uri", new Handler<WebSocket>() {
        public void handle(WebSocket ws) {  
            // Connected!
        }
    });
    

    ホスト名(・そしてポート番号も)はHttpClientインスタンスにセットされ、接続で渡される URI は類型的には相対 URI であることに気をつけてください。

    くり返しになりますが、クライアント側もReadStreamWriteStreamとを実装していますので、その他のストリームオブジェクトと同様に読み書きすることができます。

    ブラウザでのWebSocket


    WebSockets を規格に準拠したブラウザから使うには、標準の WebSocket API を使います。WebSocket を使うクライアント側の JavaScript を例に挙げます:

    <script>
    
        var socket = new WebSocket("ws://foo.com/services/echo");
    
        socket.onmessage = function(event) {
            alert("Received data from websocket: " + event.data);
        }
    
        socket.onopen = function(event) {
            alert("Web Socket opened");
            socket.send("Hello World");
        };
    
        socket.onclose = function(event) {
            alert("Web Socket closed");
        };
    
    </script>
    

    より詳しい情報は、WebSocket API documentationを見てください。

    SockJS


    WebSocket は新しい技術で、未だこれをサポートしていない、または古いバージョンとか、プレファイナルのバージョンしかサポートしていないブラウザを使っているユーザもたくさんいます。

    さらに、WebSocket は多くの企業でプロキシを通りません。つまり WebSocket のコネクションが全てのユーザに確実につながるという保証ができないわけです。

    そこで、SockJSです。

    SockJS はクライアントサイドの JavaScript ライブラリとプロトコルで、実際にブラウザまたはネットワークが本当の WebSocket コネクションを許しているかどうかに関わらず、クライアント側の JavaScript 開発者に WebSocket ライクなシンプルなインタフェースを提供します。

    これは、ブラウザとサーバ間の様々な異なる搬送をサポートして、ブラウザとネットワークの機能によって、実行時にそのうちの一つを選択することで実現しています。これらは全て透過的で、開発者には単純に動作する WebSocket ライクなインタフェースが提示されます。

    より詳しくはSockJS ウェブサイトをご覧ください。

    SockJS サーバ


    Vert.x は完全なサーバサイドの SockJS の実装を提供します。

    つまり Vert.x はリッチなクライアント側の JavaScript アプリケーションとの間で、搬送の詳細について心配する必要なくデータをやりとりできる、モダンなリアルタイム web アプリケーションをつくることができます。(ここでいうリアルタイムは、モダンな意味でのものです。元来の、よりフォーマルなソフトリアルタイム、ハードリアルタイムシステムの定義と混同しないでください。

    SockJS サーバを生成するには通常の HTTP サーバを生成して、vertxインスタンスのcreateSockJSServerメソッドに渡すだけです:

    HttpServer httpServer = vertx.createHttpServer();
    
    SockJSServer sockJSServer = vertx.createSockJSServer(httpServer);
    

    各 SockJS サーバは複数のアプリケーションをホストできます。

    各アプリケーションはいくつかの設定で定義され、SockJS コネクションがサーバに届いたときに呼び出されるハンドラを提供します。

    例えば、 SockJS のエコーアプリケーションを作るには:

    HttpServer httpServer = vertx.createHttpServer();
    
    SockJSServer sockJSServer = vertx.createSockJSServer(httpServer);
    
    JsonObject config = new JsonObject().putString("prefix", "/echo");
    
    sockJSServer.installApp(config, new Handler<SockJSSocket>() {
        public void handle(SockJSSocket sock) {
            Pump.createPump(sock, sock).start();
        }
    });
    
    httpServer.listen(8080);
    

    設定は以下のフィールドをとるorg.vertx.java.core.json.JsonObjectのインスタンスです:

    • prefix: アプリケーションの URL プリフィクスです。このプリフィクスから始まるパスに対する HTTP リクエストはこのアプリケーションで処理されます。このプロパティは必須です。
    • insert_JSESSIONID: JSESSIONID クッキーセットをもつリクエストにだけスティッキーセッションを有効とするホスティングプロバイダがあります。このプロパティはサーバがこのクッキーにダミーの値をセットするかどうかを制御します。デフォルトでは JSESSIONID クッキーの設定は有効です。より洗練された動作は、ファンクションを提供することにより実現します。
    • session_timeout: サーバは、クライアントがコネクションを受け取ったことがしばらくの間わからないと、closeイベントを送ります。このときのディレイ時間をこのプロパティでセットします。デフォルトでは、5秒以内にクライアントからの応答がなければ、closeイベントを送ります。
    • heartbeat_period: ロードバランサやプロキシが長時間にわたる HTTP リクエストをクローズしないように、コネクションがアクティブであるふりをし、ときどきハートビート(鼓動)パケットを送る必要があります。このプロパティでハートビートパケットを送る間隔を指定します。デフォルトでは5秒ごとに送ります。
    • max_bytes_streaming: たいていのストリーミング搬送はクライアント側の応答を保存し、通信したメッセージに使ったメモリを解放しません。このような搬送ではときどきガベージコレクションを行う必要があります。max_bytes_streamingプロパティはクローズされる前に送る、1度の HTTP ストリーミングリクエストで送れるバイトの最小値を設定します。この後、クライアントは新規のリクエストでオープンする必要があります。この値をセットすることで、ストリーミング搬送を無効にし、ストリーミング搬送をポーリング搬送のように動作させます。デフォルトは 128K です。
    • library_url: ネイティブでクロスドメイン通信をサポートしない搬送('eventsource'はその一つです)はiフレームトリックを使います。シンプルなページがSockJS サーバから(外部ドメインを使用して)提供され、目に見えない iframe に配置されます。この iframe から実行されるコードは SockJS サーバのドメインローカルで実行されたかのように、クロスドメイン問題を心配する必要がありません。この iframe は SockJS Javascript クライアントライブラリを必要とするので、このプロパティでその URL を指定します。(よく分からない場合は、最新の縮小版 SockJS クライアントリリースを指定してください。これがデフォルトです。)。デフォルト値はhttp://cdn.sockjs.org/sockjs-0.3.4.min.jsです。

    SockJS サーバからデータを読み書きする


    SockJS ハンドラに渡されるSockJSSocketオブジェクトはNetSocketまたはWebSocketに良く似て、ReadStreamWriteStreamを実装しています。そのため、SockJS に対する読み書き、またはポンプするには、標準のAPIを使えます。

    より詳細については、フロー制御(ストリームとポンプ)の章を見てください。

    SockJS クライアント


    SockJS クライアントライブラリの使用に関する情報は、SockJS のウェブサイトを見てください。簡単な例では:

    <script>
       var sock = new SockJS('http://mydomain.com/my_prefix');
    
       sock.onopen = function() {
           console.log('open');
       };
    
       sock.onmessage = function(e) {
           console.log('message', e.data);
       };
    
       sock.onclose = function() {
           console.log('close');
       };
    </script>
    

    API が WebSocket API と良く似ていることが解ると思います。

    SockJS - イベントバス ブリッジ


    ブリッジをセットアップする


    SockJS と Vert.x のイベントバスをつなげると、サーバ側の複数の Vert.x インスタンスをつなぐだけでなく、ブラウザで動作するクライアント側の JavaScript も含む分散型のイベントバスが作れます。

    つまり多くのブラウザとサーバを囲んだ巨大な分散バスが作れるのです。サーバ同士が接続されていれば、ブラウザたちは同じサーバに接続されている必要もありません。

    サーバ側については、すでにイベントバス APIを見てきました。

    クライアント側については、vertxbus.jsと呼ばれるクライアントサイドの JavaScript ライブラリを提供しています。これはサーバ側と同じイベントバス API を、クライアント側に提供します。

    ライブラリは、SockJS ブリッジと呼ばれる Vert.x の SockJS サーバとのデータの送受信に、内部的に SockJS を使います。SockJS ソケットとサーバ側のイベントバスとの間をつなぐのはブリッジの責任です。

    SockJS ブリッジの作成方法は単純です。SockJS サーバのbridgeメソッドを呼ぶだけです。

    さらにブリッジをセキュアにする必要があります(後述)。

    以下の例はイベントバスとクライアント側の JavaScript をブリッジします:

    HttpServer server = vertx.createHttpServer();
    
    JsonObject config = new JsonObject().putString("prefix", "/eventbus");
    
    JsonArray noPermitted = new JsonArray();
    noPermitted.add(new JsonObject());
    
    vertx.createSockJSServer(server).bridge(config, noPermitted, noPermitted);
    
    server.listen(8080);
    

    全てのメッセージを通す場合は単なる空の JSON オブジェクト(全てのメッセージにマッチします)で JSON 配列を指定すれば良いです。

    十分注意してください!

    クライアント側の JavaScript からイベントバスを使う


    ブリッジをセットアップしたら、以下のようにしてクライアント側からイベントバスを使えます:

    Web ページでvertxbus.jsをロードする必要があります。そうすれば Vert.x のイベントバス API が使えます。以下は使用法をみるためのラフなアイデアです。完全に動作する例については、サンプルを見てください。

    <script src="http://cdn.sockjs.org/sockjs-0.3.4.min.js"></script>
    <script src='vertxbus.js'></script>
    
    <script>
    
        var eb = new vertx.EventBus('http://localhost:8080/eventbus');
    
        eb.onopen = function() {
    
          eb.registerHandler('some-address', function(message) {
    
            console.log('received a message: ' + JSON.stringify(message);
    
          });
    
          eb.send('some-address', {name: 'tim', age: 587});
    
        }
    
    </script>
    

    vertxbus.jsは Vert.x ディストリビューションのclientディレクトリにあります。

    例で行っている最初のことは、イベントバスのインスタンス生成です。

    var eb = new vertx.EventBus('http://localhost:8080/eventbus');
    

    コンストラクタのパラメータはイベントバスに接続するための URI です。ここでは、eventbusというプリフィクスを使ってブリッジを作ったので、そこに接続します。

    ブリッジは、オープンするまで実際には何もできません。オープンされると、onopenハンドラが呼ばれます。

    登録・登録解除ハンドラ、およびメッセージ送信については、クライアント側でもサーバ側イベントバス API と同じです。イベントバス APIの章を見てください。

    これを動かす前にもう一つやらねばならないことがあります。以下の章を読んでください....

    ブリッジをセキュアにする


    上記の例のようにセキュアにしないままブリッジを開始してメッセージを送信しても、不思議なことにメッセージは消えてしまいます。何が起きたのでしょう?

    ほとんどのアプリケーションで、クライアントサイドの JavaScript が任意のメッセージをサーバ側の任意のバーティクル、または他の全てのブラウザたちに送れる、というのはおそらく望まれないことだと思います。

    例えば、データのアクセスや削除を行うバーティクルがイベントバスでつながってるとします。故意であれ過失であれ、クライアントにデータベース内のデータを全消去されたくないですよね!また、必ずしも全てのクライアントが、全てのトピックを盗聴できるようにしたくもないです。

    これを扱うために、SockJS ブリッジは、デフォルトでは全てのメッセージ配信を拒否します。どのメッセージがブリッジに流れることを許可するか、メッセージブリッジに教えるのはあなたです(例外として、リプライメッセージは常に流れます)。

    言い換えるとブリッジはデフォルトポリシーがdeny-allのファイアウォールのように動作します。

    どのメッセージを通過させるかを設定するのは簡単です。マッチを表わす2つの JSON 配列をbridgeの引数に渡します。

    最初の配列はインバウンド(inbound)リストで、クライアントからサーバへの通信で許可するメッセージを表わします。2番目の配列はアウトバウンド(outbound)リストで、サーバからクライアントへの通信で許可するメッセージを表わします。

    どちらのマッチも3つのフィールドを持ちます:

    1. address: メッセージが送られようとする先の正確な送付先アドレスを表わします。アドレスによってメッセージのフィルタリングをする場合はこのフィールドを使います。
    2. address_re: アドレスにマッチさせる正規表現。正規表現でメッセージをフィルタリングする場合はこのフィールドを使います。addressフィールドが指定されていると、このフィールドは無視されます。
    3. match: メッセージをその構造でフィルタします。このマッチに登録された各フィールドが存在して、値も同一であるメッセージだけが通過を許されます。これは現状、JSON メッセージに対してのみ動作します。

    メッセージはブリッジに到着すると、使用可能な許可エントリを検索します。

    • addressフィールドが指定されていれば、メッセージのaddressと完全に同じならば、一致したとみなされます。

    • addressフィールドが指定されておらず、address_reフィールドが指定されていれば、メッセージのアドレスがaddress_reにある正規表現にマッチすれば、一致したとみなされます。

    • matchフィールドが指定されていれば、さらにメッセージの構造もマッチしなければなりません。

    例を挙げましょう:

    HttpServer server = vertx.createHttpServer();
    
    JsonObject config = new JsonObject().putString("prefix", "/echo");
    
    JsonArray inboundPermitted = new JsonArray();
    
    // 'demo.orderMgr' 宛ては全て通す
    JsonObject inboundPermitted1 = new JsonObject().putString("address", "demo.orderMgr");
    inboundPermitted.add(inboundPermitted1);
    
    // 'demo.persistor' 宛てのメッセージは、action フィールドに 'find'、
    // collection フィールドに 'albums' が入っていれば通す。
    JsonObject inboundPermitted2 = new JsonObject().putString("address", "demo.persistor")
        .putObject("match", new JsonObject().putString("action", "find")
                                            .putString("collection", "albums"));
    inboundPermitted.add(inboundPermitted2);
    
    // フィールド `wibble` の値が `foo` のものは全て通す
    JsonObject inboundPermitted3 = new JsonObject().putObject("match", new JsonObject().putString("wibble", "foo"));
    inboundPermitted.add(inboundPermitted3);
    
    JsonArray outboundPermitted = new JsonArray();
    
    // フィールド `wibble` の値が `foo` のものは全て通す
    JsonObject outboundPermitted1 = new JsonObject().putString("address", "ticker.mystock");
    outboundPermitted.add(outboundPermitted1);
    
    // アドレスが "news." から始まる(例えば、news.europe, news.usa, など)は全て通す
    JsonObject outboundPermitted2 = new JsonObject().putString("address_re", "news\\..+");
    outboundPermitted.add(outboundPermitted2);
    
    vertx.createSockJSBridge(server).bridge(config, inboundPermitted, outboundPermitted);
    
    server.listen(8080);
    

    承認が必要なメッセージ


    ブリッジは、ユーザが許可されていない場合に特定のメッセージの通過を拒否することもできます。

    これを有効にするには、vertx.auth-mgrモジュールのインスタンスがイベントバス上で利用可能になっている必要があります(モジュールに関する説明はモジュールマニュアルを見てください)。

    ブリッジに、通過前に認証が必要な特定のメッセージを教えるにはマッチに値をtrueとしたrequires_authフィールドを追加してください。デフォルトはfalseになってます。例えば、以下のマッチを見てみてください:

    {
      address : 'demo.persistor',
      match : {
        action : 'find',
        collection : 'albums'
      },
      requires_auth: true
    }
    

    上の例ではブリッジに、どのメッセージもordersコレクションに注文をセーブして、ユーザが認証成功(つまり、ログイン成功)したときだけ通過させるように通知します。

    ファイルシステム


    Vert.x でファイルシステム上のファイル操作も可能です。ファイルシステム操作は非同期で、最後の引数にハンドラメソッドを取ります。このメソッドは操作が完了したとき、またはエラーが起きたときに呼び出されます。

    ハンドラにはorg.vertx.groovy.core.AsyncResultのインスタンスが渡されます。

    同期式


    ほとんどの操作には、同期式のメソッドも用意されています。とはいえ実際のアプリケーションでは、常に非同期式のメソッドが使われることを強く推奨します。

    同期式メソッドはハンドラを引数としてとらず、操作結果を戻り値で直接返します。同期式メソッドの名前は非同期式メソッドと同じ名前にSyncを付加したものになります。

    copy


    ファイルをコピーします。

    このメソッドには二通りの呼びかたがあります:

    • copy(source, destination, handler)

    Non recursive file copy. source is the source file name. destination is the destination file name.

    Here's an example:

    vertx.fileSystem().copy("foo.dat", "bar.dat", new AsyncResultHandler<Void>() {
        public void handle(AsyncResult ar) {
            if (ar.succeeded()) {
                log.info("Copy was successful");
            } else {
                log.error("Failed to copy", ar.cause());
            }
        }
    });
    
    • copy(source, destination, recursive, handler)

    再帰コピーです。sourceはコピー元のファイル名です。destinationはコピー先のファイル名です。recursiveはブーリンアンのフラグで、これがtrueでコピー元がディレクトリの場合、再帰的にディレクトリの内容を全てコピーしようとします。

    move


    ファイルを移動します。

    move(source, destination, handler)

    sourceは移動するファイル名です。destinationは移動先のファイル名です。

    truncate


    ファイルを切り詰めます。

    truncate(file, len, handler)

    fileは切り詰めるファイル名です。lenで指定したバイト数に切り詰められます。

    chmod


    ファイル・ディレクトリのパーミションを変更します。

    このメソッドは二通りの呼び出しかたがあります:

    • chmod(file, perms, handler).

    ファイルのパーミションを変更します。

    fileはファイル名です。permsは9文字からなる Unix スタイルのパーミション文字列です。最初の3文字はオーナのパーミションです。二つ目の3文字はグループのパーミションで、三番目の3文字はその他のパーミションです。どのグループについても、3文字の1文字目がrであれば読み取り可能であることを表します。2文字目がwであれば、書き込み可能であることを表します。3文字目がxであれば、実行可能であることを表します。権限を持たないことを示すには、これらの文字を-で置き換えます。いくつかの例を見てみましょう:

    rwxr-xr-x
    r--r--r--
    
    • chmod(file, perms, dirPerms, handler).

    ディレクトリのパーミションを再帰的に変更します。fileはディレクトリ名です。permsは Unix スタイルのパーミションで、これがディレクトリ内の各ファイルに再帰的に適用されます。dirPermsも Unix スタイルのパーミション文字列で、指定ディレクトリ及び子ディレクトリに再帰的に適用されます。

    props


    ファイルのプロパティを取得します。

    props(file, handler)

    fileはファイル名です。プロパティは以下のメソッドをもつオブジェクトとしてハンドラに渡されます:

    • creationTime().ファイルの作成日時です。
    • lastAccessTime().ファイルの最終アクセス日時です。
    • lastModifiedTime().ファイルの最終更新時刻です。
    • isDirectory().これがtrueなら、file はディレクトリです。
    • isRegularFile().これがtrueなら、fileは通常のファイル(シンボリックリンクやディレクトリではありません)。
    • isSymbolicLink().これがtrueなら、fileはシンボリックリンクです。
    • isOther().これがtrueなら、fileはその他のタイプです。

    例を見てみましょう:

    vertx.fileSystem().props("foo.dat", "bar.dat", new AsyncResultHandler<FileProps>() {
        public void handle(AsyncResult<FileProps> ar) {
            if (ar.succeeded()) {
                log.info("File props are:");
                log.info("Last accessed: " + ar.result().lastAccessTime());
                // etc 
            } else {
                log.error("Failed to get props", ar.cause());
            }
        }
    });
    

    lprops


    リンクのプロパティを取得します。 propsに似ていますが、リンク先ではなく、シンボリックリンクそのもののプロパティを得たいときに使います。

    引数とその結果はpropsと同じです。


    ハードリンクを作成します。

    link(link, existing, handler)

    linkはリンクの名前です。existingは存在するファイル(つまり、リンクが指し示す先)です。


    シンボリックリンクを作成します。

    symlink(link, existing, handler)

    linkはシンボリックリンクの名前です。existingは存在するファイル(つまり、リンクが指し示す先)です。


    リンクを解除(削除)します。

    unlink(link, handler)

    linkは解除するリンクの名前です。


    シンボリックリンクを読み(つまり、linkで指定した、リンクが指し示すファイルを返し)ます。

    readSymLink(link, handler)

    link is the name of the link to read. An usage example would be:

    vertx.fileSystem().readSymLink("somelink", new AsyncResultHandler<String>() {
        public void handle(AsyncResult<String> ar) {
            if (ar.succeeded()) {                
                log.info("Link points at  " + ar.result());                
            } else {
                log.error("Failed to read", ar.cause());
            }
        }
    });
    

    delete


    ファイル、またはディレクトリを再帰的に削除します。

    このメソッドは2通りの呼び出しかたがあります:

    • delete(file, handler)

    ファイルを削除します。fileはファイルの名前です。

    • delete(file, recursive, handler)

    recursivetrueなら、fileで指定されたディレクトリを再帰的に削除します。さもなければ、単に指定されたファイルを削除します。

    mkdir


    ディレクトリを作成します。

    このメソッドは3通りの呼び方があります:

    • mkdir(dirname, handler)

    dirnameで指定された空のディレクトリを作成し、デフォルトのパーミションを与えます。

    • mkdir(dirname, createParents, handler)

    createParentstrueのとき、指定されたディレクトリを、その親ディレクトリとともに作成します。例を挙げてみましょう:

    vertx.fileSystem().mkdir("a/b/c", true, new AsyncResultHandler<Void>() {
        public void handle(AsyncResult ar) {
            if (ar.suceeded()) {                
                log.info("Directory created ok");                
            } else {
                log.error("Failed to mkdir", ar.cause());
            }
        }
    });
    
    • mkdir(dirname, createParents, perms, handler)

    mkdir(dirname, createParents, handler)に似てますが、指定されたパーミションを新しいディレクトリ(再帰的な場合は複数のディレクトリ)に付加します。permsは、前に説明した Unix スタイルのパーミション文字列です。

    readDir


    ディレクトリを読み出します。つまり、ディレクトリの内容をリストします。

    このメソッドは2通りの呼び出しかたがあります:

    • readDir(dirName)

    ディレクトリ内容をリストします。

    • readDir(dirName, filter)

    ディレクトリ内容のうち、filterで指定されたフィルタにマッチした内容をリストします。以下は、ディレクトリ内の、拡張子がtxtのファイルだけをリストする例です。

    vertx.fileSystem().readDir("mydirectory", ".*\\.txt", new AsyncResultHandler<String[]>() {
        public void handle(AsyncResult<String[]> ar) {
            if (ar.succeeded() {                
                log.info("Directory contains these .txt files");
                for (int i = 0; i < ar.result().length; i++) {
                  log.info(ar.result()[i]);  
                }               
            } else {
                log.error("Failed to read", ar.cause());
            }
        }
    });
    

    このフィルタは正規表現です。

    readFile


    ファイルの内容を全て読み出します。このメソッドを巨大なファイルに使うと、ファイル内容を全てメモリに読み出そうとしますので、注意してください。

    readFile(file). fileは読み出すファイル名です。

    ファイル内容はorg.vertx.java.core.buffer.Bufferのインスタンスとしてハンドラに渡されます。

    例を挙げます:

    vertx.fileSystem().readFile("myfile.dat", new AsyncResultHandler<Buffer>() {
        public void handle(AsyncResult<Buffer> ar) {
            if (ar.succeeded()) {                
                log.info("File contains: " + ar.result().length() + " bytes");              
            } else {
                log.error("Failed to read", ar.cause());
            }
        }
    });
    

    writeFile


    Buffer全体、もしくは文字列を新しいファイルに書き出します。

    writeFile(file, data, handler)fileはファイル名です。dataBufferまたは文字列です。

    createFile


    新しく空のファイルを作成します。

    createFile(file, handler). fileはファイル名です。

    exists


    ファイルが存在しているかどうかを確認します。

    exists(file, handler). fileはファイル名です。

    結果はハンドラに渡されます。

    vertx.fileSystem().exists("some-file.txt", new AsyncResultHandler<Boolean>() {
        public void handle(AsyncResult<Boolean> ar) {
            if (ar.succeeded()) {                
                log.info("File " + (ar.result() ? "exists" : "does not exist"));             
            } else {
                log.error("Failed to check existence", ar.cause());
            }
        }
    });
    

    fsProps


    ファイルシステムのプロパティを取得します。

    fsProps(file, handler). fileはファイルシステム上にある任意のファイルです。

    結果は以下のメソッドをもつorg.vertx.java.core.file.FileSystemPropsのインスタンスとしてハンドラに渡されます:

    • totalSpace(): ファイルシステムの全容量をバイトで表します。
    • unallocatedSpace(): ファイルシステムの空き容量をバイトで表します。
    • usableSpace(): 使用している容量をバイトで表します。

    例を見てみましょう:

    vertx.fileSystem().fsProps("mydir", new AsyncResultHandler<FileSystemProps>() {
        public void handle(AsyncResult<FileSystemProps> ar) {
            if (ar.succeeded()) {                
                log.info("total space: " + ar.result().totalSpace());
                // etc            
            } else {
                log.error("Failed to check existence", ar.cause());
            }
        }
    });
    

    open


    ファイルを非同期で読み書きするためにオープンします。

    このメソッドは4通りの呼び出しかたがあります:

    • open(file, handler)

    読み書きのためファイルをオープンします。fileはファイル名です。存在しないファイルであれば、作成します。

    • open(file, perms, handler)

    読み書きのためファイルをオープンします。fileはファイル名です。存在しないファイルであれば、permsで指定されたパーミションで作成します。

    • open(file, perms, createNew, handler)

    読み書きのためファイルをオープンします。fileはファイル名です。createNewtrueならば、ファイルが存在しない場合にファイルを作成します。

    • open(file, perms, read, write, createNew, handler)

    ファイルをオープンします。fileはファイル名です。もしreadtrueであれば、読み取りのためにオープンします。writetrueならば書き込みのためにオープンします。createNewtrueならば、存在しない場合にファイルを作成します。

    • open(file, perms, read, write, createNew, flush, handler)

    ファイルをオープンします。fileはファイル名です。もしreadtrueであれば、読み取りのためにオープンします。writetrueならば書き込みのためにオープンします。createNewtrueならば、存在しない場合にファイルを作成します。flushtrueであれば、全ての書き込みは即座にフラッシュされます。(デフォルトでは、flushはfalse)です。

    ファイルがオープンされると、org.vertx.java.core.file.AsyncFileのインスタンスがハンドラに渡されます:

    vertx.fileSystem().open("some-file.dat", new AsyncResultHandler<AsyncFile>() {
        public void handle(AsyncResult<AsyncFile> ar) {
            if (ar.succeeded()) {                
                log.info("File opened ok!");
                // etc            
            } else {
                log.error("Failed to open file", ar.cause());
            }
        }
    });
    

    AsyncFile


    openを呼び出すとorg.vertx.groovy.core.file.AsyncFileのインスタンスが返され、これを使ってファイルを非同期に読み書きすることができます。非同期なランダムアクセスが可能です。

    AsyncFileReadStreamWriteStreamを実装してますので、ソケット、HTTP リクエストとレスポンス、WebSocket といったストリームとファイルをポンプすることができます。

    非同期ファイルは直接ファイルに読み書きすることも可能です。

    ランダムな書き込みアクセス


    AsyncFileでランダムな書き込みアクセスをするには、writeメソッドを使います。

    write(buffer, position, handler).

    このメソッドのパラメータは:

    • buffer: 書き込むバッファです。
    • position: バッファを書き込むファイル内の位置を整数で示します。位置がファイルのサイズより大きいか、同じであれば、ファイルは適切な大きさに伸長されます。

    ランダムアクセスの例を挙げましょう:

    vertx.fileSystem().open("some-file.dat", new AsyncResultHandler<AsyncFile>() {
        public void handle(AsyncResult<AsyncFile> ar) {
            if (ar.succeeded()) {    
                AsyncFile asyncFile = ar.result();            
                // ファイルをオープンし、バッファを5回ファイル内に書き込む
                Buffer buff = new Buffer("foo");
                for (int i = 0; i < 5; i++) {
                    asyncFile.write(buff, buff.length() * i, new AsyncResultHandler<Void>() {
                        public void handle(AsyncResult ar) {
                            if (ar.succeeded()) {                
                                log.info("Written ok!");
                                // etc            
                            } else {
                                log.error("Failed to write", ar.cause());
                            }
                        }
                    });    
                }            
            } else {
                log.error("Failed to open file", ar.cause());
            }
        }
    });
    

    ランダムなリードアクセス


    AsyncFileでランダムなリードアクセスをするにはreadメソッドを使います。

    read(buffer, offset, position, length, handler)

    このメソッドのパラメータは以下の通りです:

    • buffer: ファイルの内容を読み出すバッファです。
    • offset: 読み出したデータをバッファのどこに書き込むかを示す整数のオフセットです。
    • position: データをファイルのどこから読むかを指定します。
    • length: 読み出すデータの長さ(バイト数)です。

    ランダムリードアクセスの例です:

    vertx.fileSystem().open("some-file.dat", new AsyncResultHandler<AsyncFile>() {
        public void handle(AsyncResult<AsyncFile> ar) {
            if (ar.succeeded()) {    
                AsyncFile asyncFile = ar.result();            
                Buffer buff = new Buffer(1000);
                for (int i = 0; i < 10; i++) {
                    asyncFile.read(buff, i * 100, i * 100, 100, new AsyncResultHandler<Buffer>() {
                        public void handle(AsyncResult<Buffer> ar) {
                            if (ar.succeeded()) {                
                                log.info("Read ok!");
                                // etc            
                            } else {
                                log.error("Failed to write", ar.cause());
                            }
                        }
                    });    
                }      
            } else {
                log.error("Failed to open file", ar.cause());
            }
        }
    });
    

    If you attempt to read past the end of file, the read will not fail but it will simply read zero bytes.

    ストレージにデータをフラッシュする


    AsyncFileflush = trueを指定せずにオープンされているときは、flushメソッドを呼び出して手動でデータをフラッシュしなくてはいけません。

    このメソッドは、フラッシュが完了したときに呼び出されるハンドラを指定することもできます。

    AsyncFileをReadStreamWriteStreamとして使用する


    AsyncFileReadStreamWriteStreamを実装しています。ですので、他のストリームへの書き込み、他のストリームからの読み出しにポンプを使うこともできます。

    ファイルからデータを読み出して HTTP リクエストにポンプする例を見てみましょう:

    final HttpClient client = vertx.createHttpClient.setHost("foo.com");
    
    vertx.fileSystem().open("some-file.dat", new AsyncResultHandler<AsyncFile>() {
        public void handle(AsyncResult<AsyncFile> ar) {
            if (ar.succeeded()) {    
                final HttpClientRequest request = client.put("/uploads", new Handler<HttpClientResponse>() {
                    public void handle(HttpClientResponse resp) {
                        log.info("Received response: " + resp.statusCode());
                    }
                });
                AsyncFile asyncFile = ar.result();
                request.setChunked(true);
                Pump.createPump(asyncFile, request).start();
                asyncFile.endHandler(new VoidHandler() {
                    public void handle() {
                        // ファイルは送信されたので HTTP リクエストを終了する。
                        request.end();
                    }
                });    
            } else {
                log.error("Failed to open file", ar.cause());
            }
        }
    });
    

    AsyncFileを閉じる


    AsyncFileを閉じるには、closeメソッドを呼び出します。クローズは非同期で行われるので、終わったことを知りたい場合は引数にハンドラを渡してください。

    DNSクライアント


    非同期に DNS 情報を引く必要にかられることがちょくちょくあるでしょう。残念ながらこれは JAVA の標準 API では実現できません。ですので、Vert.x では完全に非同期の DNS 名前解決 API を固有で用意しています。

    DNS クライアントを取得するには、Vert.x インスタンスでそれを新規に作成します。

    DnsClient client = vertx.createDnsClient(new InetSocketAddress("10.0.0.1", 53), new InetSocketAddress("10.0.0.2", 53));
    

    1つ以上の DNS サーバで DNS 名前解決を試行するために、可変個の InetSocketAddress 引数を指定することができることに注意してください。ここで指定した順番に従って DNS サーバに問合せを行います。最初の問い合わせでエラーが発生すると、次のサーバが使われます。

    lookup


    指定された名前の IPv4 Aレコード、または IPv6 AAAAレコードのルックアップを試みます。最初に返されたものが使われますので、これはオペレーティングシステムで "nslookup" を実行したのと同じような動作となります。

    例えば、"vertx.io" の A / AAAA レコードをルックアップするには、こんな感じとなるでしょう:

    DnsClient client = vertx.createDnsClient(new InetSocketAddress("10.0.0.1", 53));
    client.lookup("vertx.io", new AsyncResultHandler<InetAddress>() {
        public void handle(AsyncResult<InetAddress> ar) {
            if (ar.succeeded()) {
                System.out.println(ar.result());
            } else {
                log.error("Failed to resolve entry", ar.cause());
            }    
        }
    });
    

    結果として渡される AsyncResult が、Inet4Address なのか Inet6Address なのかは、解決の結果が A レコードか AAAA レコードかに依存することに注意してください。

    lookup4


    指定された名前の、IPv4 A レコードルックアップを試行します。最初に返ってきたものが使用されますので、オペレーティングシステムで "nslookup" を実行したのと同じ動作になります。

    "vertx.io"の Aレコードをルックアップするには、類型的にはこんな感じになるでしょう:

    DnsClient client = vertx.createDnsClient(new InetSocketAddress("10.0.0.1", 53));
    client.lookup4("vertx.io", new AsyncResultHandler<Inet4Address>() {
        public void handle(AsyncResult<Inet4Address> ar) {
            if (ar.succeeded()) {
                System.out.println(ar.result());
            } else {
                log.error("Failed to resolve entry", ar.cause());
            }    
        }
    });
    

    名前解決は Aレコードのみですので、結果は IPv4 のみで、返されるのは Inet4Address となります。

    lookup6


    指定された名前の、IPv6 AAAAレコードの取得を試みます。最初に返ってきたものが使用されますので、オペレーティングシステムで "nslookup" を実行したのと同じ動作になります。

    "vertx.io" の AAAA レコードを得るには、類型的にはこのような感じになるでしょう:

    DnsClient client = vertx.createDnsClient(new InetSocketAddress("10.0.0.1", 53));
    client.lookup6("vertx.io", new AsyncResultHandler<Inet6Address>() {
        public void handle(AsyncResult<Inet6Address> ar) {
            if (ar.succeeded()) {
                System.out.println(ar.result());
            } else {
                log.error("Failed to resolve entry", ar.cause());
            }    
        }
    });
    

    名前解決は IPv6 のみ、AAAA レコードのみとなりますので、結果は Inet6Address となります。

    resolveA


    指定された名前に対する、全ての IPv4 Aレコードの取得を試みます。これは、 UNIX ライクなオペレーティングシステムで "dig" を実行するのに似ています。

    "vertx.io" に対する全ての Aレコードを得るには、類型的にこんな感じになるでしょう:

    DnsClient client = vertx.createDnsClient(new InetSocketAddress("10.0.0.1", 53));
    client.resolveA("vertx.io", new AsyncResultHandler<List<Inet4Address>>() {
        public void handle(AsyncResult<List<Inet4Address>> ar) {
            if (ar.succeeded()) {
                List<Inet4Address> records = ar.result());
                for (Inet4Address record: records) {
                    System.out.println(record);
                }
            } else {
                log.error("Failed to resolve entry", ar.cause());
            }    
        }
    });
    

    名前解決は Aレコードのみですので、結果は IPv4 のみで、Inet4Address が使われます。

    resolveAAAA


    指定された名前に対する全ての IPv6 の AAAAレコードの取得を試みます。これは、 UNIX ライクなオペレーティングシステムで "dig" を実行するのに似ています。

    "vertx.io" に対する全ての IPv6 AAAAレコードを得るには、類型的にはこんな感じでしょう:

    DnsClient client = vertx.createDnsClient(new InetSocketAddress("10.0.0.1", 53));
    client.resolveAAAA("vertx.io", new AsyncResultHandler<List<Inet6Address>>() {
        public void handle(AsyncResult<List<Inet6Address>> ar) {
            if (ar.succeeded()) {
                List<Inet6Address> records = ar.result());
                for (Inet6Address record: records) {
                    System.out.println(record);
                }
            } else {
                log.error("Failed to resolve entry", ar.cause());
            }    
        }
    });
    

    結果は AAAAレコードのみですので、それは IPv6のみとなり、Inet6Address が使われます。

    resolveCNAME


    指定された名前に対する全ての CNAME レコードの取得を試みます。これは、 UNIX ライクなオペレーティングシステムで "dig" を実行するのに似ています。

    "vertx.io" に対する全ての CNAMEレコードをルックアップには、類型的にはこんな感じでしょう:

    DnsClient client = vertx.createDnsClient(new InetSocketAddress("10.0.0.1", 53));
    client.resolveCNAME("vertx.io", new AsyncResultHandler<List<String>>() {
        public void handle(AsyncResult<List<String>> ar) {
            if (ar.succeeded()) {
                List<String> records = ar.result());
                for (String record: records) {
                    System.out.println(record);
                }
            } else {
                log.error("Failed to resolve entry", ar.cause());
            }    
        }
    });
    

    resolveMX


    指定された名前に対する全ての MXレコードの取得を試みます。MXレコードは、指定されたドメインでeメールを受け取るメールサーバに対して定義されます。

    "vertx.io" に対する全ての MXレコードをルックアップするには、類型的にはこんな感じでしょう:

    DnsClient client = vertx.createDnsClient(new InetSocketAddress("10.0.0.1", 53));
    client.resolveMX("vertx.io", new AsyncResultHandler<List<MxRecord>>() {
        public void handle(AsyncResult<List<MxRecord>> ar) {
            if (ar.succeeded()) {
                List<MxRecord> records = ar.result());
                for (MxRecord record: records) {
                    System.out.println(record);
                }
            } else {
                log.error("Failed to resolve entry", ar.cause());
            }    
        }
    });
    

    MxRecordはリストで、優先度順でソートされており、優先度が低いものがリストの最初に来るので注意してください。

    MxRecordにあるプライオリティと名前をアクセスするための以下のようなメソッドが用意されています:

    MxRecord record = ...
    record.priority();
    record.name();
    

    resolveTXT


    指定された名前に対する全ての TXTレコードの取得を試みます。TXTレコードはドメインに対する追加情報を得るときによく使われます。

    "vertx.io" に対する全ての TXTレコードを得るには、このようなことをすればよいでしょう:

    DnsClient client = vertx.createDnsClient(new InetSocketAddress("10.0.0.1", 53));
    client.resolveTXT("vertx.io", new AsyncResultHandler<List<String>>() {
        public void handle(AsyncResult<List<String>> ar) {
            if (ar.succeeded()) {
                List<String> records = ar.result());
                for (String record: records) {
                    System.out.println(record);
                }
            } else {
                log.error("Failed to resolve entry", ar.cause());
            }    
        }
    });
    

    resolveNS


    指定された名前に対する全ての NSレコードの取得を試みます。NSレコードは指定されたドメインの DNS情報を提供する DNSサーバ 情報を特定するのに使われます。

    "vertx.io" に対する全ての NSレコードを得るには、以下のようなプログラムが使えるでしょう:

    DnsClient client = vertx.createDnsClient(new InetSocketAddress("10.0.0.1", 53));
    client.resolveNS("vertx.io", new AsyncResultHandler<List<String>>() {
        public void handle(AsyncResult<List<String>> ar) {
            if (ar.succeeded()) {
                List<String> records = ar.result());
                for (String record: records) {
                    System.out.println(record);
                }
            } else {
                log.error("Failed to resolve entry", ar.cause());
            }    
        }
    });
    

    resolveSRV


    指定された名前に対する全ての SRVレコードの取得を試みます。SRVレコードはサービスのポートとホスト名などの追加情報を定義するために使われます。この追加情報を必要とするプロトコルがいくつかあります。

    "vertx.io"についての全ての SRVレコードを得るには、類型的にこんなことをすれば良いでしょう:

    DnsClient client = vertx.createDnsClient(new InetSocketAddress("10.0.0.1", 53));
    client.resolveMX("vertx.io", new AsyncResultHandler<List<SrvRecord>>() {
        public void handle(AsyncResult<List<SrvRecord>> ar) {
            if (ar.succeeded()) {
                List<SrvRecord> records = ar.result());
                for (SrvRecord record: records) {
                    System.out.println(record);
                }
            } else {
                log.error("Failed to resolve entry", ar.cause());
            }    
        }
    });
    

    SrvRecordsを構成するリストは優先度順にソートされていて、優先度の小さい順に並んでいることに注意して下さい。

    SrvRecordは、SRVレコードに含まれている全ての情報にアクセスするために、以下のようなプロパティを用意しています:

    SrvRecord record = ...
    record.priority();
    record.name();
    record.priority();
    record.weight();
    record.port();
    record.protocol();
    record.service();
    record.target();
    

    詳しくは API ドキュメントを参照してください。

    resolvePTR


    指定された名前に対する PTRレコードの解決を試みます。PTRレコードは指定ドメインの IPアドレスのマップです。

    IPアドレス 10.0.0.1 に対する PTRレコード解決をするには、PTR記法で "1.0.0.10.in-addr.arpa" を使います:

    DnsClient client = vertx.createDnsClient(new InetSocketAddress("10.0.0.1", 53));
    client.resolveTXT("1.0.0.10.in-addr.arpa", new AsyncResultHandler<String>() {
        public void handle(AsyncResult<String> ar) {
            if (ar.succeeded()) {
                String record = ar.result());
                System.out.println(record);
            } else {
                log.error("Failed to resolve entry", ar.cause());
            }    
        }
    });
    

    reverseLookup


    IPアドレスに対する逆引きを試みます。これは基本的には PTRレコードの解決と同じですが、有効な PTR クエリ文字列ではなく、IPアドレスを指定することができます。

    IPアドレス 10.0.0.1 を逆引きするには、だいたい以下のようなことをします:

    DnsClient client = vertx.createDnsClient(new InetSocketAddress("10.0.0.1", 53));
    client.reverseLookup("10.0.0.1", new AsyncResultHandler<String>() {
        public void handle(AsyncResult<String> ar) {
            if (ar.succeeded()) {
                String record = ar.result());
                System.out.println(record);
            } else {
                log.error("Failed to resolve entry", ar.cause());
            }    
        }
    });
    

    エラーを扱う


    前章で見た通り、DnsClient は問合せが完了したときに呼び出される、AsyncResult を引数としたハンドラを登録できます。問い合わせの結果がエラーだった場合、解決が失敗した理由を示す DnsResponseCode を含んだ DnsException 例外で通知されます。この DnsResponseCode を使うとエラーの詳細を検査できます。

    渡される可能性のある DnsResponseCodes は:

    NOERROR


    指定された問い合わせに対するレコードが見つからなかった

    FORMERROR


    フォーマットエラー

    SERVFAIL


    サーバ障害

    NXDOMAIN


    名前エラー

    NOTIMPL


    DNSサーバに実装されていない

    REFUSED


    DNSサーバに問い合わせを拒否された

    YXDOMAIN


    ドメイン名が存在しない

    YXRRSET


    リソースレコードが存在しない

    NXRRSET


    RRSETが存在しない

    NOTZONE


    ゾーン内に名前が存在しない

    BADVER


    サーバのバージョンではサポートしていない拡張

    BADSIG


    不正なシグニチャ

    BADKEY


    不正なキー

    BADTIME


    不正なタイムスタンプ

    これらのエラーは全て DNSサーバ自身により「生成される」ます。

    DnsResponseCode は、以下のようにして DnsException から取り出せます:

    DnsClient client = vertx.createDnsClient(new InetSocketAddress("10.0.0.1", 53));
    client.lookup("nonexisting.vert.xio", new AsyncResultHandler<InetAddress>() {
        public void handle(AsyncResult<InetAddress> ar) {
            if (ar.succeeded()) {
                InetAddress record = ar.result());
                System.out.println(record);
            } else {
                Throwable cause = ar.cause();
                if (cause instanceof DnsException) {
                    DnsException exception = (DnsException) cause;
                    DnsResponseCode code = exception.code();
                    ...
                } else {
                    log.error("Failed to resolve entry", ar.cause());
                }
            }    
        }
    });