CLOVER🍀

That was when it all began.

Vert.x Core、Vert.x Webで遊ぶ

これは、なにをしたくて書いたもの?

そろそろ、Vert.xも少し知っておいた方がいいのかなーと思いまして。

試してみようかなと。

Vert.xとは

Vert.xは、イベント駆動、ノンブロッキング、複数の言語による開発が可能なフレームワークです。

Eclipse Vert.x

複数の言語とは、Java、Kotlin、JavaScript、Groovy、Ruby、Scalaを指します。

まだ若いフレームワークかというとそうでもなく、けっこう前から存在しています。

vert.x – Node.jsの代替フレームワーク

現在のバージョンは3.9系ですが、4が開発中です。

4ではJava、Kotlin、Groovyあたりがターゲットになるんでしょうかね?(コード例が減ってる)

Eclipse Vert.x

現在のバージョンのドキュメントは、こちらです。

Vert.x Documentation

とりあえず、始めてみましょうか。

環境

今回の環境は、こちらです。

$ java --version
openjdk 11.0.9.1 2020-11-04
OpenJDK Runtime Environment (build 11.0.9.1+1-Ubuntu-0ubuntu1.20.04)
OpenJDK 64-Bit Server VM (build 11.0.9.1+1-Ubuntu-0ubuntu1.20.04, mixed mode, sharing)


$ mvn --version
Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 11.0.9.1, vendor: Ubuntu, runtime: /usr/lib/jvm/java-11-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "5.4.0-53-generic", arch: "amd64", family: "unix"

始めてみる

Vert.xには、Starterと呼ばれる初期プロジェクトを作るWebサイトがあります。

Vert.x Starter - Create new Eclipse Vert.x applications

サンプルはこちらのリポジトリにあります。

https://github.com/vert-x3/vertx-examples/tree/master

Webアプリケーションのサンプルは、こちら。

https://github.com/vert-x3/vertx-examples/tree/master/web-examples

で、どうしましょうというところですが、今回はStarterからプロジェクトを作って始めてみたいと思います。

まずは、プロジェクト用のディレクトリを作成。

$ mkdir hello-web
$ cd hello-web

Starterでプロジェクトを.tar.gzファイルで作成して、ダウンロードします。

$ curl -s -G https://start.vertx.io/starter.tar.gz \
   -d "groupId=org.littlewings" \
   -d "artifactId=hello-web" \
   -d "packageName=org.littlewings.vertx.web" \
   -d "vertxVersion=3.9.4" \
   -d "vertxDependencies=vertx-web" \
   -d "language=java" \
   -d "jdkVersion=11" \
   -d "buildTool=maven" \
   -o - | \
  tar -zxvf -

Starterのサイトにはcurlの使用例も載っていて、こちらは.zipで書かれていますが、拡張子を変更することで.tar.gzに
することができます。

依存関係にはvertx-webを加え、Vert.xのバージョンは3.9.4で作成。

できあがったプロジェクトには、こんな感じのファイルが含まれています。

$ find -type f
./.editorconfig
./.mvn/wrapper/MavenWrapperDownloader.java
./.mvn/wrapper/maven-wrapper.jar
./.mvn/wrapper/maven-wrapper.properties
./pom.xml
./README.adoc
./.gitignore
./mvnw
./mvnw.cmd
./src/main/java/org/littlewings/vertx/web/MainVerticle.java
./src/test/java/org/littlewings/vertx/web/TestMainVerticle.java

少し、代表的なファイルを見てみましょう。

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>org.littlewings</groupId>
  <artifactId>hello-web</artifactId>
  <version>1.0.0-SNAPSHOT</version>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

    <maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
    <maven-shade-plugin.version>2.4.3</maven-shade-plugin.version>
    <maven-surefire-plugin.version>2.22.2</maven-surefire-plugin.version>
    <exec-maven-plugin.version>1.5.0</exec-maven-plugin.version>

    <vertx.version>3.9.4</vertx.version>
    <junit-jupiter.version>5.4.0</junit-jupiter.version>

    <main.verticle>org.littlewings.vertx.web.MainVerticle</main.verticle>
  </properties>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>io.vertx</groupId>
        <artifactId>vertx-stack-depchain</artifactId>
        <version>${vertx.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <dependencies>
    <dependency>
      <groupId>io.vertx</groupId>
      <artifactId>vertx-web</artifactId>
    </dependency>

    <dependency>
      <groupId>io.vertx</groupId>
      <artifactId>vertx-junit5</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-api</artifactId>
      <version>${junit-jupiter.version}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-engine</artifactId>
      <version>${junit-jupiter.version}</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>${maven-compiler-plugin.version}</version>
        <configuration>
          <release>11</release>
        </configuration>
      </plugin>
      <plugin>
        <artifactId>maven-shade-plugin</artifactId>
        <version>${maven-shade-plugin.version}</version>
        <executions>
          <execution>
            <phase>package</phase>
            <goals>
              <goal>shade</goal>
            </goals>
            <configuration>
              <transformers>
                <transformer
                  implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                  <manifestEntries>
                    <Main-Class>io.vertx.core.Launcher</Main-Class>
                    <Main-Verticle>${main.verticle}</Main-Verticle>
                  </manifestEntries>
                </transformer>
                <transformer
                  implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                  <resource>META-INF/services/io.vertx.core.spi.VerticleFactory</resource>
                </transformer>
              </transformers>
              <artifactSet>
              </artifactSet>
              <outputFile>${project.build.directory}/${project.artifactId}-${project.version}-fat.jar
              </outputFile>
            </configuration>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>${maven-surefire-plugin.version}</version>
      </plugin>
      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>exec-maven-plugin</artifactId>
        <version>${exec-maven-plugin.version}</version>
        <configuration>
          <mainClass>io.vertx.core.Launcher</mainClass>
          <arguments>
            <argument>run</argument>
            <argument>${main.verticle}</argument>
          </arguments>
        </configuration>
      </plugin>
    </plugins>
  </build>


</project>

Vert.xに関するバージョン全体はBOMで指定され

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>io.vertx</groupId>
        <artifactId>vertx-stack-depchain</artifactId>
        <version>${vertx.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

依存関係には、指定したvertx-webが含まれています。

    <dependency>
      <groupId>io.vertx</groupId>
      <artifactId>vertx-web</artifactId>
    </dependency>

Maven Shade Pluginで、単一のJARを作れるようにも構成されています。

      <plugin>
        <artifactId>maven-shade-plugin</artifactId>
        <version>${maven-shade-plugin.version}</version>
        <executions>
          <execution>
            <phase>package</phase>
            <goals>
              <goal>shade</goal>
            </goals>
            <configuration>
              <transformers>
                <transformer
                  implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                  <manifestEntries>
                    <Main-Class>io.vertx.core.Launcher</Main-Class>
                    <Main-Verticle>${main.verticle}</Main-Verticle>
                  </manifestEntries>
                </transformer>
                <transformer
                  implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                  <resource>META-INF/services/io.vertx.core.spi.VerticleFactory</resource>
                </transformer>
              </transformers>
              <artifactSet>
              </artifactSet>
              <outputFile>${project.build.directory}/${project.artifactId}-${project.version}-fat.jar
              </outputFile>
            </configuration>
          </execution>
        </executions>
      </plugin>

起動クラスはVert.xのものを使い

                <transformer
                  implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                  <manifestEntries>
                    <Main-Class>io.vertx.core.Launcher</Main-Class>
                    <Main-Verticle>${main.verticle}</Main-Verticle>
                  </manifestEntries>
                </transformer>

できあがるJARファイルは、-fat.jarという名前になります、と。

              <outputFile>${project.build.directory}/${project.artifactId}-${project.version}-fat.jar
              </outputFile>

src/main/javaに含まれていたクラスは、こちら。
src/main/java/org/littlewings/vertx/web/MainVerticle.java

package org.littlewings.vertx.web;

import io.vertx.core.AbstractVerticle;
import io.vertx.core.Promise;

public class MainVerticle extends AbstractVerticle {

  @Override
  public void start(Promise<Void> startPromise) throws Exception {
    vertx.createHttpServer().requestHandler(req -> {
      req.response()
        .putHeader("content-type", "text/plain")
        .end("Hello from Vert.x!");
    }).listen(8888, http -> {
      if (http.succeeded()) {
        startPromise.complete();
        System.out.println("HTTP server started on port 8888");
      } else {
        startPromise.fail(http.cause());
      }
    });
  }
}

Verticleってなんでしょう?

Vert.xのCoreのドキュメントに書かれていそうです。

Vert.x Core Manual - Vert.x

Vert.x Coreのドキュメントを読みつつ、作成したプロジェクトを動かしてみる

Vert.x Coreのドキュメントを、少し眺めてみます。

Vert.x Core Manual - Vert.x

Vert.x Coreは、次のような機能を提供します。

  • Writing TCP clients and servers
  • Writing HTTP clients and servers including support for WebSockets
  • The Event bus
  • Shared data - local maps and clustered distributed maps
  • Periodic and delayed actions
  • Deploying and undeploying Verticles
  • Datagram Sockets
  • DNS client
  • File system access
  • High availability
  • Native transports
  • Clustering

Vert.x Coreの機能はLow Levelなものとされていて、データベースアクセスや認証、High LevelのWeb機能などは含まれません。
そして、小さく軽量です、と。

Vert.x Coreは、今回作成したプロジェクトの場合、Vert.x Webからの推移的依存関係に含まれます。

mvn dependency:treeの結果を抜粋。

[INFO] +- io.vertx:vertx-web:jar:3.9.4:compile
[INFO] |  +- io.vertx:vertx-web-common:jar:3.9.4:compile
[INFO] |  +- io.vertx:vertx-auth-common:jar:3.9.4:compile
[INFO] |  +- io.vertx:vertx-bridge-common:jar:3.9.4:compile
[INFO] |  \- io.vertx:vertx-core:jar:3.9.4:compile
[INFO] |     +- io.netty:netty-common:jar:4.1.49.Final:compile
[INFO] |     +- io.netty:netty-buffer:jar:4.1.49.Final:compile
[INFO] |     +- io.netty:netty-transport:jar:4.1.49.Final:compile
[INFO] |     +- io.netty:netty-handler:jar:4.1.49.Final:compile
[INFO] |     |  \- io.netty:netty-codec:jar:4.1.49.Final:compile
[INFO] |     +- io.netty:netty-handler-proxy:jar:4.1.49.Final:compile
[INFO] |     |  \- io.netty:netty-codec-socks:jar:4.1.49.Final:compile
[INFO] |     +- io.netty:netty-codec-http:jar:4.1.49.Final:compile
[INFO] |     +- io.netty:netty-codec-http2:jar:4.1.49.Final:compile
[INFO] |     +- io.netty:netty-resolver:jar:4.1.49.Final:compile
[INFO] |     +- io.netty:netty-resolver-dns:jar:4.1.49.Final:compile
[INFO] |     |  \- io.netty:netty-codec-dns:jar:4.1.49.Final:compile
[INFO] |     +- com.fasterxml.jackson.core:jackson-core:jar:2.11.3:compile
[INFO] |     \- com.fasterxml.jackson.core:jackson-databind:jar:2.11.3:compile
[INFO] |        \- com.fasterxml.jackson.core:jackson-annotations:jar:2.11.3:compile

Vert.x Coreのドキュメントから、いくつか特徴や注意事項を抜き出します。

イベント駆動型なので、Vert.x側がアプリケーションを呼び出します。

Don’t call us, we’ll call you.

IOなどで、ブロックしてはいけません。

Don’t block me!

Vert.xは、イベント駆動型ではありますがシングルスレッドではなく、複数のイベントループを持ちます。

Reactor and Multi-Reactor

イベントループをブロックしないこと。

The Golden Rule - Don’t Block the Event Loop

JDBCなど、ブロックするAPIを使う場合は専用のAPI(別のスレッドプール)を介して呼び出すこと。

Running blocking code

複数の非同期処理結果を調整する場合。

Async coordination

とまあ、イベント駆動かつノンブロッキングな考えでアプリケーションを作るためのフレームワークであることが
あらためてわかります。

で、読み進めていくとVerticlesが出てきます。

Verticles

どうも、必ず使うものではないようです。

This model is entirely optional and Vert.x does not force you to create your applications in this way if you don’t want to..

Verticleは、Vert.xによってデプロイおよび実行される、コードのチャンクです。Actorモデルにおける、Actorに似たものと
考えるとよいそうな。

Vert.xインスタンスはデフォルトでCPUコア × 2のイベントループスレッドを保持し、この中で複数のVerticleインスタンスが
含まれるように構成されるのだとか。また、異なるVerticleインスタンスはEvent Busを使ったメッセージ通信も可能です、と。

要するに、Vert.xにおけるデプロイの単位のように見えます。

とりあえず、今あるVerticleを動かしてみましょうか。

作成したプロジェクトをビルドしてみます。

$ mvn package -DskipTests=true

Fat JARを起動。

$ java -jar target/hello-web-1.0.0-SNAPSHOT-fat.jar
HTTP server started on port 8888
11月 15, 2020 9:35:13 午後 io.vertx.core.impl.launcher.commands.VertxIsolatedDeployer
情報: Succeeded in deploying verticle

よく見ると、Verticleをデプロイした、って書いてますね。

確認。

$ curl -i localhost:8888
HTTP/1.1 200 OK
content-type: text/plain
content-length: 18

Hello from Vert.x!

メッセージが返ってきました。Hello from〜はどこで出力しているのでしょう?

というわけで、先ほどのプログラムを見返します(抜粋)。

    vertx.createHttpServer().requestHandler(req -> {
      req.response()
        .putHeader("content-type", "text/plain")
        .end("Hello from Vert.x!");
    }).listen(8888, http -> {
      if (http.succeeded()) {
        startPromise.complete();
        System.out.println("HTTP server started on port 8888");
      } else {
        startPromise.fail(http.cause());
      }
    });

しっかり入っていますね。

ところで、この状態だとリクエストのパスに関わらずいつも同じ結果を返します。

$ curl -i localhost:8888/foo/bar
HTTP/1.1 200 OK
content-type: text/plain
content-length: 18

Hello from Vert.x!

どうやって起動しているか、今一度見返してみましょう。

Maven Shade Pluginでは、こんな設定が入っているんでした。

                <transformer
                  implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                  <manifestEntries>
                    <Main-Class>io.vertx.core.Launcher</Main-Class>
                    <Main-Verticle>${main.verticle}</Main-Verticle>
                  </manifestEntries>
                </transformer>

このあたりを少し追ってみます。

https://github.com/eclipse-vertx/vert.x/blob/3.9.4/src/main/java/io/vertx/core/Launcher.java

https://github.com/eclipse-vertx/vert.x/blob/3.9.4/src/main/java/io/vertx/core/impl/launcher/VertxCommandLauncher.java

https://github.com/eclipse-vertx/vert.x/blob/3.9.4/src/main/java/io/vertx/core/impl/launcher/commands/RunCommand.java

これらのクラス内で、指定されたVerticle(今回は自動生成されたもの)をデプロイしているようです。

また、デフォルトでrunというコマンドを実行しているようなので、Fat JARのヘルプを見てみます。

$ java -jar target/hello-web-1.0.0-SNAPSHOT-fat.jar --help
Usage: java -jar target/hello-web-1.0.0-SNAPSHOT-fat.jar [COMMAND] [OPTIONS]
            [arg...]

Commands:
    bare      Creates a bare instance of vert.x.
    list      List vert.x applications
    run       Runs a verticle called <main-verticle> in its own instance of
              vert.x.
    start     Start a vert.x application in background
    stop      Stop a vert.x application
    version   Displays the version.

Run 'java -jar target/hello-web-1.0.0-SNAPSHOT-fat.jar COMMAND --help' for more
information on a command.

他にもいくつかコマンドがありますね。コマンドの実装は、以下にあります。

https://github.com/eclipse-vertx/vert.x/tree/3.9.4/src/main/java/io/vertx/core/impl/launcher/commands

まあ、なんとなくわかってきたような…?

試しに、Verticleを使わずに、今のVerticleに近い処理を書いてみましょう。こんな感じでしょうか。
src/main/java/org/littlewings/vertx/web/App.java

package org.littlewings.vertx.web;

import io.vertx.core.Vertx;
import io.vertx.core.http.HttpServer;

public class App {
  public static void main(String... args) {
    Vertx vertx = Vertx.vertx();
    HttpServer server = vertx.createHttpServer();

    server.requestHandler(req ->
      req
        .response()
        .putHeader("content-type", "text/plain")
        .end("Hello from Vert.x!")
    );

    server.listen(8888);

    System.out.println("HTTP server started on port 8888");
  }
}

これを直接起動して、curlで確認。

$ curl -i localhost:8888
HTTP/1.1 200 OK
content-type: text/plain
content-length: 18

Hello from Vert.x!

OKです。

なお、mvn exec:javaで実行しようとすると、生成されたpom.xmlはVerticleを実行するように構成されているので
この設定のまま-Dexec.mainClassを指定してもうまくいきません。

      <plugin>
        <groupId>org.codehaus.mojo</groupId>
        <artifactId>exec-maven-plugin</artifactId>
        <version>${exec-maven-plugin.version}</version>
        <configuration>
          <mainClass>io.vertx.core.Launcher</mainClass>
          <arguments>
            <argument>run</argument>
            <argument>${main.verticle}</argument>
          </arguments>
        </configuration>
      </plugin>

ご注意を。

Vert.x Webを見る

現時点で少し使っていますが、Vert.x Webをもう少し使ってみます。

Vert.x-Web - Vert.x

コンセプトやルーティングに関するドキュメント。

Basic Vert.x-Web concepts

リクエストを処理するHandlerがあり、チェインが可能。

Handling requests and calling the next handler

If you don’t end the response in your handler, you should call next so another matching route can handle the request (if any).

ルーティングは固定のパス、前方一致、正規表現、HTTPメソッドを指定でき、パスにパラメーターを含めてキャプチャも
できます。

Routing by exact path

Routing by paths that begin with something

Capturing path parameters

Routing with regular expressions

Routing by HTTP method

HTTPボディを扱う場合。

Request body handling

では、このあたりを見つつ、自動生成されたVerticleをカスタマイズしていくとしましょう。

package org.littlewings.vertx.web;

import io.vertx.core.AbstractVerticle;
import io.vertx.core.Promise;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServer;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Route;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.handler.BodyHandler;

public class MainVerticle extends AbstractVerticle {

  @Override
  public void start(Promise<Void> startPromise) throws Exception {

      // ここに処理を書く

  }
}

まずは、HttpServerの作成とRouterの取得。

    HttpServer server = vertx.createHttpServer();

    Router router = Router.router(vertx);

    // ここにHandlerの実装を書く

    server.requestHandler(router);

    server.listen(8888, http -> {
      if (http.succeeded()) {
        startPromise.complete();
        System.out.println("HTTP server started on port 8888");
      } else {
        startPromise.fail(http.cause());
      }
    });

ここまでを、固定の実装とします。

以降は、コメントの部分の実装を変更しつつ、以下のコマンドでJARの再作成およびプログラムの起動し直しを繰り返していると
思って読んでください。

$ mvn package -DskipTests=true && java -jar target/hello-web-1.0.0-SNAPSHOT-fat.jar

まずはRouterに特になにも設定せずHandlerを追加。

    router.route().handler(routingContext -> {
      HttpServerResponse response = routingContext.response();
      response
        .putHeader("content-type", "text/plain")
        .end("Hello from Vert.x!");
    });

自動生成された処理でも似たようなHandlerらしきものが設定されていましたが、今回はRoutingContextが渡されます。

自動生成されたコードでは、HttpServerRequestが渡されていました。なお、これはRoutingContextからも取得できます。

確認。

$ curl -i localhost:8888
HTTP/1.1 200 OK
content-type: text/plain
content-length: 18

Hello from Vert.x!

次に、Handlerを複数チェインさせてみましょう。1度Routeを取得して、ここにHandlerを足していきます。

    Route route = router.route();

    route.handler(routingContext -> {
      HttpServerResponse response = routingContext.response();

      response.putHeader("content-type", "text/plain");

      routingContext.next();
    });

    route.handler(routingContext -> {
      HttpServerResponse response = routingContext.response();

      response.end("Hello from Vert.x!");
    });

最初のHandlerはRoutingContext#nextを呼び出しているところがポイントで、最後となるHandlerはHttpServerResponse#endを
呼び出す必要があります。

先ほどと動作は変わらないので、確認は省略。

次は、一気にパスの指定、QueryStringの取得、HTTPボディを扱うようにしてみましょう。

    Route getRoute = router.route("/echo").method(HttpMethod.GET);
    getRoute.handler(routingContext -> {
      HttpServerRequest request = routingContext.request();
      HttpServerResponse response = routingContext.response();

      String message = request.getParam("message");

      response.end(String.format("Server reply, [%s]", message));
    });

    Route postRoute = router.route("/json").method(HttpMethod.POST);
    postRoute.handler(BodyHandler.create());
    postRoute.handler(routingContext -> {
      String bodyAsString = routingContext.getBodyAsString();
      System.out.println(bodyAsString);

      JsonObject json = routingContext.getBodyAsJson();
      String message = json.getString("message");

      HttpServerResponse response = routingContext.response();

      response.end(String.format("Server reply, [%s]", message));
    });

Routeを取得する際にパスを指定し、またHTTPメソッドも指定します。

    Route getRoute = router.route("/echo").method(HttpMethod.GET);

QueryStringは、HttpServerRequest#getParamでパラメーターとして扱えるようです。

      String message = request.getParam("message");

HTTPボディを扱う場合は、1度BodyHandlerを追加して

    postRoute.handler(BodyHandler.create());

その後に、Handlerをチェインさせます。

    postRoute.handler(routingContext -> {
      String bodyAsString = routingContext.getBodyAsString();
      System.out.println(bodyAsString);

      JsonObject json = routingContext.getBodyAsJson();
      String message = json.getString("message");

      HttpServerResponse response = routingContext.response();

      response.end(String.format("Server reply, [%s]", message));
    });

今回は、HTTPボディを文字列として取得して標準出力に書き出しつつ、JSONとしても扱っています。

確認。

GETでQueryString。

$ curl -i 'localhost:8888/echo?message=Hello%20World!!'
HTTP/1.1 200 OK
content-length: 29

Server reply, [Hello World!!]

POSTでJSON送信。

$ curl -i localhost:8888/json -d '{"message": "Hello World!!"}'
HTTP/1.1 200 OK
content-length: 29

Server reply, [Hello World!!]

POSTの時は、サーバー側にこんな感じでHTTPボディの内容が出力されます。

{"message": "Hello World!!"}

OKですね。

まとめ

今回は、Vert.xのごくごく基本的な部分と、Vert.x Webを少し扱ってみました。

やや雰囲気がわかってきた感じがするので、これからちょっとずつ慣れていってみましょう。

最後に、今回いろいろ触ったVerticleの全体を載せておきます。
src/main/java/org/littlewings/vertx/web/MainVerticle.java

package org.littlewings.vertx.web;

import io.vertx.core.AbstractVerticle;
import io.vertx.core.Promise;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServer;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Route;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.handler.BodyHandler;

public class MainVerticle extends AbstractVerticle {

  @Override
  public void start(Promise<Void> startPromise) throws Exception {
    HttpServer server = vertx.createHttpServer();

    Router router = Router.router(vertx);

    /*
    router.route().handler(routingContext -> {
      HttpServerResponse response = routingContext.response();
      response
        .putHeader("content-type", "text/plain")
        .end("Hello from Vert.x!");
    });
     */

    /*
    Route route = router.route();

    route.handler(routingContext -> {
      HttpServerResponse response = routingContext.response();

      response.putHeader("content-type", "text/plain");

      routingContext.next();
    });

    route.handler(routingContext -> {
      HttpServerResponse response = routingContext.response();

      response.end("Hello from Vert.x!");
    });
     */

    Route getRoute = router.route("/echo").method(HttpMethod.GET);
    getRoute.handler(routingContext -> {
      HttpServerRequest request = routingContext.request();
      HttpServerResponse response = routingContext.response();

      String message = request.getParam("message");

      response.end(String.format("Server reply, [%s]", message));
    });

    Route postRoute = router.route("/json").method(HttpMethod.POST);
    postRoute.handler(BodyHandler.create());
    postRoute.handler(routingContext -> {
      String bodyAsString = routingContext.getBodyAsString();
      System.out.println(bodyAsString);

      JsonObject json = routingContext.getBodyAsJson();
      String message = json.getString("message");

      HttpServerResponse response = routingContext.response();

      response.end(String.format("Server reply, [%s]", message));
    });

    server.requestHandler(router);

    server.listen(8888, http -> {
      if (http.succeeded()) {
        startPromise.complete();
        System.out.println("HTTP server started on port 8888");
      } else {
        startPromise.fail(http.cause());
      }
    });
  }
}