Перейти к основному содержимому

Настройка заголовков запроса с помощью Feign

· 5 мин. чтения

1. Обзор

Иногда нам нужно установить заголовки запросов в наших HTTP-вызовах при использовании Feign . Feign позволяет нам создавать HTTP-клиенты с простым декларативным синтаксисом.

В этом кратком руководстве мы увидим, как настроить заголовки запроса с помощью аннотаций. Мы также увидим, как включать общие заголовки запросов с помощью перехватчиков.

2. Пример

В этом руководстве мы будем использовать пример приложения книжного магазина , которое предоставляет конечные точки REST API.

Мы можем легко клонировать проект и запускать его локально:

$ mvn install spring-boot:run

Давайте углубимся в реализацию на стороне клиента.

3. Использование аннотации заголовка

Давайте подумаем о сценарии, в котором определенные вызовы API всегда должны содержать статический заголовок. В этой ситуации мы можем настроить этот заголовок запроса как часть клиента. Типичным примером является включение заголовка Content-Type .

Используя аннотацию @Header , мы можем легко настроить статический заголовок запроса. Мы можем определить это значение заголовка либо статически, либо динамически.

3.1. Установка значения статического заголовка

Давайте настроим два статических заголовка, т. е. Accept-Language и Content-Type, в 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);
}

В приведенном выше коде заголовок Accept-Language включен во все API, поскольку он применяется к BookClient . Однако метод create имеет дополнительный заголовок Content-Type .

Далее давайте посмотрим, как создать BookClient , используя метод Feign Builder и передав уровень журнала 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");

Теперь давайте протестируем метод создания :

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 в BookClient с заполнителем requester : ``

@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 .

Теперь давайте вызовем BookClient API с заголовком x-requester-id :

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

Давайте представим сценарий, в котором все ключи и значения заголовков являются динамическими. В этой ситуации диапазон возможных ключей заранее неизвестен. Кроме того, заголовки могут различаться между разными вызовами методов на одном и том же клиенте. Типичным примером может быть установка определенных заголовков метаданных.

Использование параметра Map с аннотацией @HeaderMap устанавливает динамические заголовки :

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

Следует отметить, что перехватчики запросов могут читать, удалять или видоизменять любую часть шаблона запроса.

Теперь давайте добавим AuthInterceptor в BookClient с помощью метода компоновщика :

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");

Затем давайте протестируем BookClient API с заголовком 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 .