1. 概要

このチュートリアルでは、Jersey Client APIを使用して、 Server-Sent Event (SSE)クライアントリクエストでヘッダーを送信する簡単な方法を説明します。

また、デフォルトのJerseyトランスポートコネクタを使用して、基本的なキー/値ヘッダー、認証ヘッダー、および制限付きヘッダーを送信する適切な方法についても説明します。

2. ポイントにまっすぐ

おそらく、SSEを使用してヘッダーを送信しようとしているときに、この状況に遭遇した可能性があります。

SseEventSource を使用してSSEを受信しますが、 SseEventSource を構築するには、ヘッダーを追加する方法を提供しないWebTargetインスタンスが必要です。 Clientインスタンスも役に立ちません。 おなじみですか?

ただし、ヘッダーはSSEに関連していませんが、クライアント要求自体に関連していることを覚えておいてください。したがって、実際にそこを調べる必要があります。

次に、ClientRequestFilterで何ができるか見てみましょう。

3. 依存関係

旅を始めるには、Maven pom.xmlファイルにjersey-client依存関係JerseyのSSE依存関係が必要です。

<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 を、クライアントがイベントを消費するための架空のエンドポイントとして使用します。 実際には、これを、クライアントに使用させたい実際の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エンドポイントに送信する必要がある場合はどうでしょうか。

次のセクションに進んで、JerseyClientAPIのヘッダーについて詳しく学びましょう。

5. ジャージークライアントAPIのヘッダー

デフォルトのJerseyトランスポートコネクタの実装では、JDKのHttpURLConnectionクラスが使用されることを知っておくことが重要です。 このクラスは、一部のヘッダーの使用を制限します。 この制限を回避するために、システムプロパティを設定できます。

System.setProperty("sun.net.http.allowRestrictedHeaders", "true");

制限されたヘッダーのリストは、 Jerseydocsにあります。

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();
}

ここからは、理解しやすいように、サンプルには流暢な形式のみを使用します。

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();
}

クライアントの構築時に機能を登録したため、すべてのリクエストに適用されます。 APIは、基本仕様で必要なユーザー名とパスワードのエンコードを処理します。

ちなみに、接続のモードとしてHTTPSを暗示していることに注意してください。 これは常に価値のあるセキュリティ対策ですが、基本認証を使用する場合は基本です。そうでない場合、パスワードはプレーンテキストとして公開されます。 ジャージーは、より高度なセキュリティ構成もサポートしています。

これで、リクエスト時にクレジットを指定することもできます。

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. ダイジェスト認証

ジャージーの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. OAuth2.0を使用したベアラートークン認証

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

HttpAuthenticationFeatureに類似したOAuth2ClientSupportFeatureを提供するには、Jerseyのoauth2-client依存関係が必要です。

<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. OAuth1.0を使用したベアラートークン認証

第4に、OAuth 1.0を使用するレガシーコードと統合する必要がある場合は、Jerseyの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. 結論

要約すると、この記事では、フィルターを使用してジャージーのSSEクライアント要求にヘッダーを追加する方法について説明しました。 また、認証ヘッダーの操作方法についても具体的に説明しました。

この例のコードは、GitHubから入手できます。