CLOVER🍀

That was when it all began.

Quarkus HTTPでServletを試す

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

QuarkusのHTTPに関する処理は、Quarkus HTTPという独立したリポジトリで作られています。

GitHub - quarkusio/quarkus-http

今回、こちらで少し遊んでみようかな、と。題材はServletです。

Quarkus HTTP

Quarkus HTTPのリポジトリは、こちら。

GitHub - quarkusio/quarkus-http

リポジトリの説明には、こう書かれています。

A Vert.x based Servlet implementation.

Vert.xをベースにしたServletの実装、ということみたいです。

いくつかのモジュールがあるようです。

サンプルもついています。

quarkus-http/examples at 4.1.7 · quarkusio/quarkus-http · GitHub

中を見るとわかるのですが、リポジトリ内の各パッケージはio.undertowから始まるものになっています。

https://github.com/quarkusio/quarkus-http/blob/4.1.7/core/src/main/java/io/undertow/Undertow.java

このリポジトリはUndertow 3.xとなる予定だったもののようなのですが、それが明言されたissue等は見当たりませんでした。

とはいえ、Undertowは3以降ではトランスポート層をXNIOからNettyに移すことは決めていたようなので、最終的にはVert.xに
なったということなのでしょうね。

The Java network programming world has come a long way since Undertow was first started. Netty has emerged as the de-facto standard for network programming in Java, and the Undertow team has decided that the benefits of utilizing Netty outweigh any benefits in keeping our XNIO based transport layer.

Undertow 3.0 will keep Undertow’s programming model, however the underlying transport will be changed from XNIO to Netty. We will also use the Netty HTTP/1, HTTP/2 and Websocket implementations.

Undertow 3.0 Announcement · JBoss Community

そして、こちらのモジュールがQuarkusのUndertow Extension(Servletを使う時のExtension)で使われています。

https://github.com/quarkusio/quarkus-http/tree/4.1.7/servlet

        <dependency>
            <groupId>io.quarkus.http</groupId>
            <artifactId>quarkus-http-servlet</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.jboss.spec.javax.servlet</groupId>
                    <artifactId>jboss-servlet-api_4.0_spec</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.jboss.spec.javax.annotation</groupId>
                    <artifactId>jboss-annotations-api_1.2_spec</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

quarkus/pom.xml at 2.7.2.Final · quarkusio/quarkus · GitHub

なお、Undertow ExtensionはQuarkusを利用する時であっても、単純にRESTEasy Extensionを使うだけでは利用されません。
この場合は、Vert.xまわりのExtensionを使って駆動されます。

https://github.com/quarkusio/quarkus/tree/2.7.2.Final/extensions/vertx-http/runtime

あくまで、Undertow Extension(quarkus-undertow)を明示的に追加した場合に利用されるものです。
そのうちWildFlyとかでも使われたりするんでしょうかね?

今回は、Quarkus HTTPのServletを簡単に試してみようと思います。

環境

今回の環境は、こちら。

$ java --version
openjdk 17.0.1 2021-10-19
OpenJDK Runtime Environment (build 17.0.1+12-Ubuntu-120.04)
OpenJDK 64-Bit Server VM (build 17.0.1+12-Ubuntu-120.04, mixed mode, sharing)


$ mvn --version
Apache Maven 3.8.4 (9b656c72d54e5bacbed989b64718c159fe39b537)
Maven home: $HOME/.sdkman/candidates/maven/current
Java version: 17.0.1, 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-100-generic", arch: "amd64", family: "unix"

準備

Maven依存関係は、こちら。

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

quarkus-http-servletがQuarkus HTTPのServlet実装なのですが、これを駆動するためのランタイムが必要です。
具体的には、UndertowEngineインターフェースの実装が要求されます。

https://github.com/quarkusio/quarkus-http/blob/4.1.7/http-core/src/main/java/io/undertow/httpcore/UndertowEngine.java

現在、Quarkus HTTPの中にはUndertowEngineインターフェースの実装を提供しているのはquarkus-http-vertx-backendのみで、
こちらをquarkus-http-servletとは別に追加します。

https://github.com/quarkusio/quarkus-http/blob/4.1.7/vertx/src/main/resources/META-INF/services/io.undertow.httpcore.UndertowEngine

これで、Quarkus HTTPが提供するServletが利用できるようになります。

なお、現時点のQuarkus HTTPのServlet実装が提供する、Jakarta Servlet仕様のバージョンは4です。

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

Quarkus HTTP Servletを動かす

それでは、さっそくプログラムを書いていきます。

といっても、Undertow 2.xの頃の例がそのまま動きます。

Undertow Servlet / Creating a Servlet Deployment

あとで気づきましたが、Quarkus HTTPのリポジトリにあるexampleにこのドキュメントとまったく同じ例がありました。

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

それはさておき、自分でも簡単なプログラムを書いてみます。

Servlet

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

package org.littlewings.quarkus.http;

import java.io.IOException;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class HelloServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
        String message = Optional.ofNullable(req.getParameter("message")).orElse("World");
        res.getWriter().write(String.format("Hello %s!!", message));
    }
}

mainクラス。

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

package org.littlewings.quarkus.http;

import javax.servlet.ServletException;

import io.undertow.Handlers;
import io.undertow.Undertow;
import io.undertow.server.handlers.PathHandler;
import io.undertow.servlet.Servlets;
import io.undertow.servlet.api.DeploymentInfo;
import io.undertow.servlet.api.DeploymentManager;
import org.jboss.logging.Logger;

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

        DeploymentInfo servletBuilder =
                Servlets
                        .deployment()
                        .setClassLoader(App.class.getClassLoader())
                        .setContextPath("/")
                        .setDeploymentName("myapp")
                        .addServlets(
                                Servlets
                                        .servlet("HelloServlert", HelloServlet.class)
                                        .addMapping("/hello")
                        );

        DeploymentManager manager =
                Servlets
                        .defaultContainer()
                        .addDeployment(servletBuilder);
        manager.deploy();

        PathHandler handler =
                Handlers
                        .path(Handlers.redirect("/"))
                        .addPrefixPath("/", manager.start());

        Undertow server =
                Undertow
                        .builder()
                        .addHttpListener(8080, "0.0.0.0")
                        .setHandler(handler)
                        .build();

        server.start();

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

起動。

$ mvn compile exec:java -Dexec.mainClass=org.littlewings.quarkus.http.App

確認。

$ curl localhost:8080/hello
Hello World!!


$ curl localhost:8080/hello?message=Quarkus
Hello Quarkus!!

OKそうですね。

オマケ

見た目はUndertow 2.xを使っている時とまったく変わらないので、Quarkus HTTPを使っている感じがしません。

実際、以前のUndertow 3.xについて書いているブログにも、Servlet APIを使っている分には変化には気づかないかもと書かれて
いますしね。

If you are using the Servlet API then you will likely not notice any change. You will need some different dependencies (Netty instead of XNIO), however the rest of the experience should be mostly identical If you are using the low level Undertow HttpHandler and HttpServerExchange then you will need to migrate your application. For the most part this migration should be straightforward, as most concepts from the old API directly map to the new API.

Undertow 3.0 Announcement · JBoss Community

なお、スレッドまわりについては接続の受け付けはVert.xのイベントループスレッド(IOスレッド)ですが、リクエストを処理するのは
ワーカースレッド(ブロッキング処理用のスレッド)になります。

https://github.com/quarkusio/quarkus-http/blob/4.1.7/core/src/main/java/io/undertow/Undertow.java#L135-L138

https://github.com/quarkusio/quarkus-http/blob/4.1.7/vertx/src/main/java/io/undertow/vertx/VertxUndertowEngine.java#L27

https://github.com/quarkusio/quarkus-http/blob/4.1.7/vertx/src/main/java/io/undertow/vertx/VertxUndertowEngine.java#L146

ワーカースレッドは、デフォルトだとworkerThreadsで指定された数でExecutors#newFixedThreadPoolで作成された
スレッドプールが利用されます。

まとめ

Quarkus HTTPでServletを使ってみました。ほぼ、Undertow 2.xのままでしたね。

ここまで変わらないようだと、この上でRESTEasyを動かしたりしたくなるものですが…。
※RESTEasy Reactiveを動かすのは、ちょっと難しそうです…