1. 概要

Feign を使用する場合、HTTP呼び出しでリクエストヘッダーを設定する必要がある場合があります。 Feignを使用すると、宣言型構文を使用してHTTPクライアントを簡単に構築できます。

この短いチュートリアルでは、アノテーションを使用してリクエストヘッダーを構成する方法を説明します。 また、インターセプターを使用して一般的なリクエストヘッダーを含める方法についても説明します。

2. 例

このチュートリアル全体を通して、例を使用します 書店アプリケーション これはRESTAPIエンドポイントを公開します。 

プロジェクトのクローンを簡単に作成して、ローカルで実行できます。

$ mvn install spring-boot:run

クライアント側の実装について詳しく見ていきましょう。

3. ヘッダーアノテーションの使用

特定のAPI呼び出しに常に静的ヘッダーが含まれている必要があるシナリオを考えてみましょう。 この状況では、その要求ヘッダーをクライアントの一部として構成する場合があります。 典型的な例は、Content-Typeヘッダーを含めることです。

@Header アノテーションを使用すると、静的リクエストヘッダーを簡単に構成できます。 このヘッダー値は、静的または動的に定義できます。

3.1. 静的ヘッダー値の設定

Accept-LanguageContent-Type、の2つの静的ヘッダーをBookClientに構成しましょう。

@Headers("Accept-Language: en-US")
public interface BookClient {
    
    @RequestLine("GET /{isbn}")
    BookResource findByIsbn(@Param("isbn") String isbn);

    @RequestLine("POST")
    @Headers("Content-Type: application/json")
    void create(Book book);
}

上記のコードでは、ヘッダー 受け入れる-言語 に適用されるため、すべてのAPIに含まれています BookClient。 しかし 作成 メソッドには追加があります コンテンツタイプ ヘッダ。

次に、FeignのBuilderメソッドを使用してBookClientを作成し、HEADERSログレベルを渡す方法を見てみましょう。

Feign.builder()
  .encoder(new GsonEncoder())
  .decoder(new GsonDecoder())
  .logger(new Slf4jLogger(type))
  .logLevel(Logger.Level.HEADERS)
  .target(BookClient.class, "http://localhost:8081/api/books");

それでは、createメソッドをテストしてみましょう。

String isbn = UUID.randomUUID().toString();
Book book = new Book(isbn, "Me", "It's me!", null, null);
        
bookClient.create(book);

book = bookClient.findByIsbn(isbn).getBook();

次に、出力ロガーのヘッダーを確認しましょう。

18:01:15.039 [main] DEBUG c.b.f.c.h.staticheader.BookClient - [BookClient#create] Accept-Language: en-US
18:01:15.039 [main] DEBUG c.b.f.c.h.staticheader.BookClient - [BookClient#create] Content-Type: application/json
18:01:15.096 [main] DEBUG c.b.f.c.h.staticheader.BookClient - [BookClient#findByIsbn] Accept-Language: en-US

ヘッダー名がクライアントインターフェースとAPIメソッドの両方で同じである場合、それらは互いにオーバーライドしないことに注意する必要があります。 代わりに、リクエストにはそのような値がすべて含まれます。

3.2. 動的ヘッダー値の設定

@Header アノテーションを使用して、動的ヘッダー値を設定することもできます。 このためには、プレースホルダーとして値を表現する必要があります。

x-requester-id ヘッダーを、 requester のプレースホルダーを使用して、BookClientに含めましょう。

@Headers("x-requester-id: {requester}")
public interface BookClient {
   
    @RequestLine("GET /{isbn}")
    BookResource findByIsbn(@Param("requester") String requester, @Param("isbn") String isbn);
}

ここでは、 x-requester-idを各メソッドに渡される変数にしました。@Paramアノテーションを使用して変数 @Headersアノテーションで指定されたヘッダーを満たすために実行時に拡張されます。

それでは、 x-requester-idheaderを使用してBookClientAPIを呼び出しましょう。

String requester = "test";
book = bookClient.findByIsbn(requester, isbn).getBook();

次に、出力ロガーのリクエストヘッダーを確認しましょう。

18:04:27.515 [main] DEBUG c.b.f.c.h.s.parameterized.BookClient - [BookClient#findByIsbn] x-requester-id: test

4. HeaderMapsアノテーションの使用

ヘッダーのキーと値がすべて動的であるシナリオを想像してみましょう。 この状況では、可能なキーの範囲は事前に不明です。 また、ヘッダーは、同じクライアントでの異なるメソッド呼び出し間で異なる場合があります。 典型的な例は、特定のメタデータヘッダーを設定することです。

@HeaderMap で注釈が付けられたMapパラメーターを使用すると、動的ヘッダーが設定されます。

@RequestLine("POST")
void create(@HeaderMap Map<String, Object> headers, Book book);

それでは、ヘッダーマップを使用してcreateメソッドをテストしてみましょう。

Map<String,Object> headerMap = new HashMap<>();
    	
headerMap.put("metadata-key1", "metadata-value1");
headerMap.put("metadata-key2", "metadata-value2");
    	
bookClient.create(headerMap, book);

次に、出力ロガーのヘッダーを確認しましょう。

18:05:03.202 [main] DEBUG c.b.f.c.h.dynamicheader.BookClient - [BookClient#create] metadata-key1: metadata-value1
18:05:03.202 [main] DEBUG c.b.f.c.h.dynamicheader.BookClient - [BookClient#create] metadata-key2: metadata-value2

5. インターセプターをリクエストする

インターセプターは、すべての要求または応答に対して、ロギングや認証などのさまざまな暗黙のタスクを実行できます。

Feignは、RequestInterceptorインターフェイスを提供します。 これにより、リクエストヘッダーを追加できます。

ヘッダーをすべての呼び出しに含める必要があることがわかっている場合は、要求インターセプターを追加するのが理にかなっています。 このパターンは、認証やトレースなどの非機能要件を実装するための呼び出しコードの依存関係を取り除きます。

AuthorizationService を実装してこれを試してみましょう。これを使用して、認証トークンを生成します。

public class ApiAuthorisationService implements AuthorisationService {

    @Override
    public String getAuthToken() {
        return "Bearer " + UUID.randomUUID();
    }
}

それでは、カスタムリクエストインターセプターを実装しましょう。

public class AuthRequestInterceptor implements RequestInterceptor {
	
    private AuthorisationService authTokenService;
   
    public AuthRequestInterceptor(AuthorisationService authTokenService) {
        this.authTokenService = authTokenService;
    }

    @Override
    public void apply(RequestTemplate template) {
        template.header("Authorisation", authTokenService.getAuthToken());
    }
}

リクエストインターセプターは、リクエストテンプレートの任意の部分を読み取り、削除、または変更できることに注意してください。

次に、 builder メソッドを使用して、AuthInterceptorBookClientに追加しましょう。

Feign.builder()
  .requestInterceptor(new AuthInterceptor(new ApiAuthorisationService()))
  .encoder(new GsonEncoder())
  .decoder(new GsonDecoder())
  .logger(new Slf4jLogger(type))
  .logLevel(Logger.Level.HEADERS)
  .target(BookClient.class, "http://localhost:8081/api/books");

次に、 BookClientAPIをAuthorizationヘッダーでテストしてみましょう。

bookClient.findByIsbn("0151072558").getBook();

次に、出力ロガーのヘッダーを確認しましょう。

18:06:06.135 [main] DEBUG c.b.f.c.h.staticheader.BookClient - [BookClient#findByIsbn] Authorisation: Bearer 629e0af7-513d-4385-a5ef-cb9b341cedb5

複数のリクエストインターセプターをFeignクライアントに適用することもできます。 適用される順序については保証されませんが。

6. 結論

この記事では、Feignクライアントがリクエストヘッダーの設定をサポートする方法について説明しました。 @Headers @HeaderMaps アノテーションを使用して実装し、インターセプターをリクエストしました。

いつものように、このチュートリアルに示されているすべてのコードサンプルは、GitHubからで入手できます。