The English version of quarkus.io is the official project site. Translated sites are community supported on a best-effort basis.

RESTEasy Reactive を使用して REST サービスを作成する

このガイドでは、QuarkusでRESTEasy Reactiveを使用してRESTサービスを記述する方法を説明します。

本書は、RESTEasy Reactiveのリファレンスガイドです。よりライトな紹介は、 JSON REST サービスの実装のガイド を参照してください。

RESTEasy Reactiveとは?

RESTEasy Reactive は共通の Vert.x レイヤーで動作するように一から書かれた新しい JAX-RS の実装であり、完全にリアクティブです。また、Quarkus と非常に緊密に統合されており、結果として多くの作業をビルド時に移動させます。

JAX-RSの実装の代わりに使うことができるはずですが、それに加えて、ブロッキングエンドポイントとノンブロッキングエンドポイントの両方で優れたパフォーマンスを発揮し、JAX-RSが提供する機能に加えて多くの新機能を備えています。

エンドポイントの作成

はじめに

次のインポートをビルドファイルに追加します:

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-resteasy-reactive</artifactId>
</dependency>
build.gradle
implementation("io.quarkus:quarkus-resteasy-reactive")

これで、 org.acme.rest.Endpoint クラスで最初のエンドポイントを書くことができます:

package org.acme.rest;

import javax.ws.rs.GET;
import javax.ws.rs.Path;

@Path("")
public class Endpoint {

    @GET
    public String hello() {
        return "Hello, World!";
    }
}

用語解説

REST

REpresentational State Transfer

エンドポイント

REST 呼び出しを処理するために呼び出される Java メソッド

URL / URI (Uniform Resource Locator / Identifier)

REST リソースの場所を特定するために使用 (仕様)

リソース

ドメインオブジェクトを表します。これは、API が提供および変更するものです。JAX-RS ではエンティティーとも呼ばれます。

表現

通信上でのリソースの表現方法は、Content typeによって異なります。

Content type

text/plain や application/json などの特定の表現 (メディアタイプとも呼ばれます) を指定します

HTTP

REST 呼び出しをルーティングするための基本的なワイヤープロトコル (HTTP 仕様 を参照)

HTTP リクエスト

HTTP メソッド、ターゲット URI、ヘッダー、およびオプションのメッセージボディで設定される HTTP 呼び出しのリクエスト部分

HTTP レスポンス

HTTP レスポンスステータス、ヘッダー、およびオプションのメッセージボディで設定される HTTP 呼び出しのレスポンス部分

エンドポイントの宣言:URI マッピング

@Path アノテーションを持つクラスは、HTTP メソッドアノテーション (下記参照) を持っていれば、そのメソッドを REST エンドポイントとして公開することができます。

@Path アノテーションは、これらのメソッドが公開される URI の接頭辞を定義します。これは空であるか、あるいは restrest/V1 のような接頭辞を含むことができます。

公開された各エンドポイントメソッドは、別の @Path アノテーションを持つことができ、それを含むクラスアノテーションを追加されます。例えば、これは rest/hello というエンドポイントを定義しています:

package org.acme.rest;

import javax.ws.rs.GET;
import javax.ws.rs.Path;

@Path("rest")
public class Endpoint {

    @Path("hello")
    @GET
    public String hello() {
        return "Hello, World!";
    }
}

URI マッピングの詳細は、URI パラメーター を参照してください。

以下に示すように、@ApplicationPath アノテーションを使用して、すべての REST エンドポイントのルートパスを設定できます。

package org.acme.rest;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("/api")
public static class MyApplication extends Application {

}

これにより、すべての rest エンドポイントは /api に対する相対パスで解決されるようになり、上記の @Path ("rest") のエンドポイントには /api/rest/ でアクセスすることができるようになります。また、アノテーションを使用したくない場合は、quarkus.resteasy-reactive.path ビルド時のプロパティーを設定して、ルートパスを設定することができます。

エンドポイントの宣言: HTTP メソッド

各エンドポイントメソッドには、次のいずれかのアノテーションを付ける必要があります。これにより、どの HTTP メソッドがそのメソッドにマップされるかが定義されます。

Table 1. テーブル HTTP メソッドのアノテーション
アノテーション 使用方法

@GET

リソース表現を取得します。状態を変更しないでください。idempotent (HTTP docs)

@HEAD

ボディのない GET と同様に、リソースに関するメタデータを取得します (HTTP docs)

@POST

リソースを作成し、そのリソースへのリンクを取得します (HTTP docs)

@PUT

リソースを置き換えるか作成します。idempotent (HTTP docs) であるべきです。

@DELETE

既存のリソースを削除します。idempotent (HTTP docs)

@OPTIONS

リソースに関する情報を取得します。idempotent (HTTP docs) です。

@PATCH

リソースを更新するか、作成します。idempotent (HTTP docs) ではありません。

@HttpMethod アノテーションを付与したアノテーションを宣言することにより、他の HTTP メソッドを宣言することもできます。

package org.acme.rest;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

import javax.ws.rs.HttpMethod;
import javax.ws.rs.Path;

@Retention(RetentionPolicy.RUNTIME)
@HttpMethod("CHEESE")
@interface CHEESE {
}

@Path("")
public class Endpoint {

    @CHEESE
    public String hello() {
        return "Hello, Cheese World!";
    }
}

エンドポイントの宣言: 表現/コンテンツタイプ

各エンドポイントメソッドは、特定のリソース表現を消費したり生成したりすることがあり、それは HTTP Content-Type header で示され、その中に次のような MIME (Media Type) 値が含まれています。

  • String を返すエンドポイントのデフォルトである text/plain

  • HTML の text/html (Qute テンプレート と同様)

  • JSON REST エンドポイントapplication/json

  • 任意のテキストメディアタイプのサブタイプワイルドカードである text/*

  • 任意のメディアタイプのワイルドカードである */*

@Produces または @Consumes アノテーションで、エンドポイントクラスをアノテーションできます。この場合、エンドポイントが HTTP リクエストボディとして受け入れ、HTTP レスポンスボディとして生成できるメディアタイプを一つまたは複数指定することが可能です。これらのクラスアノテーションは、各メソッドに適用されます。

どのメソッドも @Produces または @Consumes アノテーションを付けることができ、その場合は最終的にどのクラスアノテーションもオーバーライドされることになります。

MediaType クラスには、特定の事前定義されたメディアタイプを指すために使用できる多くの定数があります。

詳細は、ネゴシエーション を参照してください。

リクエストパラメーターへのアクセス

パラメーター名の情報を生成するために、-parameters (javac) または <parameters> または <maven.compiler.parameters> (Maven) を使ってコンパイラーを設定することを忘れないでください。

次の HTTP リクエスト要素は、エンドポイントメソッドによって取得される可能性があります。

Table 2. テーブル HTTP リクエストパラメーターアノテーション
HTTP 要素 アノテーション 使用方法

パスパラメーター

リンク:https://javadoc.io/doc/io.quarkus.resteasy.reactive/resteasy-reactive-common/2.14.2.Final/org/jboss/resteasy/reactive/RestPath.html[@RestPath] (または何もない)

URI テンプレートパラメーター (URI Template specification の簡易バージョン)。 詳細は、URI パラメーター を参照してください。

クエリーパラメーター

@RestQuery

URI クエリーパラメーター の値

ヘッダ

@RestHeader

HTTP ヘッダ の値

Cookie

@RestCookie

HTTP cookie の値

フォームパラメーター

@RestForm

HTTP URL-encoded FORM の値

マトリックスパラメーター

@RestMatrix

URI パスセグメントパラメーター の値

これらのアノテーションごとに、参照する要素の名前を指定できます。指定しない場合は、アノテーションが付けられたメソッドパラメーターの名前が使用されます。

クライアントが次の HTTP 呼び出しを行った場合:

POST /cheeses;variant=goat/tomme?age=matured HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Cookie: level=hardcore
X-Cheese-Secret-Handshake: fist-bump

smell=strong

次に、このエンドポイントメソッドを使用してさまざまなパラメーターをすべて取得できます。

package org.acme.rest;

import javax.ws.rs.POST;
import javax.ws.rs.Path;

import org.jboss.resteasy.reactive.RestCookie;
import org.jboss.resteasy.reactive.RestForm;
import org.jboss.resteasy.reactive.RestHeader;
import org.jboss.resteasy.reactive.RestMatrix;
import org.jboss.resteasy.reactive.RestPath;
import org.jboss.resteasy.reactive.RestQuery;

@Path("/cheeses/{type}")
public class Endpoint {

    @POST
    public String allParams(@RestPath String type,
                            @RestMatrix String variant,
                            @RestQuery String age,
                            @RestCookie String level,
                            @RestHeader("X-Cheese-Secret-Handshake")
                            String secretHandshake,
                            @RestForm String smell) {
        return type + "/" + variant + "/" + age + "/" + level + "/"
            + secretHandshake + "/" + smell;
    }
}
The @RestPath annotation is optional: any parameter whose name matches an existing URI template variable will be automatically assumed to have @RestPath.

JAX-RS のアノテーションも使用できます。@PathParam@QueryParam@HeaderParam@CookieParam@FormParam または @MatrixParam。しかし、パラメーター名の指定が必要です。

より高度なユースケース向けについては、パラメーターマッピング を参照してください。

RESTEasy Reactive リクエストパラメータ処理コードで例外が発生した場合、例外はデフォルトでログに出力されません(セキュリティ上の理由による)。 このため、特定の HTTP ステータスコードが返される理由を理解するのが難しい場合があります(JAX-RS では、さまざまなケースで直感的でないエラーコードの使用が義務付けられているため)。 このような場合、ユーザーは次のようにして org.jboss.resteasy.reactive.server.handlers.ParameterHandler カテゴリのロギングレベルを DEBUG に設定することをお勧めします。

quarkus.log.category."org.jboss.resteasy.reactive.server.handlers.ParameterHandler".level=DEBUG

Grouping parameters in a custom class

You can group your request parameters in a container class instead of declaring them as method parameters to you endpoint, so we can rewrite the previous example like this:

package org.acme.rest;

import javax.ws.rs.POST;
import javax.ws.rs.Path;

import org.jboss.resteasy.reactive.RestCookie;
import org.jboss.resteasy.reactive.RestForm;
import org.jboss.resteasy.reactive.RestHeader;
import org.jboss.resteasy.reactive.RestMatrix;
import org.jboss.resteasy.reactive.RestPath;
import org.jboss.resteasy.reactive.RestQuery;

@Path("/cheeses/{type}")
public class Endpoint {

    public static class Parameters {
        @RestPath
        String type;

        @RestMatrix
        String variant;

        @RestQuery
        String age;

        @RestCookie
        String level;

        @RestHeader("X-Cheese-Secret-Handshake")
        String secretHandshake;

        @RestForm
        String smell;
    }

    @POST
    public String allParams(Parameters parameters) {
        return parameters.type + "/" + parameters.variant + "/" + parameters.age
            + "/" + parameters.level + "/" + parameters.secretHandshake
            + "/" + parameters.smell;
    }
}

URI パラメーターの宣言

URI パラメーターを宣言し、パスで正規表現を使用できるため、たとえば、次のエンドポイントは /hello/stef/23/hello のリクエストを処理しますが、/hello/stef/0x23 は処理しません。

package org.acme.rest;

import javax.ws.rs.GET;
import javax.ws.rs.Path;

@Path("hello")
public class Endpoint {

    @Path("{name}/{age:\\d+}")
    @GET
    public String personalisedHello(String name, int age) {
        return "Hello " + name + " is your age really " + age + "?";
    }

    @GET
    public String genericHello() {
        return "Hello stranger";
    }
}

リクエストボディへのアクセス

アノテーションのないメソッドパラメーターは、HTTP 表現からパラメーターの Java 型にマッピングされた後、それが URI テンプレートパラメーター または コンテキストオブジェクト でない限り、メソッドボディのアノテーションを受け取ります。

次のパラメーター型は、そのままでサポートされます。

Table 3. テーブルリクエストボディのパラメーター型
型e 使用方法

File

一時ファイル内のリクエストボディ全体

byte[]

デコードされていないリクエストボディ全体

char[]

デコードされたリクエストボディ全体

String

デコードされたリクエストボディ全体

InputStream

ブロッキングストリームのリクエストボディ

Reader

ブロッキングストリームのリクエストボディ

すべての Java プリミティブとそのラッパークラス

Java プリミティブ型

BigDecimal, BigInteger

大きな整数と小数

JsonArray, JsonObject, JsonStructure, JsonValue

JSON 値の型

Buffer

Vert.x Buffer

他の型

JSON から その型にマップ されます

さらに ボディのパラメータ型 のサポートを追加できます 。

マルチパートフォームデータの処理

To handle HTTP requests that have multipart/form-data as their content type, you can use the regular @RestForm annotation, but we have special types that allow you to access the parts as files or as entities. Let us look at an example of its use.

Assuming an HTTP request containing a file upload, a JSON entity and a form value containing a string description, we could write the following endpoint:

import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.MediaType;

import org.jboss.resteasy.reactive.PartType;
import org.jboss.resteasy.reactive.RestForm;
import org.jboss.resteasy.reactive.multipart.FileUpload;

@Path("multipart")
public class MultipartResource {
    public static class Person {
        public String firstName;
        public String lastName;
    }

    @POST
    public void multipart(@RestForm String description,
            @RestForm("image") FileUpload file,
            @RestForm @PartType(MediaType.APPLICATION_JSON) Person person) {
        // do something
    }
}

The description parameter will contain the data contained in the part of HTTP request called description (because @RestForm does not define a value, the field name is used), while the file parameter will contain data about the uploaded file in the image part of HTTP request, and the person parameter will read the Person entity using the JSON body reader.

マルチパートリクエストの各パートのサイズは、quarkus.http.limits.max-form-attribute-size の値に準拠する必要があります。デフォルトは 2048 バイトです。この設定を超えるパーツサイズのリクエストは、HTTP ステータスコード 413 になります。

FileUpload はアップロードされたファイルの様々なメタデータにアクセスすることができます。しかし、アップロードされたファイルへの処理が必要なだけなら、java.nio.file.Pathjava.io.File を使用することができます。

If you need access to all uploaded files for all parts regardless of their names, you can do it with @RestForm(FileUpload.ALL) List<FileUpload>.

@PartType is used to aid in deserialization of the corresponding part of the request into the desired Java type. It is only required if you need to use a special body parameter type for that particular parameter.
Just like for any other request parameter type, you can also group them into a container class.
ファイルのアップロードを処理する場合、POJO を処理するコード内でファイルを永続的なストレージ (データベース、専用ファイルシステム、クラウドストレージなど) に移動させることが非常に重要です。そうしないと、リクエストが終了したときに、ファイルにアクセスできなくなります。さらに、quarkus.http.body.delete-uploaded-files-on-end を true に設定すると、HTTP レスポンスの送信時に、アップロードされたファイルが削除されます。この設定を無効にすると、ファイルはサーバーのファイルシステム上 (quarkus.http.body.uploads-directory 設定オプションで定義したディレクトリー) に存在しますが、アップロードされたファイルは UUID ファイル名で保存されて追加のメタデータが保存されないため、これらのファイルは本質的にファイルのランダムダンプとなります。

同様に、RESTEasy Reactive は、ユーザーがサーバーからファイルをダウンロードできるようにするための Multipart Form データを生成することができます。例えば、公開したい情報を保持する POJO を、次のように書くことができます。

import javax.ws.rs.core.MediaType;

import org.jboss.resteasy.reactive.PartType;
import org.jboss.resteasy.reactive.RestForm;

public class DownloadFormData {

    @RestForm
    String name;

    @RestForm
    @PartType(MediaType.APPLICATION_OCTET_STREAM)
    File file;
}

そして、次のようなリソースを介してこの POJO を公開します。

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

@Path("multipart")
public class Endpoint {

    @GET
    @Produces(MediaType.MULTIPART_FORM_DATA)
    @Path("file")
    public DownloadFormData getFile() {
        // return something
    }
}

Additionally, you can also manually append the parts of the form using the class MultipartFormDataOutput as:

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import org.jboss.resteasy.reactive.server.core.multipart.MultipartFormDataOutput;

@Path("multipart")
public class Endpoint {

    @GET
    @Produces(MediaType.MULTIPART_FORM_DATA)
    @Path("file")
    public MultipartFormDataOutput getFile() {
        MultipartFormDataOutput form = new MultipartFormDataOutput();
        form.addFormData("person", new Person("John"), MediaType.APPLICATION_JSON_TYPE);
        form.addFormData("status", "a status", MediaType.TEXT_PLAIN_TYPE)
                .getHeaders().putSingle("extra-header", "extra-value");
        return form;
    }
}

This last approach allows you adding extra headers to the output part.

当面、マルチパートデータを返すことはエンドポイントをブロックすることに限定されます。

不正な入力の処理

マルチパートボディの読み取りの一環として、RESTEasy Reactive はリクエストの各部分に対して適切な MessageBodyReader を呼び出します。これらのパーツのいずれかで IOException が発生した場合 (たとえば、Jackson が JSON パーツをデシリアライズできなかった場合)、 org.jboss.resteasy.reactive.server.multipart.MultipartPartReadingException が出力されます。 例外のマッピング で説明したように、この例外がアプリケーションによって処理されない場合、HTTP 400 応答がデフォルトで返されます。

レスポンスボディを返す

HTTP レスポンスを返すには、単にメソッドから必要なリソースを返します。メソッドの戻り値の型とオプションのコンテントタイプをもとに、HTTP レスポンスへのシリアライズ方法を決定します (より詳細な情報については ネゴシエーション を参照してください)。

HTTP レスポンス から読み取ることができる定義済みの型のいずれかを返すことができます。またその他の型は その型から JSONに マップされます。

さらに、次の戻り値の型もサポートされています。

Table 4. 表 追加のレスポンスボディパラメーター型
型e 使用方法

Path

指定されたパスで指定されたファイルの内容

PathPart

指定されたパスで指定されたファイルの部分的な内容

FilePart

ファイルの部分的な内容

AsyncFile

Vert.x AsyncFile (完全または部分的)

あるいは、UniMulti または CompletionStage など、前述の戻り値の型に解決して、リアクティブ型 を返すことも可能です。

他のレスポンスプロパティーの設定

手動でレスポンスを設定する

ステータスコードやヘッダーなど、ボディだけでなく HTTP レスポンスに多くのプロパティーを設定する必要がある場合は、リソースメソッドからメソッドに org.jboss.resteasy.reactive.RestResponse を返すようにすることができます。この例は次のようになります。

package org.acme.rest;

import java.time.Duration;
import java.time.Instant;
import java.util.Date;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.NewCookie;

import org.jboss.resteasy.reactive.RestResponse;
import org.jboss.resteasy.reactive.RestResponse.ResponseBuilder;

@Path("")
public class Endpoint {

    @GET
    public RestResponse<String> hello() {
        // HTTP OK status with text/plain content type
        return ResponseBuilder.ok("Hello, World!", MediaType.TEXT_PLAIN_TYPE)
         // set a response header
         .header("X-Cheese", "Camembert")
         // set the Expires response header to two days from now
         .expires(Date.from(Instant.now().plus(Duration.ofDays(2))))
         // send a new cookie
         .cookie(new NewCookie("Flavour", "chocolate"))
         // end of builder API
         .build();
    }
}
また、JAX-RS 型の Response を使用することもできますが、エンティティーに強く型付けされるわけではありません。

アノテーションの使用

または、静的な値でステータスコードや HTTP ヘッダーのみを設定する必要がある場合は、それぞれ @org.jboss.resteasy.reactive.ResponseStatus および/または ResponseHeader を使用できます。この例は次のようになります。

package org.acme.rest;

import org.jboss.resteasy.reactive.Header;
import org.jboss.resteasy.reactive.ResponseHeaders;
import org.jboss.resteasy.reactive.ResponseStatus;

import javax.ws.rs.GET;
import javax.ws.rs.Path;

@Path("")
public class Endpoint {

    @ResponseStatus(201)
    @ResponseHeader(name = "X-Cheese", value = "Camembert")
    @GET
    public String hello() {
        return "Hello, World!";
    }
}

非同期/リアクティブサポート

エンドポイントメソッドがレスポンスする前に非同期またはリアクティブタスクを実行する必要がある場合は、メソッドを宣言して( Mutiny の) Uni 型を返すことができます 。この場合、現在の HTTP リクエストはメソッドの後で、返された Uni インスタンスが値に解決され、前述のルールに従って正確にレスポンスにマッピングされるまで自動的に一時停止されます。

package org.acme.rest;

import javax.ws.rs.GET;
import javax.ws.rs.Path;

import io.smallrye.mutiny.Uni;

@Path("escoffier")
public class Endpoint {

    @GET
    public Uni<Book> culinaryGuide() {
        return Book.findByIsbn("978-2081229297");
    }
}

これにより、book がデータベースから取得されている間、イベントループスレッドをブロックせず、book がクライアントに送信できるようになるまで Quarkus がより多くのリクエストに対応し、このリクエストを終了させることができます。詳しくは、実行モデルのドキュメント を参照してください。

CompletionStage 戻り値の型もサポートされています。

ストリーミングサポート

レスポンスを要素ごとにストリーミングする場合は、エンドポイントメソッドに( Mutiny の) Multi 型を返すようにすることができます。これは、テキストまたはバイナリーデータのストリーミングに特に役立ちます。

この例では、 Reactive Messaging HTTP を使用して、テキストデータをストリーミングする方法を示しています。

package org.acme.rest;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;

import org.eclipse.microprofile.reactive.messaging.Channel;

import io.smallrye.mutiny.Multi;

@Path("logs")
public class Endpoint {

    @Inject
    @Channel("log-out")
    Multi<String> logs;

    @GET
    public Multi<String> streamLogs() {
        return logs;
    }
}
レスポンスフィルターは、ストリーミングされたレスポンスでは呼び出されません。これは、ヘッダーまたは HTTP ステータスコードを設定できるという誤った印象を与えるためです。これは、最初のレスポンスの後では当てはまりません。レスポンスの一部がすでに書き込まれている可能性があるため、例外マッパーも呼び出されません。

サーバー送信イベント (SSE) のサポート

レスポンスで JSON オブジェクトをストリーミングする場合は、エンドポイントメソッドに @Produces(MediaType.SERVER_SENT_EVENTS) でアノテーションを付けるだけで、 Server-Sent Events を使用できます。そして指定した場合、 @RestStreamElementType (MediaType.APPLICATION_JSON) を使用し各要素は JSONにシリアライズ されるべきです。

package org.acme.rest;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import org.jboss.resteasy.reactive.RestStreamElementType;

import io.smallrye.mutiny.Multi;
import io.smallrye.mutiny.Uni;

import io.smallrye.reactive.messaging.annotations.Channel;

@Path("escoffier")
public class Endpoint {

    // Inject our Book channel
    @Inject
    @Channel("book-out")
    Multi<Book> books;

    @GET
    // Each element will be sent as JSON
    @RestStreamElementType(MediaType.APPLICATION_JSON)
    // by using @RestStreamElementType, we don't need to add @Produces(MediaType.SERVER_SENT_EVENTS)
    public Multi<Book> stream() {
        return books;
    }
}

HTTP キャッシング機能の制御

RESTEasy Reactive は、HTTP キャッシングセマンティックスの処理、つまり 'Cache-Control' HTTP ヘッダーの設定を容易にするために、@Cache@NoCache というアノテーションを提供します。

これらのアノテーションはリソースメソッドまたはリソースクラス (この場合、同じアノテーションを 含まない クラスのすべてのリソースメソッドに適用されます) に配置することができ、ユーザーがドメインオブジェクトを返すことができ、明示的に Cache-Control HTTP ヘッダーを構築することに対処する必要はありません。

@Cache が複雑な Cache-Control ヘッダーを構築するのに対し、@NoCache は何もキャッシュさせたくない、つまり Cache-Control: nocache というシンプルな表記法になっています。

Cache-Control ヘッダーの詳細は、 RFC 7234 を参照してください

コンテキストオブジェクトへのアクセス

エンドポイントメソッドが次の型のパラメーターを受け取る場合、フレームワークが提供するコンテキストオブジェクトは多数あります。

Table 5. テーブルコンテキストオブジェクト
型e 使用方法

HttpHeaders

すべてのリクエストヘッダー

ResourceInfo

現在のエンドポイントメソッドとクラスに関する情報 (リフレクションが必要)

SecurityContext

現在のユーザーとロールへのアクセス

SimpleResourceInfo

現在のエンドポイントメソッドとクラスに関する情報 (反映は不要)

UriInfo

現在のエンドポイントとアプリケーション URI に関する情報を提供します

Application

詳細: 現在の JAX-RS アプリケーションクラス

Configuration

詳細: デプロイされた JAX-RS アプリケーションに関する設定

Providers

詳細: JAX-RS プロバイダーへのランタイムアクセス

Request

詳細: 現在の HTTP メソッドへのアクセスと 前提条件

ResourceContext

詳細: エンドポイントのインスタンスへのアクセス

ServerRequestContext

詳細:RESTEasy 現在の要求/レスポンスへのリアクティブアクセス

Sse

詳細: 複雑な SSE のユースケース

HttpServerRequest

詳細: Vert.xHTTP リクエスト

HttpServerResponse

詳細: Vert..x HTTP レスポンス

たとえば、現在ログインしているユーザーの名前を返す方法は次のとおりです。

package org.acme.rest;

import java.security.Principal;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.SecurityContext;

@Path("user")
public class Endpoint {

    @GET
    public String userName(SecurityContext security) {
        Principal user = security.getUserPrincipal();
        return user != null ? user.getName() : "<NOT LOGGED IN>";
    }
}

同じ型のフィールドに @Inject を使用して、これらのコンテキストオブジェクトを挿入することもできます。

package org.acme.rest;

import java.security.Principal;

import javax.inject.Inject;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.SecurityContext;

@Path("user")
public class Endpoint {

    @Inject
    SecurityContext security;

    @GET
    public String userName() {
        Principal user = security.getUserPrincipal();
        return user != null ? user.getName() : "<NOT LOGGED IN>";
    }
}

または、エンドポイントコンストラクターでも:

package org.acme.rest;

import java.security.Principal;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.SecurityContext;

@Path("user")
public class Endpoint {

    SecurityContext security;

    Endpoint(SecurityContext security) {
        this.security = security;
    }

    @GET
    public String userName() {
        Principal user = security.getUserPrincipal();
        return user != null ? user.getName() : "<NOT LOGGED IN>";
    }
}

JSON シリアライゼーション

io.quarkus:quarkus-resteasy-reactive をインポートする代わりに、以下のモジュールのいずれかをインポートすることで、JSON のサポートを受けることができます。

Table 6. テーブルコンテキストオブジェクト
GAV 使用方法

io.quarkus:quarkus-resteasy-reactive-jackson

Jackson サポート

io.quarkus:quarkus-resteasy-reactive-jsonb

JSON-B サポート

どちらの場合も、これらのモジュールをインポートすると、 より具体的なシリアライゼーションで登録されていないすべての型 に対して、HTTP メッセージボディを JSON から読み取って JSON にシリアライズできます。

詳細なJackson固有の機能

quarkus-resteasy-reactive-jackson エクステンションを使用する場合、RESTEasy Reactive がサポートするいくつかの高度な機能があります。

セキュア・シリアライゼーション

JSON をシリアライズするために Jackson を使用する場合、RESTEasy Reactive は、現在のユーザのroleに基づいてシリアライズされるフィールドのセットを制限する機能を提供します。これは、返されるPOJOのフィールド(またはゲッター)を @io.quarkus.resteasy.reactive.jackson.SecureField でアノテーションするだけで実現できます。

簡単な例を挙げると、次のようになります:

Person という名前のPOJOがあり、以下のようになっているとします:

package org.acme.rest;

import io.quarkus.resteasy.reactive.jackson.SecureField;

public class Person {

    @SecureField(rolesAllowed = "admin")
    private final Long id;
    private final String first;
    private final String last;

    public Person(Long id, String first, String last) {
        this.id = id;
        this.first = first;
        this.last = last;
    }

    public Long getId() {
        return id;
    }

    public String getFirst() {
        return first;
    }

    public String getLast() {
        return last;
    }
}

Person を使用する非常にシンプルな JAX-RS Resource は次のようになります:

package org.acme.rest;

import javax.ws.rs.GET;
import javax.ws.rs.Path;

@Path("person")
public class Person {

    @Path("{id}")
    @GET
    public Person getPerson(Long id) {
        return new Person(id, "foo", "bar");
    }
}

アプリケーションのセキュリティーが設定されていると仮定すると (詳細は ガイド を参照)、admin ロールを持つユーザーが /person/1 に対して HTTP GET を実行すると、次のようなメッセージが表示されます。

{
  "id": 1,
  "first": "foo",
  "last": "bar"
}

レスポンスを閉じます。

ただし、admin ロールを持たないユーザーは次のものを受け取ります。

{
  "first": "foo",
  "last": "bar"
}
この安全なシリアル化を実行するために、追加の設定を適用する必要はありません。ただし、ユーザーは @io.quarkus.resteasy.reactive.jackson.EnableSecureSerialization@io.quarkus.resteasy.reactive.jackson.DisableSecureSerialization アノテーションを使って、特定の JAX-RS Resource クラスまたはメソッドを選択することも可能です。
@JsonView support

JAX-RS のメソッドに @JsonView のアノテーションを付けると、返される POJO のシリアル化をメソッド単位でカスタマイズすることが可能です。これは例で説明するのが一番わかりやすいでしょう。

@JsonView の一般的な使用法は、特定のメソッドの特定のフィールドを非表示にすることです。その流れの中で、2 つのビューを定義しましょう。

public class Views {

    public static class Public {
    }

    public static class Private extends Public {
    }
}

シリアル化中にフィールドを非表示にする User POJO があると仮定します。この簡単な例は次のとおりです。

public class User {

    @JsonView(Views.Private.class)
    public int id;

    @JsonView(Views.Public.class)
    public String name;
}

このユーザーを返す JAX-RS メソッドによっては、シリアル化から id フィールドを除外したい場合があります。たとえば、安全でないメソッドでこのフィールドを公開しないようにしたい場合があります。RESTEasy Reactive でそれを実現する方法を、次の例に示します。

@JsonView(Views.Public.class)
@GET
@Path("/public")
public User userPublic() {
    return testUser();
}

@JsonView(Views.Private.class)
@GET
@Path("/private")
public User userPrivate() {
    return testUser();
}

結果の userPublic メソッドがシリアル化されると、Public ビューに含まれないため、id フィールドはレスポンスに含まれません。ただし、userPrivate の結果には、シリアル化されたときに期待されるように id が含まれます。

メソッドごとに完全にカスタマイズされたシリアル化

JAX-RS のメソッドごとに POJO のシリアル化を完全にカスタマイズする必要がある場合があります。このような場合、@io.quarkus.resteasy.reactive.jackson.CustomSerialization アノテーションは素晴らしいツールで、自由に設定できる com.fasterxml.jackson.databind.ObjectWriter というメソッド単位で設定することが可能です。

ユースケースの例を次に示します。

@CustomSerialization(UnquotedFields.class)
@GET
@Path("/invalid-use-of-custom-serializer")
public User invalidUseOfCustomSerializer() {
    return testUser();
}

ここで、UnquotedFields は次のように定義された BiFunction です。

public static class UnquotedFields implements BiFunction<ObjectMapper, Type, ObjectWriter> {

    @Override
    public ObjectWriter apply(ObjectMapper objectMapper, Type type) {
        return objectMapper.writer().without(JsonWriteFeature.QUOTE_FIELD_NAMES);
    }
}

基本的にこのクラスが行うことは、フィールド名に引用符を含めないよう Jackson に強制することです。

このカスタマイズは @CustomSerialization(UnquotedFields.class) を使用する JAX-RS メソッドのシリアル化にのみ行われることに注意することが重要です。

XML シリアライゼーション

XML サポートを有効にするには、プロジェクトに quarkus-resteasy-reactive-jaxb エクステンションを追加します。

Table 7. テーブルコンテキストオブジェクト
GAV 使用方法

io.quarkus:quarkus-resteasy-reactive-jaxb

XML サポート

このモジュールをインポートすると、 より具体的なシリアライゼーションで登録されていないすべての型 に対して、HTTP メッセージボディを XML から読み取って XML にシリアライズできます。

高度な JAXB 固有の機能

quarkus-resteasy-reactive-jaxb エクステンションを使用する場合、RESTEasy Reactive がサポートするいくつかの高度な機能があります。

JAXB コンポーネントを注入する

JAXB resteasy リアクティブエクステンションは、ユーザーに対して透過的にリクエストとレスポンスをシリアライズおよびアンシリアライズします。ただし、JAXB コンポーネントをより細かく制御する必要がある場合は、JAXBContext、Marshaller、または Unmarshaller コンポーネントのいずれかを Bean に注入できます。

@ApplicationScoped
public class MyService {

    @Inject
    JAXBContext jaxbContext;

    @Inject
    Marshaller marshaller;

    @Inject
    Unmarshaller unmarshaller;

    // ...
}

Quarkus は、@XmlRootElement でアノテーションが付けられたすべてのクラスを自動的に検出し、それらを JAXB コンテキストにバインドします。

JAXB 設定をカスタマイズする

JAXB コンテキスト、および/または Marshaller/Unmarshaller コンポーネントの JAXB 設定をカスタマイズするには、型 io.quarkus.jaxb.runtime.JaxbContextCustomizer の CDI Bean を定義することをお勧めします。

カスタムモジュールを登録する必要がある場合の例は次のようになります。

@Singleton
public class RegisterCustomModuleCustomizer implements JaxbContextCustomizer {

    // For JAXB context configuration
    @Override
    public void customizeContextProperties(Map<String, Object> properties) {

    }

    // For Marshaller configuration
    @Override
    public void customizeMarshaller(Marshaller marshaller) throws PropertyException {
        marshaller.setProperty("jaxb.formatted.output", Boolean.TRUE);
    }

    // For Unmarshaller configuration
    @Override
    public void customizeUnmarshaller(Unmarshaller unmarshaller) throws PropertyException {
        // ...
    }
}

3 つのメソッドすべてを実装する必要はありませんが、必要なものだけを実装する必要があります。

または、次のようにして、独自の JAXBContext Bean を提供できます。

public class CustomJaxbContext {

    // Replaces the CDI producer for JAXBContext built into Quarkus
    @Singleton
    @Produces
    JAXBContext jaxbContext() {
        // ...
    }
}

カスタム JAXB コンテキストインスタンスを提供する場合は、XML シリアル化に使用するクラスを登録する必要があることに注意してください。これは、Quarkus がカスタム JAXB コンテキストインスタンスを自動検出されたクラスで更新しないことを意味します。

Web リンクのサポート

Table 8. テーブルコンテキストオブジェクト
GAV 使用方法

io.quarkus:quarkus-resteasy-reactive-links

Web リンクのサポート

このモジュールをインポートすると、エンドポイントリソースに @InjectRestLinks アノテーションを付けるだけで、レスポンスの HTTP ヘッダに Web リンクを挿入することができるようになります。返される Web リンクを宣言するには、リンクされたメソッドで @RestLink アノテーションを使用する必要があります。この例としては、以下のようなものがあります。

@Path("/records")
public class RecordsResource {

    @GET
    @RestLink(rel = "list")
    @InjectRestLinks
    public List<Record> getAll() {
        // ...
    }

    @GET
    @Path("/{id}")
    @RestLink(rel = "self")
    @InjectRestLinks(RestLinkType.INSTANCE)
    public TestRecord get(@PathParam("id") int id) {
        // ...
    }

    @PUT
    @Path("/{id}")
    @RestLink
    @InjectRestLinks(RestLinkType.INSTANCE)
    public TestRecord update(@PathParam("id") int id) {
        // ...
    }

    @DELETE
    @Path("/{id}")
    @RestLink
    public TestRecord delete(@PathParam("id") int id) {
        // ...
    }
}

上記のリソース内でメソッド getAll によって定義されたエンドポイント /records を curl を使用して呼び出すと、Web リンクヘッダーが取得されます。

& curl -i localhost:8080/records
Link: <http://localhost:8080/records>; rel="list"

このリソースは型 Record の単一のインスタンスを返さないため、メソッド getupdate、および delete のリンクは挿入されません。これで、エンドポイント /records/1 を呼び出すと、次の Web リンクが表示されます。

& curl -i localhost:8080/records/1
Link: <http://localhost:8080/records>; rel="list"
Link: <http://localhost:8080/records/1>; rel="self"
Link: <http://localhost:8080/records/1>; rel="update"
Link: <http://localhost:8080/records/1>; rel="delete"

最後に、delete リソースを呼び出すとき、メソッド delete には @InjectRestLinks アノテーションが付けられていないため、Web リンクは表示されません。

Web リンクレジストリーへのプログラムによるアクセス

RestLinksProvider Bean を挿入するだけで、プログラムで Web リンクレジストリーにアクセスできます。

@Path("/records")
public class RecordsResource {

    @Inject
    RestLinksProvider linksProvider;

    // ...
}

RestLinksProvider の挿入された Bean を使用すると、メソッド RestLinksProvider.getTypeLinks を使用して型ごとにリンクを取得するか、メソッド RestLinksProvider.getInstanceLinks を使用して具象インスタンスによってリンクを取得できます。

JSON ハイパーテキストアプリケーション言語 (HAL) のサポート

HAL 標準は、Web リンクを表す単純な形式です。

HAL サポートを有効にするには、プロジェクトに quarkus-hal エクステンションを追加します。また、HAL には JSON サポートが必要なため、quarkus-resteasy-reactive-jsonb または quarkus-resteasy-reactive-jackson エクステンションを追加する必要があります。

Table 9. テーブルコンテキストオブジェクト
GAV 使用方法

io.quarkus:quarkus-hal

HAL

エクステンションを追加した後、REST リソースにアノテーションを付けて、メディアタイプ application/hal+json を生成できます (または RestMediaType.APPLICATION_HAL_JSON を使用します)。例えば:

@Path("/records")
public class RecordsResource {

    @GET
    @Produces({ MediaType.APPLICATION_JSON, RestMediaType.APPLICATION_HAL_JSON })
    @RestLink(rel = "list")
    @InjectRestLinks
    public List<Record> getAll() {
        // ...
    }

    @GET
    @Produces({ MediaType.APPLICATION_JSON, RestMediaType.APPLICATION_HAL_JSON })
    @Path("/{id}")
    @RestLink(rel = "self")
    @InjectRestLinks(RestLinkType.INSTANCE)
    public TestRecord get(@PathParam("id") int id) {
        // ...
    }
}

ここで、エンドポイント /records/records/{id} は、jsonhal + json の両方のメディアタイプを受け入れて、Hal 形式でレコードを出力します。

たとえば、curl を使用して /records エンドポイントを呼び出してレコードのリストを返す場合、HAL 形式は次のようになります。

& curl -H "Accept:application/hal+json" -i localhost:8080/records
{
    "_embedded": {
        "items": [
            {
                "id": 1,
                "slug": "first",
                "value": "First value",
                "_links": {
                    "self": {
                        "href": "http://localhost:8081/records/1"
                    },
                    "list": {
                        "href": "http://localhost:8081/records"
                    }
                }
            },
            {
                "id": 2,
                "slug": "second",
                "value": "Second value",
                "_links": {
                    "self": {
                        "href": "http://localhost:8081/records/2"
                    },
                    "list": {
                        "href": "http://localhost:8081/records"
                    }
                }
            }
        ]
    },
    "_links": {
        "list": {
            "href": "http://localhost:8081/records"
        }
    }
}

1 つのインスタンスのみを返すリソース /records/1 を呼び出すと、出力は次のようになります。

& curl -H "Accept:application/hal+json" -i localhost:8080/records/1
{
    "id": 1,
    "slug": "first",
    "value": "First value",
    "_links": {
        "self": {
            "href": "http://localhost:8081/records/1"
        },
        "list": {
            "href": "http://localhost:8081/records"
        }
    }
}

最後に、次の例で説明するように、 HalCollectionWrapper (エンティティのリストを返す) または HalEntityWrapper (単一オブジェクトを返す)を返すことで、リソースにプログラム的に追加のHALリンクを提供することも可能です。

@Path("/records")
public class RecordsResource {

    @Inject
    RestLinksProvider linksProvider;

    @GET
    @Produces({ MediaType.APPLICATION_JSON, RestMediaType.APPLICATION_HAL_JSON })
    @RestLink(rel = "list")
    public HalCollectionWrapper getAll() {
        List<Record> list = // ...
        HalCollectionWrapper halCollection = new HalCollectionWrapper(list, "collectionName", linksProvider.getTypeLinks(Record.class));
        halCollection.addLinks(Link.fromPath("/records/1").rel("first-record").build());
        return halCollection;
    }

    @GET
    @Produces({ MediaType.APPLICATION_JSON, RestMediaType.APPLICATION_HAL_JSON })
    @Path("/{id}")
    @RestLink(rel = "self")
    @InjectRestLinks(RestLinkType.INSTANCE)
    public HalEntityWrapper get(@PathParam("id") int id) {
        Record entity = // ...
        HalEntityWrapper halEntity = new HalEntityWrapper(entity, linksProvider.getInstanceLinks(entity));
        halEntity.addLinks(Link.fromPath("/records/1/parent").rel("parent-record").build());
        return halEntity;
    }
}

CORS フィルター

クロスオリジンリソース共有 (CORS)は、ウェブページ上の制限されたリソースを、最初のリソースが提供されたドメイン以外の別のドメインから要求できるようにするメカニズムです。

Quarkus には、HTTP レイヤーレベルの CORS フィルターが付属しています。使用方法については、HTTPリファレンスドキュメント を参照してください。

より高度な使用法

ここでは、最初は知る必要がないかもしれませんが、より複雑なユースケースに役立つ可能性のある、より詳細なトピックをいくつか紹介します。

実行モデル、ブロッキング、ノンブロッキング

RESTEasy Reactive は、次の 2 つのメインスレッド型を使用して実装されます。

  • イベントループスレッド: 特に、HTTP リクエストからバイトを読み取る役割を果たします。 HTTP レスポンスにバイトを書き戻す

  • ワーカースレッド: プールされ、長時間実行される操作の負荷を軽減するために使用できます

イベントループスレッド (IO スレッドとも呼ばれます) は、すべての IO 操作を非同期で実際に実行し、それらの IO 操作の完了に関心のあるリスナーをトリガーする役割を果たします。

デフォルトでは、スレッド RESTEasy Reactive は、メソッドのシグネチャーに応じてエンドポイントメソッドを実行します。メソッドが次の型のいずれかを返す場合、それは非ブロッキングと見なされ、デフォルトで IO スレッドで実行されます。

  • io.smallrye.mutiny.Uni

  • io.smallrye.mutiny.Multi

  • java.util.concurrent.CompletionStage

  • org.reactivestreams.Publisher

  • Kotlin の suspended メソッド

この最良の推測アプローチは、操作の大部分がデフォルトで正しいスレッドで実行されることを意味します。リアクティブコードを記述している場合、メソッドは通常、これらの型の 1 つを返し、IO スレッドで実行されます。ブロッキングコードを記述している場合、メソッドは通常、結果を直接返し、これらはワーカースレッドで実行されます。

@Blocking アノテーションと @NonBlocking アノテーションを使用してこの動作をオーバーライドできます。これは、メソッド、クラス、または javax.ws.rs.core.Application レベルで適用できます。

以下の例は、デフォルトの動作をオーバーライドし、Uni を返しても、常にワーカースレッドで実行されます。

package org.acme.rest;

import javax.ws.rs.GET;
import javax.ws.rs.Path;

import io.smallrye.common.annotation.Blocking;

@Path("yawn")
public class Endpoint {

    @Blocking
    @GET
    public Uni<String> blockingHello() throws InterruptedException {
        // do a blocking operation
        Thread.sleep(1000);
        return Uni.createFrom().item("Yaaaawwwwnnnnnn…");
    }
}

ほとんどの場合、 MutinyHibernate Reactive 、または Quarkusリアクティブエクステンション などを使って、同じブロック操作を非同期/リアクティブに実現する方法があります。

package org.acme.rest;

import java.time.Duration;

import javax.ws.rs.GET;
import javax.ws.rs.Path;

import io.smallrye.mutiny.Uni;

@Path("yawn")
public class Endpoint {

    @GET
    public Uni<String> blockingHello() throws InterruptedException {
        return Uni.createFrom().item("Yaaaawwwwnnnnnn…")
                // do a non-blocking sleep
                .onItem().delayIt().by(Duration.ofSeconds(2));
    }
}

メソッドまたはクラスに javax.transaction.Transactional アノテーションが付けられている場合、それもブロッキングメソッドとして扱われます。これは、JTA がブロッキングテクノロジーであり、Hibernate や JDBC などの他のブロッキングテクノロジーで一般的に使用されているためです。クラスでの明示的な @Blocking または @NonBlocking は、この動作をオーバーライドします。

デフォルトの動作のオーバーライド

デフォルトの動作をオーバーライドする場合は、アプリケーションの javax.ws.rs.core.Application サブクラスに @Blocking または @NonBlocking のアノテーションを付けることができます。これにより、明示的なアノテーションを付与していないすべてのメソッドのデフォルトが設定されます。

動作は、クラスまたはメソッドレベルで直接アノテーションを付けることでオーバーライドできますが、アノテーションのないすべてのエンドポイントは、メソッドのシグネチャーに関係なく、デフォルトに従うようになります。

例外のマッピング

もしアプリケーションがエラー時に正規の HTTP コード以外を返す必要がある場合、最善の方法は、フレームワークが WebApplicationException またはその継承型を使用して適切な HTTP レスポンスを送信する結果となる例外を投げることです。

package org.acme.rest;

import javax.ws.rs.BadRequestException;
import javax.ws.rs.GET;
import javax.ws.rs.NotFoundException;
import javax.ws.rs.Path;

@Path("cheeses/{cheese}")
public class Endpoint {

    @GET
    public String findCheese(String cheese) {
        if(cheese == null)
            // send a 400
            throw new BadRequestException();
        if(!cheese.equals("camembert"))
            // send a 404
            throw new NotFoundException("Unknown cheese: " + cheese);
        return "Camembert is a very nice cheese";
    }
}

エンドポイントメソッドが JAX-RS を認識しない別のサービスレイヤーに呼び出しを委任している場合は、サービス例外を HTTP レスポンスに変換する方法が必要であり、メソッドに @ServerExceptionMapper を使用してこれを行うことができます。例外マッパーは処理する例外型のパラメーターが 1 つあり、その例外を RestResponse (あるいは Uni<RestResponse<?>>) に変換します。

package org.acme.rest;

import java.util.Map;

import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.ws.rs.BadRequestException;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.core.Response;

import org.jboss.resteasy.reactive.server.ServerExceptionMapper;
import org.jboss.resteasy.reactive.RestResponse;

class UnknownCheeseException extends RuntimeException {
    public final String name;

    public UnknownCheeseException(String name) {
        this.name = name;
    }
}

@ApplicationScoped
class CheeseService {
    private static final Map<String, String> cheeses =
            Map.of("camembert", "Camembert is a very nice cheese",
                   "gouda", "Gouda is acceptable too, especially with cumin");

    public String findCheese(String name) {
        String ret = cheeses.get(name);
        if(ret != null)
            return ret;
        throw new UnknownCheeseException(name);
    }
}

@Path("cheeses/{cheese}")
public class Endpoint {

    @Inject
    CheeseService cheeses;

    @ServerExceptionMapper
    public RestResponse<String> mapException(UnknownCheeseException x) {
        return RestResponse.status(Response.Status.NOT_FOUND, "Unknown cheese: " + x.name);
    }

    @GET
    public String findCheese(String cheese) {
        if(cheese == null)
            // send a 400
            throw new BadRequestException();
        return cheeses.findCheese(cheese);
    }
}
REST エンドポイントクラスで定義された例外マッパーは、例外が同じクラスで出力された場合にのみ呼び出されます。グローバル例外マッパーを定義する場合は、REST エンドポイントクラスの外部で定義するだけです。
package org.acme.rest;

import org.jboss.resteasy.reactive.server.ServerExceptionMapper;
import org.jboss.resteasy.reactive.RestResponse;

class ExceptionMappers {
    @ServerExceptionMapper
    public RestResponse<String> mapException(UnknownCheeseException x) {
        return RestResponse.status(Response.Status.NOT_FOUND, "Unknown cheese: " + x.name);
    }
}

JAX-RSの方式で例外マッパー を宣言することもできます。

例外マッパーは、次のパラメーター型のいずれかを宣言できます。

Table 10. テーブル例外マッパーパラメーター
型e 使用方法

例外型

処理する例外型を定義します

コンテキストオブジェクト のいずれか

ContainerRequestContext

現在のリクエストにアクセスするためのコンテキストオブジェクト

次の戻り値の型のいずれかを宣言できます。

Table 11. テーブル 例外マッパーの戻り値の型
型e 使用方法

RestResponse または Response

例外が発生したときにクライアントに送信するレスポンス

Uni<RestResponse> または Uni<Response>

例外が発生したときにクライアントに送信する非同期レスポンス

例外が発生した場合、RESTEasy Reactive はデフォルトでログを記録しません (セキュリティ上の理由による)。 このため、特定の例外処理コードが呼び出された (または呼び出されなかった) 理由を理解するのが難しい場合があります。 RESTEasy Reactive が例外マッピングコードを実行する前に実際の例外をログするようにするには、次のように org.jboss.resteasy.reactive.common.core.AbstractResteasyReactiveContext ログカテゴリを DEBUG に設定します。

quarkus.log.category."org.jboss.resteasy.reactive.common.core.AbstractResteasyReactiveContext".level=DEBUG

リクエストまたはレスポンスフィルター

アノテーション経由

リクエスト処理の次のフェーズで呼び出される関数を宣言できます。

  • エンドポイントメソッドが識別される前: 事前ルーティングリクエストフィルター

  • ルーティング後、エンドポイントメソッドが呼び出される前: 通常のリクエストフィルター

  • エンドポイントメソッドが呼び出された後: レスポンスフィルター

これらのフィルターを使用すると、リクエスト URI、HTTP メソッドの調査、ルーティングへの影響、リクエストヘッダーの検索または変更、リクエストの中止、レスポンスの変更など、さまざまなことができます。

リクエストフィルターは、 @ServerRequestFilter アノテーションで宣言することが出来ます:

import java.util.Optional;

class Filters {

    @ServerRequestFilter(preMatching = true)
    public void preMatchingFilter(ContainerRequestContext requestContext) {
        // make sure we don't lose cheese lovers
        if("yes".equals(requestContext.getHeaderString("Cheese"))) {
            requestContext.setRequestUri(URI.create("/cheese"));
        }
    }

    @ServerRequestFilter
    public Optional<RestResponse<Void>> getFilter(ContainerRequestContext ctx) {
        // only allow GET methods for now
        if(ctx.getMethod().equals(HttpMethod.GET)) {
            return Optional.of(RestResponse.status(Response.Status.METHOD_NOT_ALLOWED));
        }
        return Optional.empty();
    }
}

リクエストフィルターは通常、リクエストを処理するメソッドが実行されるのと同じスレッドで実行されます。つまり、リクエストを処理するメソッドが @Blocking でアノテーションされている場合は、フィルターもワーカスレッドで実行されます。メソッドに @NonBlocking のアノテーションがある場合 (あるいは全くアノテーションがない場合)、フィルターは同じイベントループのスレッドで実行されます。

しかし、リクエストを処理するメソッドがワーカスレッドで実行されるにも関わらず、フィルターをイベントループで実行する必要がある場合、@ServerRequestFilter (nonBlocking=true) を使用することができます。しかし、これらのフィルターはこの設定を使わず、ワーカースレッドで実行される どんな フィルターよりも前に実行される必要があることに注意してください。

同様に、レスポンスフィルターは @ServerResponseFilter アノテーションで宣言することが可能です。

class Filters {
    @ServerResponseFilter
    public void getFilter(ContainerResponseContext responseContext) {
        Object entity = responseContext.getEntity();
        if(entity instanceof String) {
            // make it shout
            responseContext.setEntity(((String)entity).toUpperCase());
        }
    }
}

フィルターは、次のパラメーター型のいずれかを宣言できます。

Table 12. テーブルフィルターパラメーター
型e 使用方法

コンテキストオブジェクト のいずれか

ContainerRequestContext

現在のリクエストにアクセスするためのコンテキストオブジェクト

ContainerResponseContext

現在のレスポンスにアクセスするためのコンテキストオブジェクト

Throwable

出力された例外、または null (レスポンスフィルターの場合のみ)

次の戻り値の型のいずれかを宣言できます。

Table 13. テーブルフィルターの戻り値の型
型e 使用方法

RestResponse<?> または Response

フィルターチェーンを続行する代わりにクライアントに送信するレスポンス、またはフィルターチェーンを続行する必要がある場合は null

Optional<RestResponse<?>> または Optional<Response>

フィルターチェーンを続行する代わりにクライアントに送信するオプションのレスポンス、またはフィルターチェーンを続行する必要がある場合は空の値

Uni<RestResponse<?>> または Uni<Response>

フィルターチェーンを続行する代わりにクライアントに送信する非同期レスポンス、またはフィルターチェーンを続行する必要がある場合は null

@NameBinding メタアノテーションを使用して、フィルターが実行されるリソースメソッドを制限できます。

JAX-RS の方法

HTTP リクエストとレスポンスの両方とも、それぞれ ContainerRequestFilter または ContainerResponseFilter の実装を提供することで、 インターセプトすることができます。これらのフィルタは、メッセージに関連付けられたメタデータを処理するのに適しています。HTTP ヘッダ、クエリパラメーター、メディアタイプ、その他のメタデータです。また、ユーザーがエンドポイントにアクセスする権限を持っていない場合など、リクエスト処理を中止する機能も持っています。

ContainerRequestFilter を使用して、サービスにロギング機能を追加してみましょう。 ContainerRequestFilter を実装して、 @Provider アノテーションをつけることで実現できます。

package org.acme.rest.json;

import io.vertx.core.http.HttpServerRequest;
import org.jboss.logging.Logger;

import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.ext.Provider;

@Provider
public class LoggingFilter implements ContainerRequestFilter {

    private static final Logger LOG = Logger.getLogger(LoggingFilter.class);

    @Context
    UriInfo info;

    @Context
    HttpServerRequest request;

    @Override
    public void filter(ContainerRequestContext context) {

        final String method = context.getMethod();
        final String path = info.getPath();
        final String address = request.remoteAddress().toString();

        LOG.infof("Request %s %s from IP %s", method, path, address);
    }
}

これで、RESTメソッドが呼び出されるたびに、リクエストがコンソールにログとして記録されるようになりました。

2019-06-05 12:44:26,526 INFO  [org.acm.res.jso.LoggingFilter] (executor-thread-1) Request GET /legumes from IP 127.0.0.1
2019-06-05 12:49:19,623 INFO  [org.acm.res.jso.LoggingFilter] (executor-thread-1) Request GET /fruits from IP 0:0:0:0:0:0:0:1
2019-06-05 12:50:44,019 INFO  [org.acm.res.jso.LoggingFilter] (executor-thread-1) Request POST /fruits from IP 0:0:0:0:0:0:0:1
2019-06-05 12:51:04,485 INFO  [org.acm.res.jso.LoggingFilter] (executor-thread-1) Request GET /fruits from IP 127.0.0.1

リーダーとライター: マッピングエンティティーと HTTP ボディ

エンドポイントメソッドが(RestResponse<?> もしくはエンティティの Response を返し)オブジェクトを返すときはいつでも、RESTEasy Reactive は、それを HTTP レスポンスボディにマップする方法を探します。

同様に、エンドポイントメソッドがオブジェクトをパラメーターとして受け取る場合は常に、HTTP リクエストのボディをそのオブジェクトにマッピングする方法を探します。

これは、MessageBodyReaderMessageBodyWriter インターフェイスのプラグインシステムによって行われ、どの Java 型からどのメディアタイプへマッピングするか、またその型の Java インスタンスとどのように HTTP ボディをやり取りするかを定義する役割を持っています。

たとえば、エンドポイントに独自の Cheese 型がある場合:

package org.acme.rest;

import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;

class Cheese {
    public String name;

    public Cheese(String name) {
        this.name = name;
    }
}

@Path("cheese")
public class Endpoint {

    @GET
    public Cheese sayCheese() {
        return new Cheese("Cheeeeeese");
    }

    @PUT
    public void addCheese(Cheese cheese) {
        System.err.println("Received a new cheese: " + cheese.name);
    }
}

次に、 @Provider でアノテーションを付けたボディリーダー/ライターを使用して読み取りと書き込みの方法を定義できます:

package org.acme.rest;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;

@Provider
public class CheeseBodyHandler implements MessageBodyReader<Cheese>,
                                           MessageBodyWriter<Cheese> {

    @Override
    public boolean isWriteable(Class<?> type, Type genericType,
                               Annotation[] annotations, MediaType mediaType) {
        return type == Cheese.class;
    }

    @Override
    public void writeTo(Cheese t, Class<?> type, Type genericType,
                        Annotation[] annotations, MediaType mediaType,
                        MultivaluedMap<String, Object> httpHeaders,
                        OutputStream entityStream)
            throws IOException, WebApplicationException {
        entityStream.write(("[CheeseV1]" + t.name)
                           .getBytes(StandardCharsets.UTF_8));
    }

    @Override
    public boolean isReadable(Class<?> type, Type genericType,
                              Annotation[] annotations, MediaType mediaType) {
        return type == Cheese.class;
    }

    @Override
    public Cheese readFrom(Class<Cheese> type, Type genericType,
                            Annotation[] annotations, MediaType mediaType,
                            MultivaluedMap<String, String> httpHeaders,
                            InputStream entityStream)
            throws IOException, WebApplicationException {
        String body = new String(entityStream.readAllBytes(), StandardCharsets.UTF_8);
        if(body.startsWith("[CheeseV1]"))
            return new Cheese(body.substring(11));
        throw new IOException("Invalid cheese: " + body);
    }

}

ライターのパフォーマンスを最大限に高めたい場合は、 MessageBodyWriter の代わりに ServerMessageBodyWriter を拡張できます。こちらでは、リフレクションの使用を減らし、ブロッキング IO レイヤーをバイパスできます。

package org.acme.rest;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.Provider;

import org.jboss.resteasy.reactive.server.spi.ResteasyReactiveResourceInfo;
import org.jboss.resteasy.reactive.server.spi.ServerMessageBodyWriter;
import org.jboss.resteasy.reactive.server.spi.ServerRequestContext;

@Provider
public class CheeseBodyHandler implements MessageBodyReader<Cheese>,
                                           ServerMessageBodyWriter<Cheese> {

    // …

    @Override
    public boolean isWriteable(Class<?> type, ResteasyReactiveResourceInfo target,
                               MediaType mediaType) {
        return type == Cheese.class;
    }

    @Override
    public void writeResponse(Cheese t, ServerRequestContext context)
      throws WebApplicationException, IOException {
        context.serverResponse().end("[CheeseV1]" + t.name);
    }
}
プロバイダークラスに Consumes/Produces アノテーションを追加すると、リーダー/ライターが適用するコンテンツタイプを制限することができます。

リーダーおよびライターインターセプター

リクエストとレスポンスをインターセプトできるように、@Providerでアノテーションが付けられたクラスの ReaderInterceptor または WriterInterceptor を拡張することによってリーダーとライターをインターセプトすることもできます。

このエンドポイントを見ると、次のようになります。

package org.acme.rest;

import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;

@Path("cheese")
public class Endpoint {

    @GET
    public String sayCheese() {
        return "Cheeeeeese";
    }

    @PUT
    public void addCheese(String cheese) {
        System.err.println("Received a new cheese: " + cheese);
    }
}

次のようなリーダーとライターのインターセプターを追加できます。

package org.acme.rest;

import java.io.IOException;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.ext.Provider;
import javax.ws.rs.ext.ReaderInterceptor;
import javax.ws.rs.ext.ReaderInterceptorContext;
import javax.ws.rs.ext.WriterInterceptor;
import javax.ws.rs.ext.WriterInterceptorContext;

@Provider
public class CheeseIOInterceptor implements ReaderInterceptor, WriterInterceptor {

    @Override
    public void aroundWriteTo(WriterInterceptorContext context)
      throws IOException, WebApplicationException {
        System.err.println("Before writing " + context.getEntity());
        context.proceed();
        System.err.println("After writing " + context.getEntity());
    }

    @Override
    public Object aroundReadFrom(ReaderInterceptorContext context)
      throws IOException, WebApplicationException {
        System.err.println("Before reading " + context.getGenericType());
        Object entity = context.proceed();
        System.err.println("After reading " + entity);
        return entity;
    }
}

RESTEasy リアクティブおよび REST クライアントリアクティブインタラクション

Quarkus では、RESTEasy Reactive エクステンションと REST クライアントリアクティブエクステンション は同じインフラストラクチャを共有しています。この考慮の重要な結果の一つは、(JAX-RS の意味での) プロバイダーのリストを共有しているということです。

例えば、 WriterInterceptor を宣言した場合、デフォルトではサーバーの呼び出しとクライアントの呼び出しの両方をインターセプトしますが、これは望ましい動作ではないかもしれません。

しかし、このデフォルトの動作を変更してプロバイダを制約することができます。

  • プロバイダに @ConstrainedTo(RuntimeType.SERVER) アノテーションを追加することで、 サーバー コールのみを考慮します。

  • プロバイダに @ConstrainedTo(RuntimeType.CLIENT) アノテーションを追加することで、 クライアント コールのみを考慮します。

パラメーターマッピング

String だけでなく、次のいずれかの型も含め、全ての リクエストパラメーター はリンクとして宣言できます:

  • ParamConverterProvider を通じて登録済の ParamConverter が存在する型

  • プリミティブ型

  • 単一の String 引数を受け入れるコンストラクターを持つ型。

  • String という引数を持ち、その型のインスタンスを返す valueOf または fromString という名前の静的メソッドを持っている型。もし両方のメソッドが存在する場合は、valueOf が使用されます。ただし、その型が enum の場合は fromString が使用されます。

  • List<T>Set<T>、または SortedSet<T>。ここで T は上記の基準を満たします。

次の例は、これらすべての可能性を示しています。

package org.acme.rest;

import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.List;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.ext.ParamConverter;
import javax.ws.rs.ext.ParamConverterProvider;
import javax.ws.rs.ext.Provider;

import org.jboss.resteasy.reactive.RestQuery;

@Provider
class MyConverterProvider implements ParamConverterProvider {

    @Override
    public <T> ParamConverter<T> getConverter(Class<T> rawType, Type genericType,
                                              Annotation[] annotations) {
        // declare a converter for this type
        if(rawType == Converter.class) {
            return (ParamConverter<T>) new MyConverter();
        }
        return null;
    }

}

// this is my custom converter
class MyConverter implements ParamConverter<Converter> {

    @Override
    public Converter fromString(String value) {
        return new Converter(value);
    }

    @Override
    public String toString(Converter value) {
        return value.value;
    }

}

// this uses a converter
class Converter {
    String value;
    Converter(String value) {
        this.value = value;
    }
}

class Constructor {
    String value;
    // this will use the constructor
    public Constructor(String value) {
        this.value = value;
    }
}

class ValueOf {
    String value;
    private ValueOf(String value) {
        this.value = value;
    }
    // this will use the valueOf method
    public static ValueOf valueOf(String value) {
        return new ValueOf(value);
    }
}

@Path("hello")
public class Endpoint {

    @Path("{converter}/{constructor}/{primitive}/{valueOf}")
    @GET
    public String conversions(Converter converter, Constructor constructor,
                              int primitive, ValueOf valueOf,
                              @RestQuery List<Constructor> list) {
        return converter + "/" + constructor + "/" + primitive
               + "/" + valueOf + "/" + list;
    }
}

日付の取り扱い

RESTEasy Reactiveは、java.time.Temporal の実装( java.time.LocalDateTime のような)を、クエリ、パス、フォームのパラメーターとして使用することをサポートしています。さらに、 @org.jboss.resteasy.reactive.DateFormat アノテーションを提供しており、これを使用してカスタムの期待されるパターンを設定することができます(そうしないと、各型に対する JDK のデフォルトフォーマットが暗黙のうちに使用されます)。

前提条件

以下のようないくつかの条件の下で、HTTPでは条件付きのリクエストが可能です :

  • リソースの最終変更日

  • リソースの状態またはバージョンを指定するためのリソースのハッシュコードに似たリソースタグ

Request コンテキストオブジェクトを使用して、条件付きリクエスト検証を行う方法を見てみましょう。

package org.acme.rest;

import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalUnit;
import java.util.Date;

import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.core.EntityTag;
import javax.ws.rs.core.Request;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;

@Path("conditional")
public class Endpoint {

    // It's important to keep our date on seconds because that's how it's sent to the
    // user in the Last-Modified header
    private Date date = Date.from(Instant.now().truncatedTo(ChronoUnit.SECONDS));
    private int version = 1;
    private EntityTag tag = new EntityTag("v1");
    private String resource = "Some resource";

    @GET
    public Response get(Request request) {
        // first evaluate preconditions
        ResponseBuilder conditionalResponse = request.evaluatePreconditions(date, tag);
        if(conditionalResponse != null)
            return conditionalResponse.build();
        // preconditions are OK
        return Response.ok(resource)
                .lastModified(date)
                .tag(tag)
                .build();
    }

    @PUT
    public Response put(Request request, String body) {
        // first evaluate preconditions
        ResponseBuilder conditionalResponse = request.evaluatePreconditions(date, tag);
        if(conditionalResponse != null)
            return conditionalResponse.build();
        // preconditions are OK, we can update our resource
        resource = body;
        date = Date.from(Instant.now().truncatedTo(ChronoUnit.SECONDS));
        version++;
        tag = new EntityTag("v" + version);
        return Response.ok(resource)
                .lastModified(date)
                .tag(tag)
                .build();
    }
}

GET /conditional を初めて呼び出すと、このようなレスポンスが返ってきます。

HTTP/1.1 200 OK
Content-Type: text/plain;charset=UTF-8
ETag: "v1"
Last-Modified: Wed, 09 Dec 2020 16:10:19 GMT
Content-Length: 13

Some resource

これで、新しいバージョンを取得する必要があるかどうかを確認したい場合は、次のようなリクエストを行うことができます:

GET /conditional HTTP/1.1
Host: localhost:8080
If-Modified-Since: Wed, 09 Dec 2020 16:10:19 GMT

そして、次のようなレスポンスが返ってきます:

HTTP/1.1 304 Not Modified

なぜなら、そのリソースはその日以降に変更されていないからです。これはリソースを送信する手間を省くだけでなく、ユーザーが同時に変更されたことを検出するのにも役立ちます。たとえば、あるクライアントがリソースを更新したいが、別のユーザーがその後にリソースを変更したとしましょう。この場合、前の GET リクエストに続いて、この更新を行うことができます。

PUT /conditional HTTP/1.1
Host: localhost:8080
If-Unmodified-Since: Wed, 09 Dec 2020 16:25:43 GMT
If-Match: v1
Content-Length: 8
Content-Type: text/plain

newstuff

また、他のユーザーが GET と PUT の間のリソースを変更した場合は、次の回答が返されます。

HTTP/1.1 412 Precondition Failed
ETag: "v2"
Content-Length: 0

ネゴシエーション

REST (および HTTP) の主な考え方の 1 つは、リソースはその表現に依存せず、クライアントとサーバーの両方が、望むだけ多くのメディアタイプでリソースを自由に表現できることです。これにより、サーバーは複数の表現のサポートを宣言し、クライアントはどの表現をサポートするかを宣言し、適切なものを提供されるようにすることができます。

次のエンドポイントは、プレーンテキストまたは JSON での cheese の提供をサポートしています。

package org.acme.rest;

import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import com.fasterxml.jackson.annotation.JsonCreator;

class Cheese {
    public String name;
    @JsonCreator
    public Cheese(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "Cheese: " + name;
    }
}

@Path("negotiated")
public class Endpoint {

    @Produces({MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN})
    @GET
    public Cheese get() {
        return new Cheese("Morbier");
    }

    @Consumes(MediaType.TEXT_PLAIN)
    @PUT
    public Cheese putString(String cheese) {
        return new Cheese(cheese);
    }

    @Consumes(MediaType.APPLICATION_JSON)
    @PUT
    public Cheese putJson(Cheese cheese) {
        return cheese;
    }
}

JSON の場合、ユーザーは Accept ヘッダーでどの表現を取得するかを選択することができます。

> GET /negotiated HTTP/1.1
> Host: localhost:8080
> Accept: application/json

< HTTP/1.1 200 OK
< Content-Type: application/json
< Content-Length: 18
<
< {"name":"Morbier"}

そしてテキストの場合:

> GET /negotiated HTTP/1.1
> Host: localhost:8080
> Accept: text/plain
>
< HTTP/1.1 200 OK
< Content-Type: text/plain
< Content-Length: 15
<
< Cheese: Morbier

同様に、2 つの異なる表現を PUT することができます。JSON:

> PUT /negotiated HTTP/1.1
> Host: localhost:8080
> Content-Type: application/json
> Content-Length: 16
>
> {"name": "brie"}

< HTTP/1.1 200 OK
< Content-Type: application/json;charset=UTF-8
< Content-Length: 15
<
< {"name":"brie"}

またはプレーンテキスト:

> PUT /negotiated HTTP/1.1
> Host: localhost:8080
> Content-Type: text/plain
> Content-Length: 9
>
> roquefort

< HTTP/1.1 200 OK
< Content-Type: application/json;charset=UTF-8
< Content-Length: 20
<
< {"name":"roquefort"}

HTTP 圧縮

HTTP レスポンスのボディは、デフォルトでは圧縮されていません。quarkus.http.enable-compression=true を使用して HTTP 圧縮サポートを有効にできます。

圧縮サポートが有効であれば、以下の場合にレスポンスボディが圧縮されます。

  • ルートメソッドが @io.quarkus.vertx.http.Compressed でアノテーションされている、または

  • Content-Type ヘッダーが設定されており、その値は quarkus.http.compress-media-types を介して設定された圧縮メディアタイプである。

次の場合、レスポンスボディは圧縮されません。

  • ルートメソッドが @io.quarkus.vertx.http.Uncompressed でアノテーションされている、または

  • Content-Type ヘッダーが設定されていない。

デフォルトでは、次のメディアタイプのリストが圧縮されています: text/htmltext/plaintext/xmltext/csstext/javascriptapplication/javascript
クライアントが HTTP 圧縮をサポートしていない場合、レスポンスボディは圧縮されません。

ビルド時条件でJAX-RSクラスを含める/除外する

Quarkusでは、CDI Beanの場合と同様に、ビルド時の条件に応じて、JAX-RSリソース、プロバイダー、および機能を直接包含または除外することができます。したがって、さまざまなJAX-RSクラスは、プロファイル条件( @io.quarkus.arc.profile.IfBuildProfile または @io.quarkus.arc.profile.UnlessBuildProfile )および/またはプロパティ条件( io.quarkus.arc.properties.IfBuildProperty または io.quarkus.arc.properties.UnlessBuildProperty )でアノテーションすることができ、構築時にどの条件でこれらのJAX-RSクラスが含まれるべきかをQuarkusに示すことができます。

次の例では、Quarkusは、ビルドプロファイル app1 が有効になっている場合に限り、エンドポイント sayHello を含めます。

@IfBuildProfile("app1")
public class ResourceForApp1Only {

    @GET
    @Path("sayHello")
    public String sayHello() {
        return "hello";
     }
}

JAX-RSアプリケーションが検出され、メソッド getClasses() および/または getSingletons() がオーバーライドされている/されている場合、Quarkusはビルド時の条件を無視し、JAX-RSアプリケーションで定義されているもののみを考慮することに注意してください。

RESTEasy Reactive クライアント

RESTEasy Reactiveには、サーバー側に加えて、ノンブロッキングを核とした新しいMicroProfile Rest Clientの実装が搭載されています。

なお、RESTEasy Reactiveでは、 quarkus-rest-client エクステンションが正常に動作しない場合があります。 quarkus-rest-client-reactive を代わりに使用して下さい。

リアクティブ・クライアントの詳細については、REST Client Reactiveガイド を参照してください。