CLOVER🍀

That was when it all began.

UndertowからVert.xバックエンドになった、QuarkusのRESTEasyを試す

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

最近Quarkusを触っていなかったのですが、少し前にRESTEasyのバックエンドがUndertowからVert.xに移ったという話を見たので、
1度見ておこうかなと。

Quarkus 0.24.0 released - Vert.x everywhere

Quarkus 0.23.2 released - Back on track

Quarkus 0.23.1 released - Paving the way to our new HTTP layer

0.24.0で完全に移行していて、0.23.1から移り始めていたんですね。2019年9月の話ですねぇ。

久しぶりに、ちょっと試してみましょう。

環境

今回の環境は、こちら。

$ java -version
openjdk version "1.8.0_232"
OpenJDK Runtime Environment (build 1.8.0_232-8u232-b09-0ubuntu1~18.04.1-b09)
OpenJDK 64-Bit Server VM (build 25.232-b09, mixed mode)


$ $GRAALVM_HOME/bin/native-image --version
GraalVM Version 19.2.1 CE


$ mvn --version
Apache Maven 3.6.3 (cecedd343002696d0abb50b32b541b8a6ba2883f)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 1.8.0_232, vendor: Private Build, runtime: /usr/lib/jvm/java-8-openjdk-amd64/jre
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "4.15.0-74-generic", arch: "amd64", family: "unix"

サンプルプロジェクト

今回は、RESTEasy+JSON(Jackson)な構成でいきましょう。

プロジェクトの作成。今回使用するQuarkusのバージョンは、1.1.1.Finalです。

$ mvn io.quarkus:quarkus-maven-plugin:1.1.1.Final:create \
    -DprojectGroupId=org.littlewings \
    -DprojectArtifactId=resteasy-vertx-jackson \
    -Dextensions="resteasy-jackson"

作成されたディレクトリに移動。

$ cd resteasy-vertx-jackson

pom.xmlに書かれた依存関係は、こんな感じです。

  <dependencies>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-resteasy</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-junit5</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>io.rest-assured</groupId>
      <artifactId>rest-assured</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-resteasy-jackson</artifactId>
    </dependency>
  </dependencies>

Mavenで依存関係を見てみましょう。

$ mvn dependency:tree

こんな感じの結果に。

[INFO] --- maven-dependency-plugin:2.8:tree (default-cli) @ resteasy-vertx-jackson ---
[INFO] org.littlewings:resteasy-vertx-jackson:jar:1.0-SNAPSHOT
[INFO] +- io.quarkus:quarkus-resteasy:jar:1.1.1.Final:compile
[INFO] |  +- io.quarkus:quarkus-vertx-http:jar:1.1.1.Final:compile
[INFO] |  |  +- io.quarkus.security:quarkus-security:jar:1.0.1.Final:compile
[INFO] |  |  +- jakarta.enterprise:jakarta.enterprise.cdi-api:jar:2.0.2:compile
[INFO] |  |  |  +- jakarta.el:jakarta.el-api:jar:3.0.3:compile
[INFO] |  |  |  \- jakarta.interceptor:jakarta.interceptor-api:jar:1.2.5:compile
[INFO] |  |  +- io.quarkus:quarkus-vertx-core:jar:1.1.1.Final:compile
[INFO] |  |  |  +- io.quarkus:quarkus-netty:jar:1.1.1.Final:compile
[INFO] |  |  |  |  +- io.netty:netty-codec:jar:4.1.42.Final:compile
[INFO] |  |  |  |  \- io.netty:netty-handler:jar:4.1.42.Final:compile
[INFO] |  |  |  \- io.vertx:vertx-core:jar:3.8.4:compile
[INFO] |  |  |     +- io.netty:netty-common:jar:4.1.42.Final:compile
[INFO] |  |  |     +- io.netty:netty-buffer:jar:4.1.42.Final:compile
[INFO] |  |  |     +- io.netty:netty-transport:jar:4.1.42.Final:compile
[INFO] |  |  |     +- io.netty:netty-handler-proxy:jar:4.1.42.Final:compile
[INFO] |  |  |     |  \- io.netty:netty-codec-socks:jar:4.1.42.Final:compile
[INFO] |  |  |     +- io.netty:netty-codec-http:jar:4.1.42.Final:compile
[INFO] |  |  |     +- io.netty:netty-codec-http2:jar:4.1.42.Final:compile
[INFO] |  |  |     +- io.netty:netty-resolver:jar:4.1.42.Final:compile
[INFO] |  |  |     \- io.netty:netty-resolver-dns:jar:4.1.42.Final:compile
[INFO] |  |  |        \- io.netty:netty-codec-dns:jar:4.1.42.Final:compile
[INFO] |  |  \- io.vertx:vertx-web:jar:3.8.4:compile
[INFO] |  |     +- io.vertx:vertx-web-common:jar:3.8.4:compile
[INFO] |  |     +- io.vertx:vertx-auth-common:jar:3.8.4:compile
[INFO] |  |     \- io.vertx:vertx-bridge-common:jar:3.8.4:compile
[INFO] |  \- io.quarkus:quarkus-resteasy-server-common:jar:1.1.1.Final:compile
[INFO] |     +- io.quarkus:quarkus-arc:jar:1.1.1.Final:compile
[INFO] |     |  +- io.quarkus.arc:arc:jar:1.1.1.Final:compile
[INFO] |     |  \- org.eclipse.microprofile.context-propagation:microprofile-context-propagation-api:jar:1.0.1:compile
[INFO] |     +- io.quarkus:quarkus-resteasy-common:jar:1.1.1.Final:compile
[INFO] |     |  +- org.jboss.resteasy:resteasy-core:jar:4.4.1.Final:compile
[INFO] |     |  |  +- org.jboss.spec.javax.ws.rs:jboss-jaxrs-api_2.1_spec:jar:2.0.1.Final:compile
[INFO] |     |  |  +- org.jboss.resteasy:resteasy-core-spi:jar:4.4.1.Final:compile
[INFO] |     |  |  +- org.reactivestreams:reactive-streams:jar:1.0.3:compile
[INFO] |     |  |  \- org.eclipse.microprofile.config:microprofile-config-api:jar:1.3:compile
[INFO] |     |  \- com.sun.activation:jakarta.activation:jar:1.2.1:compile
[INFO] |     \- jakarta.validation:jakarta.validation-api:jar:2.0.2:compile
[INFO] +- io.quarkus:quarkus-junit5:jar:1.1.1.Final:test
[INFO] |  +- io.quarkus:quarkus-bootstrap-core:jar:1.1.1.Final:test
[INFO] |  |  +- com.google.guava:guava:jar:27.0.1-jre:compile
[INFO] |  |  |  +- com.google.guava:failureaccess:jar:1.0.1:compile
[INFO] |  |  |  +- com.google.guava:listenablefuture:jar:9999.0-empty-to-avoid-conflict-with-guava:compile
[INFO] |  |  |  +- com.google.code.findbugs:jsr305:jar:3.0.2:compile
[INFO] |  |  |  +- org.checkerframework:checker-qual:jar:2.5.2:compile
[INFO] |  |  |  +- com.google.errorprone:error_prone_annotations:jar:2.2.0:compile
[INFO] |  |  |  +- com.google.j2objc:j2objc-annotations:jar:1.1:compile
[INFO] |  |  |  \- org.codehaus.mojo:animal-sniffer-annotations:jar:1.17:compile
[INFO] |  |  +- org.apache.commons:commons-lang3:jar:3.9:test
[INFO] |  |  +- org.apache.maven:maven-embedder:jar:3.5.4:test
[INFO] |  |  |  +- org.apache.maven:maven-settings:jar:3.5.4:test
[INFO] |  |  |  +- org.apache.maven:maven-core:jar:3.5.4:test
[INFO] |  |  |  |  \- org.apache.maven:maven-artifact:jar:3.5.4:test
[INFO] |  |  |  +- org.apache.maven:maven-plugin-api:jar:3.5.4:test
[INFO] |  |  |  +- org.apache.maven:maven-model:jar:3.5.4:test
[INFO] |  |  |  +- org.apache.maven:maven-model-builder:jar:3.5.4:test
[INFO] |  |  |  +- org.apache.maven:maven-builder-support:jar:3.5.4:test
[INFO] |  |  |  +- org.apache.maven.resolver:maven-resolver-api:jar:1.1.1:test
[INFO] |  |  |  +- org.apache.maven.resolver:maven-resolver-util:jar:1.1.1:test
[INFO] |  |  |  +- org.apache.maven.shared:maven-shared-utils:jar:3.2.1:test
[INFO] |  |  |  |  \- commons-io:commons-io:jar:2.6:test
[INFO] |  |  |  +- com.google.inject:guice:jar:no_aop:4.2.0:test
[INFO] |  |  |  |  \- aopalliance:aopalliance:jar:1.0:test
[INFO] |  |  |  +- org.codehaus.plexus:plexus-utils:jar:3.0.24:test
[INFO] |  |  |  +- org.codehaus.plexus:plexus-classworlds:jar:2.5.2:test
[INFO] |  |  |  +- org.eclipse.sisu:org.eclipse.sisu.plexus:jar:0.3.3:test
[INFO] |  |  |  +- org.codehaus.plexus:plexus-component-annotations:jar:1.7.1:test
[INFO] |  |  |  \- commons-cli:commons-cli:jar:1.4:test
[INFO] |  |  +- jakarta.annotation:jakarta.annotation-api:jar:1.3.5:compile
[INFO] |  |  +- jakarta.inject:jakarta.inject-api:jar:1.0:compile
[INFO] |  |  +- org.apache.maven:maven-settings-builder:jar:3.5.4:test
[INFO] |  |  |  +- org.codehaus.plexus:plexus-interpolation:jar:1.24:test
[INFO] |  |  |  \- org.sonatype.plexus:plexus-sec-dispatcher:jar:1.4:test
[INFO] |  |  |     \- org.sonatype.plexus:plexus-cipher:jar:1.4:test
[INFO] |  |  +- org.apache.maven:maven-resolver-provider:jar:3.5.4:test
[INFO] |  |  |  +- org.apache.maven:maven-repository-metadata:jar:3.5.4:test
[INFO] |  |  |  +- org.apache.maven.resolver:maven-resolver-spi:jar:1.1.1:test
[INFO] |  |  |  \- org.apache.maven.resolver:maven-resolver-impl:jar:1.1.1:test
[INFO] |  |  +- org.apache.maven.resolver:maven-resolver-connector-basic:jar:1.1.1:test
[INFO] |  |  +- org.apache.maven.resolver:maven-resolver-transport-wagon:jar:1.1.1:test
[INFO] |  |  |  \- org.apache.maven.wagon:wagon-provider-api:jar:3.0.0:test
[INFO] |  |  +- org.apache.maven.wagon:wagon-http:jar:3.0.0:test
[INFO] |  |  |  \- org.apache.maven.wagon:wagon-http-shared:jar:3.0.0:test
[INFO] |  |  |     \- org.jsoup:jsoup:jar:1.7.2:test
[INFO] |  |  +- org.apache.maven.wagon:wagon-file:jar:3.0.0:test
[INFO] |  |  +- org.jboss.logging:jboss-logging:jar:3.3.2.Final:compile
[INFO] |  |  \- org.jboss.logging:commons-logging-jboss-logging:jar:1.0.0.Final:test
[INFO] |  +- io.quarkus:quarkus-test-common:jar:1.1.1.Final:test
[INFO] |  |  +- io.quarkus:quarkus-core-deployment:jar:1.1.1.Final:test
[INFO] |  |  |  +- io.quarkus.gizmo:gizmo:jar:1.0.0.Final:test
[INFO] |  |  |  |  \- org.ow2.asm:asm-util:jar:7.1:test
[INFO] |  |  |  |     +- org.ow2.asm:asm-tree:jar:7.1:test
[INFO] |  |  |  |     \- org.ow2.asm:asm-analysis:jar:7.1:test
[INFO] |  |  |  +- org.ow2.asm:asm:jar:7.1:test
[INFO] |  |  |  \- io.quarkus:quarkus-builder:jar:1.1.1.Final:test
[INFO] |  |  +- io.quarkus:quarkus-jsonp-deployment:jar:1.1.1.Final:test
[INFO] |  |  |  \- io.quarkus:quarkus-jsonp:jar:1.1.1.Final:test
[INFO] |  |  |     \- org.glassfish:jakarta.json:jar:1.1.6:test
[INFO] |  |  \- org.jboss:jandex:jar:2.1.2.Final:test
[INFO] |  +- org.junit.jupiter:junit-jupiter:jar:5.5.2:test
[INFO] |  |  +- org.junit.jupiter:junit-jupiter-api:jar:5.5.2:test
[INFO] |  |  |  +- org.apiguardian:apiguardian-api:jar:1.1.0:test
[INFO] |  |  |  +- org.opentest4j:opentest4j:jar:1.2.0:test
[INFO] |  |  |  \- org.junit.platform:junit-platform-commons:jar:1.5.2:test
[INFO] |  |  +- org.junit.jupiter:junit-jupiter-params:jar:5.5.2:test
[INFO] |  |  \- org.junit.jupiter:junit-jupiter-engine:jar:5.5.2:test
[INFO] |  |     \- org.junit.platform:junit-platform-engine:jar:1.5.2:test
[INFO] |  \- io.quarkus:quarkus-core:jar:1.1.1.Final:compile
[INFO] |     +- io.smallrye.config:smallrye-config:jar:1.5.1:compile
[INFO] |     |  \- io.smallrye.config:smallrye-config-common:jar:1.5.1:compile
[INFO] |     +- org.jboss.logmanager:jboss-logmanager-embedded:jar:1.0.4:compile
[INFO] |     +- org.jboss.logging:jboss-logging-annotations:jar:2.1.0.Final:compile
[INFO] |     +- org.jboss.threads:jboss-threads:jar:3.0.0.Final:compile
[INFO] |     +- org.slf4j:slf4j-api:jar:1.7.29:compile
[INFO] |     +- org.jboss.slf4j:slf4j-jboss-logging:jar:1.2.0.Final:compile
[INFO] |     +- org.graalvm.sdk:graal-sdk:jar:19.2.1:compile
[INFO] |     \- org.wildfly.common:wildfly-common:jar:1.5.0.Final-format-001:compile
[INFO] +- io.rest-assured:rest-assured:jar:4.1.2:test
[INFO] |  +- org.codehaus.groovy:groovy:jar:2.5.8:test
[INFO] |  +- org.codehaus.groovy:groovy-xml:jar:2.5.8:test
[INFO] |  +- org.apache.httpcomponents:httpclient:jar:4.5.10:test
[INFO] |  |  +- org.apache.httpcomponents:httpcore:jar:4.4.12:test
[INFO] |  |  \- commons-codec:commons-codec:jar:1.13:test
[INFO] |  +- org.apache.httpcomponents:httpmime:jar:4.5.3:test
[INFO] |  +- org.hamcrest:hamcrest:jar:2.1:test
[INFO] |  +- org.ccil.cowan.tagsoup:tagsoup:jar:1.2.1:test
[INFO] |  +- io.rest-assured:json-path:jar:4.1.2:test
[INFO] |  |  +- org.codehaus.groovy:groovy-json:jar:2.5.8:test
[INFO] |  |  \- io.rest-assured:rest-assured-common:jar:4.1.2:test
[INFO] |  \- io.rest-assured:xml-path:jar:4.1.2:test
[INFO] |     +- javax.xml.bind:jaxb-api:jar:2.3.1:test
[INFO] |     \- org.apache.sling:org.apache.sling.javax.activation:jar:0.1.0:test
[INFO] \- io.quarkus:quarkus-resteasy-jackson:jar:1.1.1.Final:compile
[INFO]    +- io.quarkus:quarkus-jackson:jar:1.1.1.Final:compile
[INFO]    |  +- com.fasterxml.jackson.core:jackson-databind:jar:2.9.10.2:compile
[INFO]    |  +- com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:2.9.10:compile
[INFO]    |  +- com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:2.9.10:compile
[INFO]    |  \- com.fasterxml.jackson.module:jackson-module-parameter-names:jar:2.9.10:compile
[INFO]    +- org.jboss.resteasy:resteasy-jackson2-provider:jar:4.4.1.Final:compile
[INFO]    |  +- org.jboss.resteasy:resteasy-jaxb-provider:jar:4.4.1.Final:compile
[INFO]    |  |  \- org.glassfish.jaxb:jaxb-runtime:jar:2.3.3-b01:compile
[INFO]    |  |     +- org.glassfish.jaxb:txw2:jar:2.3.3-b01:compile
[INFO]    |  |     \- com.sun.istack:istack-commons-runtime:jar:3.0.10:compile
[INFO]    |  +- com.fasterxml.jackson.core:jackson-core:jar:2.9.10:compile
[INFO]    |  +- com.fasterxml.jackson.core:jackson-annotations:jar:2.9.10:compile
[INFO]    |  +- com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:jar:2.9.10:compile
[INFO]    |  |  +- com.fasterxml.jackson.jaxrs:jackson-jaxrs-base:jar:2.9.10:compile
[INFO]    |  |  \- com.fasterxml.jackson.module:jackson-module-jaxb-annotations:jar:2.9.10:compile
[INFO]    |  \- com.github.fge:json-patch:jar:1.9:compile
[INFO]    |     \- com.github.fge:jackson-coreutils:jar:1.6:compile
[INFO]    |        \- com.github.fge:msg-simple:jar:1.1:compile
[INFO]    |           \- com.github.fge:btf:jar:1.2:compile
[INFO]    \- org.jboss.spec.javax.xml.bind:jboss-jaxb-api_2.3_spec:jar:2.0.0.Final:compile

RESTEasyまわりを見ると、確かにUndertowがいません。Vert.xとNettyが見えます。

[INFO] +- io.quarkus:quarkus-resteasy:jar:1.1.1.Final:compile
[INFO] |  +- io.quarkus:quarkus-vertx-http:jar:1.1.1.Final:compile
[INFO] |  |  +- io.quarkus.security:quarkus-security:jar:1.0.1.Final:compile
[INFO] |  |  +- jakarta.enterprise:jakarta.enterprise.cdi-api:jar:2.0.2:compile
[INFO] |  |  |  +- jakarta.el:jakarta.el-api:jar:3.0.3:compile
[INFO] |  |  |  \- jakarta.interceptor:jakarta.interceptor-api:jar:1.2.5:compile
[INFO] |  |  +- io.quarkus:quarkus-vertx-core:jar:1.1.1.Final:compile
[INFO] |  |  |  +- io.quarkus:quarkus-netty:jar:1.1.1.Final:compile
[INFO] |  |  |  |  +- io.netty:netty-codec:jar:4.1.42.Final:compile
[INFO] |  |  |  |  \- io.netty:netty-handler:jar:4.1.42.Final:compile
[INFO] |  |  |  \- io.vertx:vertx-core:jar:3.8.4:compile
[INFO] |  |  |     +- io.netty:netty-common:jar:4.1.42.Final:compile
[INFO] |  |  |     +- io.netty:netty-buffer:jar:4.1.42.Final:compile
[INFO] |  |  |     +- io.netty:netty-transport:jar:4.1.42.Final:compile
[INFO] |  |  |     +- io.netty:netty-handler-proxy:jar:4.1.42.Final:compile
[INFO] |  |  |     |  \- io.netty:netty-codec-socks:jar:4.1.42.Final:compile
[INFO] |  |  |     +- io.netty:netty-codec-http:jar:4.1.42.Final:compile
[INFO] |  |  |     +- io.netty:netty-codec-http2:jar:4.1.42.Final:compile
[INFO] |  |  |     +- io.netty:netty-resolver:jar:4.1.42.Final:compile
[INFO] |  |  |     \- io.netty:netty-resolver-dns:jar:4.1.42.Final:compile
[INFO] |  |  |        \- io.netty:netty-codec-dns:jar:4.1.42.Final:compile
[INFO] |  |  \- io.vertx:vertx-web:jar:3.8.4:compile
[INFO] |  |     +- io.vertx:vertx-web-common:jar:3.8.4:compile
[INFO] |  |     +- io.vertx:vertx-auth-common:jar:3.8.4:compile
[INFO] |  |     \- io.vertx:vertx-bridge-common:jar:3.8.4:compile
[INFO] |  \- io.quarkus:quarkus-resteasy-server-common:jar:1.1.1.Final:compile
[INFO] |     +- io.quarkus:quarkus-arc:jar:1.1.1.Final:compile
[INFO] |     |  +- io.quarkus.arc:arc:jar:1.1.1.Final:compile
[INFO] |     |  \- org.eclipse.microprofile.context-propagation:microprofile-context-propagation-api:jar:1.0.1:compile
[INFO] |     +- io.quarkus:quarkus-resteasy-common:jar:1.1.1.Final:compile
[INFO] |     |  +- org.jboss.resteasy:resteasy-core:jar:4.4.1.Final:compile
[INFO] |     |  |  +- org.jboss.spec.javax.ws.rs:jboss-jaxrs-api_2.1_spec:jar:2.0.1.Final:compile
[INFO] |     |  |  +- org.jboss.resteasy:resteasy-core-spi:jar:4.4.1.Final:compile
[INFO] |     |  |  +- org.reactivestreams:reactive-streams:jar:1.0.3:compile
[INFO] |     |  |  \- org.eclipse.microprofile.config:microprofile-config-api:jar:1.3:compile
[INFO] |     |  \- com.sun.activation:jakarta.activation:jar:1.2.1:compile
[INFO] |     \- jakarta.validation:jakarta.validation-api:jar:2.0.2:compile

というか、Servletすらいないじゃないですか。JAX-RSのSpecは、さすがにいますね。

今回は、書籍をお題にコードを書いてみます。

書籍クラス。
src/main/java/org/littlewings/quarkus/resteasy/Book.java

package org.littlewings.quarkus.resteasy;

public class Book {
    private String isbn;
    private String title;
    private int price;

    // getter/setterは省略
}

JAX-RSリソースクラス。
src/main/java/org/littlewings/quarkus/resteasy/BookResource.java

package org.littlewings.quarkus.resteasy;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;

@Path("book")
public class BookResource {
    Map<String, Book> books = new ConcurrentHashMap<>();

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public List<Book> books() {
        return new ArrayList<>(books.values());
    }

    @GET
    @Path("{isbn}")
    @Produces(MediaType.APPLICATION_JSON)
    public Book find(@PathParam("isbn") String isbn) {
        return books.get(isbn);
    }

    @PUT
    @Path("{isbn}")
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public Response create(Book book, @Context UriInfo uriInfo) {
        books.put(book.getIsbn(), book);

        return Response.created(uriInfo.getRequestUriBuilder().build()).build();
    }
}

パッケージングして、

$ mvn package

起動。

$ java -jar target/resteasy-vertx-jackson-1.0-SNAPSHOT-runner.jar

認識しているExtensionに、Servletはいません。

2020-01-25 19:11:45,900 INFO  [io.quarkus] (main) Installed features: [cdi, resteasy, resteasy-jackson]

データの登録。

$ curl -i -XPUT -H 'Content-Type: application/json' http://localhost:8080/book/978-4774183169 -d '{"isbn": "978-4774183169", "title": "パーフェクト Java EE", "price": 3456}'
HTTP/1.1 201 Created
Content-Length: 0
Location: http://localhost:8080/book/978-4774183169


$ curl -i -XPUT -H 'Content-Type: application/json' http://localhost:8080/book/978-4798124605 -d '{"isbn": "978-4798124605", "title": "Beginning Java EE 6", "price": 3891}'
HTTP/1.1 201 Created
Content-Length: 0
Location: http://localhost:8080/book/978-4798124605


$ curl -i -XPUT -H 'Content-Type: application/json' http://localhost:8080/book/978-4798140926 -d '{"isbn": "978-4798140926", "title": "Java EE 7徹底入門", "price": 4104}'
HTTP/1.1 201 Created
Content-Length: 0
Location: http://localhost:8080/book/978-4798140926

データの取得。

$ curl -s http://localhost:8080/book/978-4774183169 | jq
{
  "isbn": "978-4774183169",
  "title": "パーフェクト Java EE",
  "price": 3456
}


$ curl -s localhost:8080/book | jq
[
  {
    "isbn": "978-4798124605",
    "title": "Beginning Java EE 6",
    "price": 3891
  },
  {
    "isbn": "978-4798140926",
    "title": "Java EE 7徹底入門",
    "price": 4104
  },
  {
    "isbn": "978-4774183169",
    "title": "パーフェクト Java EE",
    "price": 3456
  }
]

ネイティブイメージにしても結果は同じなので、割愛。

$ mvn -P native package

Servlet APIの代わりや、スレッドの情報を見る

ところで、ここまでだと特に面白味がないので、HttpServletRequest代わりの情報とかどうしたらいいんだろう?というところだと、
RESTEasyのSPIを使うみたいです。

HttpRequest (RESTEasy JAX-RS 4.4.1.Final API)

Servlet compatibility

https://docs.jboss.org/resteasy/docs/4.4.1.Final/javadocs/org/jboss/resteasy/spi/package-summary.html

あと、スレッドの情報とか気になったりします。

というわけで、こんなJAX-RSリソースクラスを使って確認。
src/main/java/org/littlewings/quarkus/resteasy/HttpRequestResource.java

package org.littlewings.quarkus.resteasy;

import java.util.LinkedHashMap;
import java.util.Map;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;

import org.jboss.logging.Logger;
import org.jboss.resteasy.spi.HttpRequest;

@Path("http-request")
public class HttpRequestResource {
    Logger logger = Logger.getLogger(HttpRequestResource.class);

    @GET
    @Path("print-request")
    @Produces(MediaType.APPLICATION_JSON)
    public Map<String, Object> printRequest(@Context HttpRequest request) {
        Map<String, Object> response = new LinkedHashMap<>();

        response.put("method", request.getHttpMethod());
        response.put("request-uri", request.getUri().getRequestUri().toString());
        response.put("path", request.getUri().getPath());
        response.put("query", request.getUri().getRequestUri().getQuery());

        return response;
    }

    @GET
    @Path("thread-dump")
    @Produces(MediaType.TEXT_PLAIN)
    public String printThreadDump() {
        logger.infof("Thread[%s / %s]", Thread.currentThread().getName(), Thread.currentThread().getClass().getName());

        Thread.dumpStack();

        return "print Thread Dump";
    }
}

確認。

$ curl -s localhost:8080/http-request/print-request?param=hoge | jq
{
  "method": "GET",
  "request-uri": "http://localhost:8080/http-request/print-request?param=hoge",
  "path": "/http-request/print-request",
  "query": "param=hoge"
}


$ curl -s http://localhost:8080/http-request/thread-dump
print Thread Dump

スレッドダンプは、こんな感じでした。

2020-01-25 19:12:04,117 INFO  [org.lit.qua.res.HttpRequestResource] (executor-thread-1) Thread[executor-thread-1 / org.jboss.threads.JBossThread]
java.lang.Exception: Stack trace
    at java.lang.Thread.dumpStack(Thread.java:1336)
    at org.littlewings.quarkus.resteasy.HttpRequestResource.printThreadDump(HttpRequestResource.java:38)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:151)
    at org.jboss.resteasy.core.MethodInjectorImpl.lambda$invoke$3(MethodInjectorImpl.java:122)
    at java.util.concurrent.CompletableFuture.uniApply(CompletableFuture.java:616)
    at java.util.concurrent.CompletableFuture.uniApplyStage(CompletableFuture.java:628)
    at java.util.concurrent.CompletableFuture.thenApply(CompletableFuture.java:1996)
    at java.util.concurrent.CompletableFuture.thenApply(CompletableFuture.java:110)
    at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:122)
    at org.jboss.resteasy.core.ResourceMethodInvoker.internalInvokeOnTarget(ResourceMethodInvoker.java:594)
    at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTargetAfterFilter(ResourceMethodInvoker.java:468)
    at org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invokeOnTarget$2(ResourceMethodInvoker.java:421)
    at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:363)
    at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:423)
    at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:391)
    at org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invoke$1(ResourceMethodInvoker.java:365)
    at java.util.concurrent.CompletableFuture.uniComposeStage(CompletableFuture.java:995)
    at java.util.concurrent.CompletableFuture.thenCompose(CompletableFuture.java:2137)
    at java.util.concurrent.CompletableFuture.thenCompose(CompletableFuture.java:110)
    at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:365)
    at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:477)
    at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:252)
    at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:153)
    at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:363)
    at org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:156)
    at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:238)
    at io.quarkus.resteasy.runtime.standalone.RequestDispatcher.service(RequestDispatcher.java:73)
    at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.dispatch(VertxRequestHandler.java:120)
    at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.access$000(VertxRequestHandler.java:36)
    at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler$1.run(VertxRequestHandler.java:85)
    at org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
    at org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:2011)
    at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1535)
    at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1426)
    at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
    at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
    at java.lang.Thread.run(Thread.java:748)
    at org.jboss.threads.JBossThread.run(JBossThread.java:479)

スレッドプールの情報を追うと、このあたりのようです。

quarkus/ExecutorRecorder.java at 1.1.1.Final · quarkusio/quarkus · GitHub

https://quarkus.io/guides/all-config#quarkus-core_quarkus.thread-pool.core-threads

https://github.com/quarkusio/quarkus/blob/1.1.1.Final/core/runtime/src/main/java/io/quarkus/runtime/ThreadPoolConfig.java

他のバージョンだと、Vert.xのスレッドプールで調整しているようだったので、バージョンごとにだいぶ変わるんですねぇ…。

なお、ネイティブイメージの時のスタックトレースは、こんな感じでした。

2020-01-25 19:12:49,917 INFO  [org.lit.qua.res.HttpRequestResource] (executor-thread-1) Thread[executor-thread-1 / org.jboss.threads.JBossThread]
java.lang.Exception: Stack trace
    at java.lang.Thread.dumpStack(Thread.java:1336)
    at org.littlewings.quarkus.resteasy.HttpRequestResource.printThreadDump(HttpRequestResource.java:38)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:151)
    at org.jboss.resteasy.core.MethodInjectorImpl.lambda$invoke$3(MethodInjectorImpl.java:122)
    at java.util.concurrent.CompletableFuture.uniApply(CompletableFuture.java:616)
    at java.util.concurrent.CompletableFuture.uniApplyStage(CompletableFuture.java:628)
    at java.util.concurrent.CompletableFuture.thenApply(CompletableFuture.java:1996)
    at java.util.concurrent.CompletableFuture.thenApply(CompletableFuture.java:110)
    at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:122)
    at org.jboss.resteasy.core.ResourceMethodInvoker.internalInvokeOnTarget(ResourceMethodInvoker.java:594)
    at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTargetAfterFilter(ResourceMethodInvoker.java:468)
    at org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invokeOnTarget$2(ResourceMethodInvoker.java:421)
    at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:363)
    at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:423)
    at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:391)
    at org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invoke$1(ResourceMethodInvoker.java:365)
    at java.util.concurrent.CompletableFuture.uniComposeStage(CompletableFuture.java:995)
    at java.util.concurrent.CompletableFuture.thenCompose(CompletableFuture.java:2137)
    at java.util.concurrent.CompletableFuture.thenCompose(CompletableFuture.java:110)
    at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:365)
    at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:477)
    at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:252)
    at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:153)
    at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:363)
    at org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:156)
    at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:238)
    at io.quarkus.resteasy.runtime.standalone.RequestDispatcher.service(RequestDispatcher.java:73)
    at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.dispatch(VertxRequestHandler.java:120)
    at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler.access$000(VertxRequestHandler.java:36)
    at io.quarkus.resteasy.runtime.standalone.VertxRequestHandler$1.run(VertxRequestHandler.java:85)
    at org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
    at org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:2011)
    at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1535)
    at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1395)
    at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
    at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
    at java.lang.Thread.run(Thread.java:748)
    at org.jboss.threads.JBossThread.run(JBossThread.java:479)
    at com.oracle.svm.core.thread.JavaThreads.threadStartRoutine(JavaThreads.java:460)
    at com.oracle.svm.core.posix.thread.PosixJavaThreads.pthreadStartRoutine(PosixJavaThreads.java:193)

ここが増えてますね。

 at com.oracle.svm.core.thread.JavaThreads.threadStartRoutine(JavaThreads.java:460)
    at com.oracle.svm.core.posix.thread.PosixJavaThreads.pthreadStartRoutine(PosixJavaThreads.java:193)

Servlet APIを使う

RESTEasyのバックエンドを、Vert.xではなくUndertowにするには、Undertow Extensionを追加します。

$ mvn quarkus:add-extension -Dextensions=quarkus-undertow

依存関係に、こちらが追加されます。

    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-undertow</artifactId>
    </dependency>

「mvn dependency:tree」で依存関係を見ると、Servlet APIは追加されるものの、その背後にいるのはVert.xですね。

[INFO] \- io.quarkus:quarkus-undertow:jar:1.1.1.Final:compile
[INFO]    +- io.quarkus:quarkus-arc:jar:1.1.1.Final:compile
[INFO]    |  \- io.quarkus.arc:arc:jar:1.1.1.Final:compile
[INFO]    +- io.quarkus.security:quarkus-security:jar:1.0.1.Final:compile
[INFO]    +- jakarta.enterprise:jakarta.enterprise.cdi-api:jar:2.0.2:compile
[INFO]    |  +- jakarta.el:jakarta.el-api:jar:3.0.3:compile
[INFO]    |  \- jakarta.interceptor:jakarta.interceptor-api:jar:1.2.5:compile
[INFO]    +- io.quarkus.http:quarkus-http-servlet:jar:3.0.1.Final:compile
[INFO]    +- jakarta.servlet:jakarta.servlet-api:jar:4.0.3:compile
[INFO]    +- jakarta.annotation:jakarta.annotation-api:jar:1.3.5:compile
[INFO]    +- io.quarkus.http:quarkus-http-vertx-backend:jar:3.0.1.Final:compile
[INFO]    |  +- io.vertx:vertx-core:jar:3.8.4:compile
[INFO]    |  |  +- io.netty:netty-common:jar:4.1.42.Final:compile
[INFO]    |  |  +- io.netty:netty-buffer:jar:4.1.42.Final:compile
[INFO]    |  |  +- io.netty:netty-transport:jar:4.1.42.Final:compile
[INFO]    |  |  +- io.netty:netty-handler:jar:4.1.42.Final:compile
[INFO]    |  |  +- io.netty:netty-handler-proxy:jar:4.1.42.Final:compile
[INFO]    |  |  |  \- io.netty:netty-codec-socks:jar:4.1.42.Final:compile
[INFO]    |  |  +- io.netty:netty-codec-http2:jar:4.1.42.Final:compile
[INFO]    |  |  +- io.netty:netty-resolver:jar:4.1.42.Final:compile
[INFO]    |  |  \- io.netty:netty-resolver-dns:jar:4.1.42.Final:compile
[INFO]    |  |     \- io.netty:netty-codec-dns:jar:4.1.42.Final:compile
[INFO]    |  \- io.quarkus.http:quarkus-http-http-core:jar:3.0.1.Final:compile
[INFO]    +- io.quarkus.http:quarkus-http-core:jar:3.0.1.Final:compile
[INFO]    |  \- io.netty:netty-codec-http:jar:4.1.42.Final:compile
[INFO]    |     \- io.netty:netty-codec:jar:4.1.42.Final:compile
[INFO]    \- org.eclipse.microprofile.context-propagation:microprofile-context-propagation-api:jar:1.0.1:com

アプリケーションの起動時には、ExtensionとしてServletが認識されています。

2020-01-25 19:18:56,652 INFO  [io.quarkus] (main) Installed features: [cdi, resteasy, resteasy-jackson, servlet]

起動時に、XNIOのログも出力されず、スレッドダンプを取ったりするとNettyが現れるので、完全にNettyベースになったんですねぇ。

"vert.x-acceptor-thread-0" #31 prio=5 os_prio=0 tid=0x00007f46780a2800 nid=0x4722 runnable [0x00007f46a2dee000]
   java.lang.Thread.State: RUNNABLE
    at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)
    at sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.java:269)
    at sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:93)
    at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:86)
    - locked <0x00000006c6c26e60> (a io.netty.channel.nio.SelectedSelectionKeySet)
    - locked <0x00000006c6c27f60> (a java.util.Collections$UnmodifiableSet)
    - locked <0x00000006c6c27e88> (a sun.nio.ch.EPollSelectorImpl)
    at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:97)
    at io.netty.channel.nio.SelectedSelectionKeySetSelector.select(SelectedSelectionKeySetSelector.java:62)
    at io.netty.channel.nio.NioEventLoop.select(NioEventLoop.java:824)
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:457)
    at io.netty.util.concurrent.SingleThreadEventExecutor$6.run(SingleThreadEventExecutor.java:1044)
    at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
    at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
    at java.lang.Thread.run(Thread.java:748)

"vert.x-eventloop-thread-15" #30 prio=5 os_prio=0 tid=0x00007f4711177800 nid=0x4721 runnable [0x00007f46a30ef000]
   java.lang.Thread.State: RUNNABLE
    at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)
    at sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.java:269)
    at sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:93)
    at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:86)
    - locked <0x00000006c6c06978> (a io.netty.channel.nio.SelectedSelectionKeySet)
    - locked <0x00000006c6c07a98> (a java.util.Collections$UnmodifiableSet)
    - locked <0x00000006c6c079a0> (a sun.nio.ch.EPollSelectorImpl)
    at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:97)
    at io.netty.channel.nio.SelectedSelectionKeySetSelector.select(SelectedSelectionKeySetSelector.java:62)
    at io.netty.channel.nio.NioEventLoop.select(NioEventLoop.java:824)
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:457)
    at io.netty.util.concurrent.SingleThreadEventExecutor$6.run(SingleThreadEventExecutor.java:1044)
    at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
    at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
    at java.lang.Thread.run(Thread.java:748)


〜省略〜

"vert.x-eventloop-thread-0" #15 prio=5 os_prio=0 tid=0x00007f471115e000 nid=0x4712 runnable [0x00007f46a3ffe000]
   java.lang.Thread.State: RUNNABLE
    at sun.nio.ch.EPollArrayWrapper.epollWait(Native Method)
    at sun.nio.ch.EPollArrayWrapper.poll(EPollArrayWrapper.java:269)
    at sun.nio.ch.EPollSelectorImpl.doSelect(EPollSelectorImpl.java:93)
    at sun.nio.ch.SelectorImpl.lockAndDoSelect(SelectorImpl.java:86)
    - locked <0x00000006c6ce4040> (a io.netty.channel.nio.SelectedSelectionKeySet)
    - locked <0x00000006c6ce5150> (a java.util.Collections$UnmodifiableSet)
    - locked <0x00000006c6ce5068> (a sun.nio.ch.EPollSelectorImpl)
    at sun.nio.ch.SelectorImpl.select(SelectorImpl.java:97)
    at io.netty.channel.nio.SelectedSelectionKeySetSelector.select(SelectedSelectionKeySetSelector.java:62)
    at io.netty.channel.nio.NioEventLoop.select(NioEventLoop.java:824)
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:457)
    at io.netty.util.concurrent.SingleThreadEventExecutor$6.run(SingleThreadEventExecutor.java:1044)
    at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
    at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
    at java.lang.Thread.run(Thread.java:748)

"vertx-blocked-thread-checker" #13 daemon prio=5 os_prio=0 tid=0x00007f4710e52000 nid=0x4711 in Object.wait() [0x00007f46ac3db000]
   java.lang.Thread.State: TIMED_WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <0x00000006c6ce6148> (a java.util.TaskQueue)
    at java.util.TimerThread.mainLoop(Timer.java:552)
    - locked <0x00000006c6ce6148> (a java.util.TaskQueue)
    at java.util.TimerThread.run(Timer.java:505)

〜省略〜

先ほど作成したJAX-RSリソースクラスを使って、スタックトレースを出力してみます。

2020-01-25 19:18:58,459 INFO  [org.lit.qua.res.HttpRequestResource] (executor-thread-1) Thread[executor-thread-1 / org.jboss.threads.JBossThread]
java.lang.Exception: Stack trace
    at java.lang.Thread.dumpStack(Thread.java:1336)
    at org.littlewings.quarkus.resteasy.HttpRequestResource.printThreadDump(HttpRequestResource.java:38)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:151)
    at org.jboss.resteasy.core.MethodInjectorImpl.lambda$invoke$3(MethodInjectorImpl.java:122)
    at java.util.concurrent.CompletableFuture.uniApply(CompletableFuture.java:616)
    at java.util.concurrent.CompletableFuture.uniApplyStage(CompletableFuture.java:628)
    at java.util.concurrent.CompletableFuture.thenApply(CompletableFuture.java:1996)
    at java.util.concurrent.CompletableFuture.thenApply(CompletableFuture.java:110)
    at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:122)
    at org.jboss.resteasy.core.ResourceMethodInvoker.internalInvokeOnTarget(ResourceMethodInvoker.java:594)
    at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTargetAfterFilter(ResourceMethodInvoker.java:468)
    at org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invokeOnTarget$2(ResourceMethodInvoker.java:421)
    at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:363)
    at org.jboss.resteasy.core.ResourceMethodInvoker.invokeOnTarget(ResourceMethodInvoker.java:423)
    at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:391)
    at org.jboss.resteasy.core.ResourceMethodInvoker.lambda$invoke$1(ResourceMethodInvoker.java:365)
    at java.util.concurrent.CompletableFuture.uniComposeStage(CompletableFuture.java:995)
    at java.util.concurrent.CompletableFuture.thenCompose(CompletableFuture.java:2137)
    at java.util.concurrent.CompletableFuture.thenCompose(CompletableFuture.java:110)
    at org.jboss.resteasy.core.ResourceMethodInvoker.invoke(ResourceMethodInvoker.java:365)
    at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:477)
    at org.jboss.resteasy.core.SynchronousDispatcher.lambda$invoke$4(SynchronousDispatcher.java:252)
    at org.jboss.resteasy.core.SynchronousDispatcher.lambda$preprocess$0(SynchronousDispatcher.java:153)
    at org.jboss.resteasy.core.interception.jaxrs.PreMatchContainerRequestContext.filter(PreMatchContainerRequestContext.java:363)
    at org.jboss.resteasy.core.SynchronousDispatcher.preprocess(SynchronousDispatcher.java:156)
    at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:238)
    at org.jboss.resteasy.plugins.server.servlet.ServletContainerDispatcher.service(ServletContainerDispatcher.java:249)
    at io.quarkus.resteasy.runtime.ResteasyFilter$ResteasyResponseWrapper.sendError(ResteasyFilter.java:65)
    at io.undertow.servlet.handlers.DefaultServlet.doGet(DefaultServlet.java:172)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:503)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:590)
    at io.undertow.servlet.handlers.ServletHandler.handleRequest(ServletHandler.java:74)
    at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:129)
    at io.quarkus.resteasy.runtime.ResteasyFilter.doFilter(ResteasyFilter.java:28)
    at io.undertow.servlet.core.ManagedFilter.doFilter(ManagedFilter.java:61)
    at io.undertow.servlet.handlers.FilterHandler$FilterChainImpl.doFilter(FilterHandler.java:131)
    at io.undertow.servlet.handlers.FilterHandler.handleRequest(FilterHandler.java:84)
    at io.undertow.servlet.handlers.security.ServletSecurityRoleHandler.handleRequest(ServletSecurityRoleHandler.java:63)
    at io.undertow.servlet.handlers.ServletChain$1.handleRequest(ServletChain.java:68)
    at io.undertow.servlet.handlers.ServletDispatchingHandler.handleRequest(ServletDispatchingHandler.java:36)
    at io.undertow.servlet.handlers.security.SSLInformationAssociationHandler.handleRequest(SSLInformationAssociationHandler.java:133)
    at io.undertow.servlet.handlers.security.ServletAuthenticationCallHandler.handleRequest(ServletAuthenticationCallHandler.java:57)
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at io.undertow.security.handlers.AbstractConfidentialityHandler.handleRequest(AbstractConfidentialityHandler.java:46)
    at io.undertow.servlet.handlers.security.ServletConfidentialityConstraintHandler.handleRequest(ServletConfidentialityConstraintHandler.java:65)
    at io.undertow.security.handlers.AuthenticationMechanismsHandler.handleRequest(AuthenticationMechanismsHandler.java:60)
    at io.undertow.servlet.handlers.security.CachedAuthenticatedSessionHandler.handleRequest(CachedAuthenticatedSessionHandler.java:77)
    at io.undertow.security.handlers.NotificationReceiverHandler.handleRequest(NotificationReceiverHandler.java:50)
    at io.undertow.security.handlers.AbstractSecurityContextAssociationHandler.handleRequest(AbstractSecurityContextAssociationHandler.java:43)
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at io.undertow.server.handlers.PredicateHandler.handleRequest(PredicateHandler.java:43)
    at io.undertow.servlet.handlers.ServletInitialHandler.handleFirstRequest(ServletInitialHandler.java:270)
    at io.undertow.servlet.handlers.ServletInitialHandler.access$100(ServletInitialHandler.java:59)
    at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:116)
    at io.undertow.servlet.handlers.ServletInitialHandler$2.call(ServletInitialHandler.java:113)
    at io.undertow.servlet.core.ServletRequestContextThreadSetupAction$1.call(ServletRequestContextThreadSetupAction.java:48)
    at io.undertow.servlet.core.ContextClassLoaderSetupAction$1.call(ContextClassLoaderSetupAction.java:43)
    at io.quarkus.undertow.runtime.UndertowDeploymentRecorder$9$1$1.call(UndertowDeploymentRecorder.java:482)
    at io.undertow.servlet.handlers.ServletInitialHandler.dispatchRequest(ServletInitialHandler.java:250)
    at io.undertow.servlet.handlers.ServletInitialHandler.access$000(ServletInitialHandler.java:59)
    at io.undertow.servlet.handlers.ServletInitialHandler$1.handleRequest(ServletInitialHandler.java:82)
    at io.undertow.server.Connectors.executeRootHandler(Connectors.java:290)
    at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:669)
    at org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35)
    at org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:2011)
    at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1535)
    at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1395)
    at org.jboss.threads.DelegatingRunnable.run(DelegatingRunnable.java:29)
    at org.jboss.threads.ThreadLocalResettingRunnable.run(ThreadLocalResettingRunnable.java:29)
    at java.lang.Thread.run(Thread.java:748)
    at org.jboss.threads.JBossThread.run(JBossThread.java:479)

ここでは、がっつりとUndertowのAPIが登場します。

この状態だと、Servlet APIが利用可能になります。

    @GET
    @Path("print-servlet-request")
    @Produces(MediaType.APPLICATION_JSON)
    public Map<String, Object> printServletRequest(@Context HttpServletRequest request) {
        Map<String, Object> response = new LinkedHashMap<>();

        response.put("method", request.getMethod());
        response.put("request-url", request.getRequestURL());
        response.put("request-uri", request.getRequestURI());
        response.put("query", request.getQueryString());

        return response;
    }

確認。

$ curl -s localhost:8080/http-request/print-servlet-request?param=hoge | jq
{
  "method": "GET",
  "request-url": "http://localhost:8080/http-request/print-servlet-request",
  "request-uri": "/http-request/print-servlet-request",
  "query": "param=hoge"
}

メソッド追加後のコードは、こちら。
src/main/java/org/littlewings/quarkus/resteasy/HttpRequestResource.java

package org.littlewings.quarkus.resteasy;

import java.util.LinkedHashMap;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;

import org.jboss.logging.Logger;
import org.jboss.resteasy.spi.HttpRequest;

@Path("http-request")
public class HttpRequestResource {
    Logger logger = Logger.getLogger(HttpRequestResource.class);

    @GET
    @Path("print-request")
    @Produces(MediaType.APPLICATION_JSON)
    public Map<String, Object> printRequest(@Context HttpRequest request) {
        Map<String, Object> response = new LinkedHashMap<>();

        response.put("method", request.getHttpMethod());
        response.put("request-uri", request.getUri().getRequestUri().toString());
        response.put("path", request.getUri().getPath());
        response.put("query", request.getUri().getRequestUri().getQuery());

        return response;
    }

    @GET
    @Path("thread-dump")
    @Produces(MediaType.TEXT_PLAIN)
    public String printThreadDump() {
        logger.infof("Thread[%s / %s]", Thread.currentThread().getName(), Thread.currentThread().getClass().getName());

        Thread.dumpStack();

        return "print Thread Dump";
    }

    @GET
    @Path("print-servlet-request")
    @Produces(MediaType.APPLICATION_JSON)
    public Map<String, Object> printServletRequest(@Context HttpServletRequest request) {
        Map<String, Object> response = new LinkedHashMap<>();

        response.put("method", request.getMethod());
        response.put("request-url", request.getRequestURL());
        response.put("request-uri", request.getRequestURI());
        response.put("query", request.getQueryString());

        return response;
    }
}

ところでですね、依存関係にUndertowの名前がほとんど登場しなかったのに、スレッドダンプを見たらいきなり出てきたのがちょっと
気になります。

どうやら、こちらが実体のようですね。

GitHub - quarkusio/quarkus-http

A Vert.x based Servlet implementation.

らしいです。

こちらに置かれているコードのパッケージは、「io.undertow」となっていますし。

とりあえず、見たかったものはざっくりと見れたので、ここまで。

Moleculeで使うインスタンスを、Vagrantで管理する

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

MoleculeでAnsibleのRoleをテストする時のDriverとして、Dockerではなく仮想マシン…というかVagrantが使えそうだったので
1度試してみることにしました。

まあ、いろいろありそうなのですが…。

Molecule Vagrant Driver

ドキュメントを見ると、以下のようにVagrant用のDriverが存在することが確認できます。

Configuration / Vagrant

すぐ近くにいろいろ注意書きがあります。

This driver is alpha quality software. Do not perform any additonal tasks inside the create playbook. Molecule does not know about the Vagrant instances’ configuration until the converge playbook is executed.

α品質だよとか、改善が必要な箇所があるとか書いてあるのですが…。

他にも書いておくことがあるのですが、それは最後にします。

まずは、動かしてみましょう。

環境

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

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 18.04.3 LTS
Release:    18.04
Codename:   bionic


$ vagrant version
Installed Version: 2.2.6
Latest Version: 2.2.6


$ vagrant plugin list
vagrant-libvirt (0.0.45, global)


$ python3 -V
Python 3.6.9

Ubuntu Linux 18.04 LTSで、Vagrantは2.2.6、VagrantのProviderにはlibvirtを使います。

Molecure、Vagrant Driverのインストール

では、Moleculeと、Vagrant用のDriverをインストールします。

$ pip3 install molecule
$ pip3 install molecule[vagrant]

バージョンは、こちらです。

$ pip3 freeze | grep -E 'ansible|molecule|vagrant'
ansible==2.9.3
ansible-lint==4.2.0
molecule==2.22
python-vagrant==0.5.15

RoleとMolecule関連ファイルの作成、動作確認まで

「molecule init role」で、Roleを作成しましょう。お題としては、Apacheをインストールしてsystemdを使って起動するRoleとします。

$ molecule init role -r apache --driver-name vagrant

この時に、「--driver-name」で「vagrant」を指定します。

作成されたRoleのディレクトリ内に移動します。

$ cd apache

作成されたファイルを見てみましょう。

$ find -type f
./handlers/main.yml
./README.md
./vars/main.yml
./meta/main.yml
./defaults/main.yml
./tasks/main.yml
./molecule/default/prepare.yml
./molecule/default/playbook.yml
./molecule/default/tests/__pycache__/test_default.cpython-36.pyc
./molecule/default/tests/test_default.py
./molecule/default/molecule.yml
./molecule/default/INSTALL.rst
./.yamllint

Docker Driverの時と、ちょっと構成が違います。

Ansible Roleを開発、テストするためのMoleculeを試す - CLOVER🍀

「prepare.yml」というファイルがあるので、中身を見てみます。
molecule/default/prepare.yml

---
- name: Prepare
  hosts: all
  gather_facts: false
  tasks:
    - name: Install python for Ansible
      raw: test -e /usr/bin/python || (apt -y update && apt install -y python-minimal)
      become: true
      changed_when: false

Pythonをインストールするようですね。

「molecule.yml」を見ると、Driverの指定がVagrantになっています。
molecule/default/molecule.yml

---
dependency:
  name: galaxy
driver:
  name: vagrant
  provider:
    name: virtualbox
lint:
  name: yamllint
platforms:
  - name: instance
    box: ubuntu/xenial64
provisioner:
  name: ansible
  lint:
    name: ansible-lint
verifier:
  name: testinfra
  lint:
    name: flake8

Playbookは、ふつうです。
molecule/default/playbook.yml

---
- name: Converge
  hosts: all
  roles:
    - role: apache

ここからは、今回やりたいことに合わせてファイルを修正していきます。

「molecule.yml」では、まずVagrantのProviderをlibvirtにします。

driver:
  name: vagrant
  provider:
    name: libvirt

VagrantのBoxは、「generic/ubuntu1804」とします。

platforms:
  - name: instance
    box: generic/ubuntu1804

この状態で、「molecule create」。

$ molecule create

インスタンスを見てみます。

$ molecule list
--> Validating schema /path/to/apache/molecule/default/molecule.yml.
Validation completed successfully.
Instance Name    Driver Name    Provisioner Name    Scenario Name    Created    Converged
---------------  -------------  ------------------  ---------------  ---------  -----------
instance         vagrant        ansible             default          true       false

もちろん、「molecule login」で起動したインスタンスにログインすることができます。

$ molecule login
--> Validating schema /path/to/apache/molecule/default/molecule.yml.
Validation completed successfully.
Warning: Permanently added '192.168.121.7' (ECDSA) to the list of known hosts.
Last login: Mon Jan 20 14:48:59 2020 from 192.168.121.1

確認できたら、1度、インスタンスから出ます。

次に、Roleを作成しましょう。せっかく仮想マシンを使うので、aptでApacheをインストールするだけでなく、systemctlを使って
Apacheを起動させます。 tasks/main.yml

---
- name: install apache2
  become: yes
  apt:
    name: apache2
    state: present
- name: start apache2
  become: yes
  systemd:
    state: started
    name: apache2

「molecule converge」を実行。

$ molecule converge

完了後、インスタンスにログインすると、Apacheが動作していることが確認できます。

$ molecule login


$ curl -I localhost
HTTP/1.1 200 OK
Date: Mon, 20 Jan 2020 14:54:08 GMT
Server: Apache/2.4.29 (Ubuntu)
Last-Modified: Mon, 20 Jan 2020 14:53:18 GMT
ETag: "2aa6-59c9373ab6eac"
Accept-Ranges: bytes
Content-Length: 10918
Vary: Accept-Encoding
Content-Type: text/html

テストもしてみましょう。
molecule/default/tests/test_default.py

import os

import testinfra.utils.ansible_runner

testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner(
    os.environ['MOLECULE_INVENTORY_FILE']
).get_hosts('all')


def test_apache_is_installed(host):
    apache2 = host.package('apache2')
    assert apache2.is_installed


def test_apache_is_running_and_enabled(host):
    apache2 = host.service('apache2')
    assert apache2.is_running
    assert apache2.is_enabled

Apacheがインストールされていることと、サービスが実行・有効化されていることを確認します。

「molecule verify」でテスト実行。

$ molecule verify
--> Scenario: 'default'
--> Action: 'verify'
--> Executing Testinfra tests found in /path/to/apache/molecule/default/tests/...
    ============================= test session starts ==============================
    platform linux -- Python 3.6.9, pytest-5.3.3, py-1.8.1, pluggy-0.13.1
    rootdir: /path/to/apache/molecule/default
    plugins: testinfra-3.4.0
collected 2 items                                                              
    
    tests/test_default.py ..                                                 [100%]
    
    ============================== 2 passed in 5.92s ===============================

OKです。

インスタンスを破棄。

$ molecule destroy

最後に、ここまでまとめて「molecule test」で確認します。自動生成された状態の「meta/main.yml」は、LintでNGになるので修正
しておいてください。

$ molecule test

インスタンスの作成からPlaybookの実行、インスタンスの破棄までが確認できます。

Vagrant Driverはなくなる?

Vagrant Driver、それからドキュメントに記載のある、Molecule Vagrant Moduleあたりが気になっていろいろ見ていると、こんな
情報を見つけました。

plugins: externalize vagrant driver · Issue #2315 · ansible/molecule · GitHub

Removed migration and vagrant driver by ssbarnea · Pull Request #2414 · ansible/molecule · GitHub

vagrant rpms are not published in a repository · Issue #11070 · hashicorp/vagrant · GitHub

どうやら、Vagrant DriverはMoleculeからいなくなりそうですね。yum/dnfリポジトリがないから、メンテナンスが大変だということ
みたいです。

リポジトリのIssueを見ても、メンテナンスしてくれる人を探している感じです。

GitHub - ansible-community/molecule-vagrant: Molecule Vagrant Driver

looking for maintainers · Issue #2 · ansible-community/molecule-vagrant · GitHub

ローカルで、コンテナではなく仮想マシンを使いたい場合はどうするんでしょうね?libvirtになるのでしょうか?

GitHub - ansible-community/molecule-libvirt: Molecule LibVirt Provider

とりあえず、直近は(なくなる想定であっても)Vagrantかなぁと思います。