これは、なにをしたくて書いたもの?
そろそろ、Vert.xも少し知っておいた方がいいのかなーと思いまして。
試してみようかなと。
Vert.xとは
Vert.xは、イベント駆動、ノンブロッキング、複数の言語による開発が可能なフレームワークです。
複数の言語とは、Java、Kotlin、JavaScript、Groovy、Ruby、Scalaを指します。
まだ若いフレームワークかというとそうでもなく、けっこう前から存在しています。
現在のバージョンは3.9系ですが、4が開発中です。
4ではJava、Kotlin、Groovyあたりがターゲットになるんでしょうかね?(コード例が減ってる)
現在のバージョンのドキュメントは、こちらです。
とりあえず、始めてみましょうか。
環境
今回の環境は、こちらです。
$ 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のドキュメントを読みつつ、作成したプロジェクトを動かしてみる
Vert.x Coreのドキュメントを、少し眺めてみます。
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などで、ブロックしてはいけません。
Vert.xは、イベント駆動型ではありますがシングルスレッドではなく、複数のイベントループを持ちます。
イベントループをブロックしないこと。
The Golden Rule - Don’t Block the Event Loop
JDBCなど、ブロックするAPIを使う場合は専用のAPI(別のスレッドプール)を介して呼び出すこと。
複数の非同期処理結果を調整する場合。
とまあ、イベント駆動かつノンブロッキングな考えでアプリケーションを作るためのフレームワークであることが
あらためてわかります。
で、読み進めていくと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
これらのクラス内で、指定された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.
他にもいくつかコマンドがありますね。コマンドの実装は、以下にあります。
まあ、なんとなくわかってきたような…?
試しに、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をもう少し使ってみます。
コンセプトやルーティングに関するドキュメント。
リクエストを処理する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 paths that begin with something
Routing with regular expressions
HTTPボディを扱う場合。
では、このあたりを見つつ、自動生成された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()); } }); } }