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 .