CLOVER🍀

That was when it all began.

Quarkus HTTPの上でRESTEasyを動かす

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

前に、Quarkus HTTPを使ってServletを動かすエントリーを書いてみました。

Quarkus HTTPでServletを試す - CLOVER🍀

Quarkus HTTPは使い方がUndertowと変わらないので、RESTEasyもそのまま動かせるのでは?ということで遊んでみました。

完全にネタです。

Quarkus HTTP上でRESTEasyを動かす

Quarkus HTTPは、Vert.xをベースにしたServletの実装です。

GitHub - quarkusio/quarkus-http

サンプルを見るとわかりますが、見た目はUndertowそのものです。

https://github.com/quarkusio/quarkus-http/tree/4.1.8/examples/src/main/java/io/undertow/examples/servlet

一方、RESTEasyにはUndertow向けのServer Adapterがあります。

Jakarta RESTFul Web Services / Embedded Containers / Undertow

依存しているJakarta Servletのバージョンは、RESTEasy、Quarkus HTTPともに4.0.4です。

https://github.com/resteasy/resteasy/blob/5.0.2.Final/resteasy-dependencies-bom/pom.xml#L70

https://github.com/quarkusio/quarkus-http/blob/4.1.8/pom.xml#L76

Jakarta Servletのバージョン的には、RESTEasy 5.0で良さそうです。

また、RESTEasyが依存するUndertowのバージョンは2.2.7.Finalです。

https://github.com/resteasy/resteasy/blob/5.0.2.Final/resteasy-dependencies-bom/pom.xml#L32

Undertowについては、RESTEasy Undertow Adapterの依存関係に含まれているのでexcludeする必要があります。

ちなみに、この方法でやるくらいなら素直にVert.xやNetty 4の上で動かした方がいいような気はします。

Jakarta RESTFul Web Services / Embedded Containers / Vert.x

Jakarta RESTFul Web Services / Embedded Containers / Netty

Jakarta Servletも不要になりますし。

Vert.xを使う場合は4.1.0に依存していますが、Vert.xの場合は依存関係がprovided(利用者側で明示的に依存関係に追加する必要がある)に
なっています。

https://github.com/resteasy/resteasy/blob/5.0.2.Final/resteasy-dependencies-bom/pom.xml#L31

Netty 4の場合は、依存関係に含まれます。

とりあえず、今回はRESTEasyをQuarkus HTTP上で動かし、加えてJackson 2 Providerも入れて動かしてみましょう。

環境

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

$ java --version
openjdk 17.0.2 2022-01-18
OpenJDK Runtime Environment (build 17.0.2+8-Ubuntu-120.04)
OpenJDK 64-Bit Server VM (build 17.0.2+8-Ubuntu-120.04, mixed mode, sharing)


$ mvn --version
Apache Maven 3.8.5 (3599d3414f046de2324203b78ddcf9b5e4388aa0)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 17.0.2, vendor: Private Build, runtime: /usr/lib/jvm/java-17-openjdk-amd64
Default locale: ja_JP, platform encoding: UTF-8
OS name: "linux", version: "5.4.0-105-generic", arch: "amd64", family: "unix"

準備

Maven依存関係等は、こちら。

    <dependencies>
        <dependency>
            <groupId>io.quarkus.http</groupId>
            <artifactId>quarkus-http-servlet</artifactId>
            <version>4.1.8</version>
        </dependency>
        <dependency>
            <groupId>io.quarkus.http</groupId>
            <artifactId>quarkus-http-vertx-backend</artifactId>
            <version>4.1.8</version>
        </dependency>
        <dependency>
            <groupId>org.jboss.resteasy</groupId>
            <artifactId>resteasy-undertow</artifactId>
            <version>5.0.2.Final</version>
            <exclusions>
                <exclusion>
                    <groupId>io.undertow</groupId>
                    <artifactId>undertow-servlet</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>io.undertow</groupId>
                    <artifactId>undertow-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.jboss.resteasy</groupId>
            <artifactId>resteasy-jackson2-provider</artifactId>
            <version>5.0.2.Final</version>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.8.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.rest-assured</groupId>
            <artifactId>rest-assured</artifactId>
            <version>4.5.1</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>2.22.2</version>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.6.5</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

Quakus HTTPはこちらですね。

        <dependency>
            <groupId>io.quarkus.http</groupId>
            <artifactId>quarkus-http-servlet</artifactId>
            <version>4.1.8</version>
        </dependency>
        <dependency>
            <groupId>io.quarkus.http</groupId>
            <artifactId>quarkus-http-vertx-backend</artifactId>
            <version>4.1.8</version>
        </dependency>

resteasy-undertowからUndertowに関するアーティファクトをexcludeし、Jackson 2 Providerも足しておきます。

        <dependency>
            <groupId>org.jboss.resteasy</groupId>
            <artifactId>resteasy-undertow</artifactId>
            <version>5.0.2.Final</version>
            <exclusions>
                <exclusion>
                    <groupId>io.undertow</groupId>
                    <artifactId>undertow-servlet</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>io.undertow</groupId>
                    <artifactId>undertow-core</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.jboss.resteasy</groupId>
            <artifactId>resteasy-jackson2-provider</artifactId>
            <version>5.0.2.Final</version>
        </dependency>

Spring Boot Maven Pluginが入っているのは、Uber JARを作るために入れています。

プログラムを作成する

では、JAX-RSとQuarkus HTTPを使ってプログラムを作成します。

JAX-RSリソースクラス。

src/main/java/org/littlewings/quarkus/http/EchoResource.java

package org.littlewings.quarkus.http;

import java.util.Map;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("echo")
public class EchoResource {
    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    @Produces(MediaType.APPLICATION_JSON)
    public Map<String, String> message(Map<String, String> request) {
        String message = request.getOrDefault("message", "World");
        return Map.of("replyMessage", "Hello " + message + "!!");
    }
}

Applicationのサブクラス。

src/main/java/org/littlewings/quarkus/http/JaxrsActivator.java

package org.littlewings.quarkus.http;

import java.util.Set;
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("")
public class JaxrsActivator extends Application {
    @Override
    public Set<Object> getSingletons() {
        return Set.of(new EchoResource());
    }
}

mainクラス。こちらで、Undertow Adapterを使います。

src/main/java/org/littlewings/quarkus/http/Server.java

package org.littlewings.quarkus.http;


import javax.ws.rs.core.Application;

import io.undertow.Undertow;
import io.undertow.servlet.api.DeploymentInfo;
import org.jboss.logging.Logger;
import org.jboss.resteasy.plugins.server.undertow.UndertowJaxrsServer;

public class Server implements AutoCloseable {
    UndertowJaxrsServer server;

    public static void main(String... args) {
        Logger logger = Logger.getLogger(Server.class);

        int port = 8080;
        String contextPath = "/";

        try (Server server = new Server()) {
            server.start(port, new JaxrsActivator(), contextPath);
            logger.infof("server[port = %d, context-path = %s] startup.", port, contextPath);

            System.console().readLine("Enter stop.");
        }

        logger.info("server stopped.");
    }

    public void start(int port, Application jaxrsActivator, String contextPath) {
        server = new UndertowJaxrsServer();
        server.start(Undertow.builder().addHttpListener(port, "0.0.0.0"));

        DeploymentInfo deployment = server.undertowDeployment(jaxrsActivator.getClass());
        deployment.setContextPath(contextPath);
        deployment.setDeploymentName("app");
        server.deploy(deployment);
    }

    @Override
    public void close() {
        server.stop();
    }
}

停止は、Enterで。

プログラムはこれくらいにしておきます。

パッケージング。

$ mvn package

起動。

$ java -jar target/resteasy-server-0.0.1-SNAPSHOT.jar

ログ。

3月 25, 2022 11:54:35 午後 org.jboss.resteasy.core.ResteasyDeploymentImpl processApplication
INFO: RESTEASY002225: Deploying javax.ws.rs.core.Application: class org.littlewings.quarkus.http.JaxrsActivator
3月 25, 2022 11:54:35 午後 org.jboss.resteasy.core.ResteasyDeploymentImpl processApplication
INFO: RESTEASY002220: Adding singleton resource org.littlewings.quarkus.http.EchoResource from Application class org.littlewings.quarkus.http.JaxrsActivator
3月 25, 2022 11:54:35 午後 org.littlewings.quarkus.http.Server main
INFO: server[port = 8080, context-path = /] startup.
Enter stop.

確認。

$ curl -XPOST -H 'Content-Type: application/json' localhost:8080/echo -d '{"message": "RESTEasy"}'
{"replyMessage":"Hello RESTEasy!!"}

OKですね。

最後に、テストコードも書いておきましょう。REST-assuredを使ったテスト。

src/test/java/org/littlewings/quarkus/http/EchoResourceTest.java

package org.littlewings.quarkus.http;

import java.util.Map;

import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.equalTo;

class EchoResourceTest {
    Server server;
    int port = 8081;
    String contextPath = "/";

    @BeforeEach
    public void setUp() {
        RestAssured.baseURI = "http://localhost";
        RestAssured.port = port;
        RestAssured.basePath = contextPath;

        server = new Server();
        server.start(port, new JaxrsActivator(), contextPath);
    }

    @AfterEach
    public void tearDown() {
        server.close();
    }

    @Test
    public void reply() {
        given()
                .contentType(ContentType.JSON)
                .body(Map.of("message", "RESTEasy"))
                .when()
                .post("echo")
                .then()
                .body("replyMessage", equalTo("Hello RESTEasy!!"));
    }
}

こんな感じで。

まとめ

Quarkus HTTP上で、RESTEasyを動かしてみました。

見かけがほぼUndertowそのままなので勢いでやってみた感じなのですが、あっさりと動きましたね。

まあ、今回は完全にネタです…。