Jersey SSEクライアントリクエストへのヘッダーの追加

1. 概要

このチュートリアルでは、ジャージークライアントAPIを使用してlink:/spring-server-sent-events[Server-Sent Event](SSE)クライアントリクエストでヘッダーを送信する簡単な方法を紹介します。
また、デフォルトのJerseyトランスポートコネクタを使用して、基本的なキー/値ヘッダー、認証ヘッダー、および制限付きヘッダーを送信する適切な方法についても説明します。

2. まっすぐに

おそらく、SSEを使用してヘッダーを送信しようとしているときに、この状況に遭遇しました。
_SseEventSource_を使用してSSEを受信しますが、_SseEventSource_を構築するには、ヘッダーを追加する方法を提供しない_WebTarget_インスタンスが必要です。 _Client_インスタンスも役に立ちません。 おなじみの?
**ただし、ヘッダーはSSEに関連するのではなく、クライアントリクエスト自体に関連することを覚えておいてください。**
それでは、_ClientRequestFilter_を使用して何ができるかを見てみましょう。

3. 依存関係

旅を始めるには、https://search.maven.org/search?q = g:org.glassfish.jersey.core%20AND%20a:jersey-clientが必要です
<dependency>
    <groupId>org.glassfish.jersey.core</groupId>
    <artifactId>jersey-client</artifactId>
    <version>2.29</version>
</dependency>
<dependency>
    <groupId>org.glassfish.jersey.media</groupId>
    <artifactId>jersey-media-sse</artifactId>
    <version>2.29</version>
</dependency>
  • Jerseyは2.29の時点でJAX-RS 2.1をサポートしているため、その機能を使用できるようになります。

4. ClientRequestFilter

最初に、各クライアントリクエストにヘッダーを追加するフィルターを実装します。
public class AddHeaderOnRequestFilter implements ClientRequestFilter {

    public static final String FILTER_HEADER_VALUE = "filter-header-value";
    public static final String FILTER_HEADER_KEY = "x-filter-header";

    @Override
    public void filter(ClientRequestContext requestContext) throws IOException {
        requestContext.getHeaders().add(FILTER_HEADER_KEY, FILTER_HEADER_VALUE);
    }
}
その後、登録して使用します。
この例では、_https://sse.example.org_を、クライアントがイベントを消費するための仮想エンドポイントとして使用します。 実際には、これを実際のlink:/java-ee-jax-rs-sse[SSEイベントサーバーエンドポイント]に変更して、クライアントに消費させます。
Client client = ClientBuilder.newBuilder()
  .register(AddHeaderOnRequestFilter.class)
  .build();

WebTarget webTarget = client.target("https://sse.example.org/");

SseEventSource sseEventSource = SseEventSource.target(webTarget).build();
sseEventSource.register((event) -> { /* Consume event here */ });
sseEventSource.open();
// do something here until ready to close
sseEventSource.close();
*さて、SSEエンドポイントに認証ヘッダーなどのより複雑なヘッダーを送信する必要がある場合はどうなりますか?*
  • Jersey Client APIのヘッダーの詳細については、次のセクションに一緒に行きましょう。*

5. Jersey Client APIのヘッダー

*デフォルトのJerseyトランスポートコネクタの実装は、JDK *の_HttpURLConnection_クラスを使用することを知っておくことが重要です。 このクラスは、一部のヘッダーの使用を制限します。 この制限を回避するために、システムプロパティを設定できます。
System.setProperty("sun.net.http.allowRestrictedHeaders", "true");
制限付きヘッダーのリストはhttps://jersey.github.io/documentation/latest/user-guide.html#d0e4970[Jersey docs]にあります。

5.1. 単純な一般ヘッダー

ヘッダーを定義する最も簡単な方法は、_WebTarget#request_を呼び出して、_header_メソッドを提供する_Invocation.Builder_を取得することです。
public Response simpleHeader(String headerKey, String headerValue) {
    Client client = ClientBuilder.newClient();
    WebTarget webTarget = client.target("https://sse.example.org/");
    Invocation.Builder invocationBuilder = webTarget.request();
    invocationBuilder.header(headerKey, headerValue);
    return invocationBuilder.get();
}
そして、実際には、読みやすくするためにこれを非常にうまく圧縮できます:
public Response simpleHeaderFluently(String headerKey, String headerValue) {
    Client client = ClientBuilder.newClient();

    return client.target("https://sse.example.org/")
      .request()
      .header(headerKey, headerValue)
      .get();
}
ここからは、理解しやすいため、サンプルには流fluentな形式のみを使用します。

5.2. 基本認証

実際、Jersey Client APIは、認証ヘッダーを簡単に送信できる_HttpAuthenticationFeature_クラスを提供します*。
public Response basicAuthenticationAtClientLevel(String username, String password) {
    HttpAuthenticationFeature feature = HttpAuthenticationFeature.basic(username, password);
    Client client = ClientBuilder.newBuilder().register(feature).build();

    return client.target("https://sse.example.org/")
      .request()
      .get();
}
クライアントの構築中に_feature_を登録したため、すべてのリクエストに適用されます。 APIは、基本仕様で必要なユーザー名とパスワードのエンコードを処理します。
**ところで、接続のモードとしてHTTPSを暗示していることに注意してください。 **これは常に価値のあるセキュリティ対策ですが、基本認証を使用する場合は基本です。それ以外の場合、パスワードはプレーンテキストとして公開されます。 Jerseyはhttps://jersey.github.io/documentation/latest/user-guide.html#d0e5313 [より高度なセキュリティ構成]もサポートしています。
また、リクエスト時に信任状を指定することもできます。
public Response basicAuthenticationAtRequestLevel(String username, String password) {
    HttpAuthenticationFeature feature = HttpAuthenticationFeature.basicBuilder().build();
    Client client = ClientBuilder.newBuilder().register(feature).build();

    return client.target("https://sse.example.org/")
      .request()
      .property(HTTP_AUTHENTICATION_BASIC_USERNAME, username)
      .property(HTTP_AUTHENTICATION_BASIC_PASSWORD, password)
      .get();
}

5.3. ダイジェスト認証

Jerseyの_HttpAuthenticationFeature_は、ダイジェスト認証もサポートしています。
public Response digestAuthenticationAtClientLevel(String username, String password) {
    HttpAuthenticationFeature feature = HttpAuthenticationFeature.digest(username, password);
    Client client = ClientBuilder.newBuilder().register(feature).build();

    return client.target("https://sse.example.org/")
      .request()
      .get();
}
そして、同様に、リクエスト時にオーバーライドできます:
public Response digestAuthenticationAtRequestLevel(String username, String password) {
    HttpAuthenticationFeature feature = HttpAuthenticationFeature.digest();
    Client client = ClientBuilder.newBuilder().register(feature).build();

    return client.target("http://sse.example.org/")
      .request()
      .property(HTTP_AUTHENTICATION_DIGEST_USERNAME, username)
      .property(HTTP_AUTHENTICATION_DIGEST_PASSWORD, password)
      .get();
}

5.4. OAuth 2.0を使用したベアラートークン認証

  • OAuth 2.0は、別の認証メカニズムとしてBearerトークンの概念をサポートしています。

    https://search.maven.org/search?q=g:org.glassfish.jersey.security%20AND%20a:oauth2-client&core=gav[Jersey's _oauth2-client_dependency]を使用して_OAuth2ClientSupportFeature_を取得する必要があります。 _HttpAuthenticationFeature_と同様:
<dependency>
    <groupId>org.glassfish.jersey.security</groupId>
    <artifactId>oauth2-client</artifactId>
    <version>2.29</version>
</dependency>
ベアラートークンを追加するには、以前と同様のパターンに従います。
public Response bearerAuthenticationWithOAuth2AtClientLevel(String token) {
    Feature feature = OAuth2ClientSupport.feature(token);
    Client client = ClientBuilder.newBuilder().register(feature).build();

    return client.target("https://sse.examples.org/")
      .request()
      .get();
}
または、リクエストレベルでオーバーライドできます。これは、ローテーションによってトークンが変更された場合に特に便利です。
public Response bearerAuthenticationWithOAuth2AtRequestLevel(String token, String otherToken) {
    Feature feature = OAuth2ClientSupport.feature(token);
    Client client = ClientBuilder.newBuilder().register(feature).build();

    return client.target("https://sse.example.org/")
      .request()
      .property(OAuth2ClientSupport.OAUTH2_PROPERTY_ACCESS_TOKEN, otherToken)
      .get();
}

5.5. OAuth 1.0を使用したベアラートークン認証

第4に、OAuth 1.0を使用するレガシーコードと統合する必要がある場合、https://search.maven.org/search?q = g:org.glassfish.jersey.security%20AND%20a:oauth1-が必要です。 client&core = gav [ジャージーの_oauth1-client_依存関係]:
<dependency>
    <groupId>org.glassfish.jersey.security</groupId>
    <artifactId>oauth1-client</artifactId>
    <version>2.29</version>
</dependency>
OAuth 2.0と同様に、使用できる_OAuth1ClientSupport_があります。
public Response bearerAuthenticationWithOAuth1AtClientLevel(String token, String consumerKey) {
    ConsumerCredentials consumerCredential =
      new ConsumerCredentials(consumerKey, "my-consumer-secret");
    AccessToken accessToken = new AccessToken(token, "my-access-token-secret");

    Feature feature = OAuth1ClientSupport
      .builder(consumerCredential)
      .feature()
      .accessToken(accessToken)
      .build();

    Client client = ClientBuilder.newBuilder().register(feature).build();

    return client.target("https://sse.example.org/")
      .request()
      .get();
}
_OAuth1ClientSupport.OAUTH_PROPERTY_ACCESS_TOKEN_プロパティによってリクエストレベルが再び有効になります。

6. 結論

要約すると、この記事では、フィルターを使用してJerseyのSSEクライアント要求にヘッダーを追加する方法を説明しました。 また、認証ヘッダーの使用方法も具体的に説明しました。
この例のコードはhttps://github.com/eugenp/tutorials/tree/master/jersey[over on GitHub]で入手できます。