1. Overview
1.概述
Sometimes we need to set request headers in our HTTP calls when using Feign. Feign allows us to build HTTP clients simply with a declarative syntax.
在使用Feign时,有时我们需要在我们的HTTP调用中设置请求头信息。Feign允许我们用声明式的语法简单地构建HTTP客户端。
In this short tutorial, we’ll see how to configure the request headers using annotations. We’ll also see how to include common request headers by using interceptors.
在这个简短的教程中,我们将看到如何使用注解来配置请求头信息。我们还将看到如何通过使用拦截器来包含常见的请求头信息。
2. Example
2.例子
Throughout this tutorial, we’ll be using an example Bookstore Application that exposes REST API endpoints.
在本教程中,我们将使用一个例子书店应用程序暴露了 REST API 端点。
We can easily clone the project and run it locally:
我们可以很容易地克隆该项目并在本地运行。
$ mvn install spring-boot:run
Let’s deep dive into the client-side implementation.
让我们深入研究一下客户端的实现。
3. Using the Header Annotation
3.使用标题注释
Let’s think of a scenario where specific API calls should always contain a static header. In this situation, we might configure that request header as part of the client. A typical example is to include a Content-Type header.
让我们想一想,特定的API调用应该总是包含一个静态头。在这种情况下,我们可能将该请求头配置为客户端的一部分。一个典型的例子是包含一个Content-Type头。。
Using the @Header annotation, we can easily configure a static request header. We can define this header value either statically or dynamically.
使用@Header 注解,我们可以轻松地配置一个静态请求头。我们可以静态地或动态地定义这个头的值。
3.1. Setting Static Header Value
3.1.设置静态标头值
Let’s configure two static headers, i.e., Accept-Language and Content-Type, into the BookClient:
让我们在BookClient中配置两个静态头信息,即Accept-Language和Content-Type,。
@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);
}
In the above code, the header Accept-Language is included in all APIs since it is applied to the BookClient. However, the create method has an additional Content-Type header.
在上述代码中,头文件Accept-Language被包含在所有API中,因为它被应用到BookClient。然而,create方法有一个额外的Content-Type标题。
Next, let’s see how to create the BookClient using Feign’s Builder method and passing the HEADERS log level:
接下来,让我们看看如何使用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");
Now, let’s test the create method:
现在,让我们测试一下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();
Then, let’s verify the headers in the output logger:
然后,让我们验证一下输出记录器中的头信息。
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
We should note that if the header name is the same in both the client interface and the API method, they will not override each other. Instead, the request will include all such values.
我们应该注意,如果头的名称在客户接口和API方法中都是相同的,它们将不会互相覆盖。相反,请求将包括所有这些值。
3.2. Setting Dynamic Header Value
3.2.设置动态标头值
Using the @Header annotation, we can also set a dynamic header value. For this, we need to express the value as a placeholder.
使用@Header注解,我们也可以设置一个动态的标头值。为此,我们需要将该值表达为一个占位符。
Let’s include the x-requester-id header into the BookClient with a placeholder of requester:
让我们把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);
}
Here we made the x-requester-id a variable that’s passed into each method. We used the @Param annotation to match the name of the variable. It’s expanded at runtime to satisfy the header specified by the @Headers annotation.
这里我们让x-requester-id成为传入每个方法的变量。我们使用@Param annotation来匹配变量的名称。它在运行时被展开,以满足@Headers 注释所指定的标题。
Now, let’s call the BookClient API with the x-requester-id header:
现在,让我们用x-requester-id头来调用BookClient API。
String requester = "test";
book = bookClient.findByIsbn(requester, isbn).getBook();
Then, let’s verify the request header in the output logger:
然后,让我们验证一下输出记录器中的请求头。
18:04:27.515 [main] DEBUG c.b.f.c.h.s.parameterized.BookClient - [BookClient#findByIsbn] x-requester-id: test
4. Using the HeaderMaps Annotation
4.使用HeaderMaps注释
Let’s imagine a scenario where the header keys and values are all dynamic. In this situation, the range of possible keys is unknown ahead of time. Also, the headers may vary between different method calls on the same client. A typical example would be setting certain metadata headers.
让我们想象一个场景,头的键和值都是动态的。在这种情况下,可能的键的范围是提前未知的。另外,在同一个客户端的不同方法调用之间,头信息可能会有所不同。一个典型的例子是设置某些元数据头。
Using a Map parameter annotated with @HeaderMap sets the dynamic headers:
使用注有@HeaderMap的Map参数来设置动态头文件。
@RequestLine("POST")
void create(@HeaderMap Map<String, Object> headers, Book book);
Now, let’s try to test the create method with the header map:
现在,让我们试着用头图测试一下create方法。
Map<String,Object> headerMap = new HashMap<>();
headerMap.put("metadata-key1", "metadata-value1");
headerMap.put("metadata-key2", "metadata-value2");
bookClient.create(headerMap, book);
Then, let’s verify the headers in the output logger:
然后,让我们验证一下输出记录器中的头信息。
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. Request Interceptors
5.要求拦截器
Interceptors can perform various implicit tasks like logging or authentication for every request or response.
拦截器可以执行各种隐性任务,如对每个请求或响应进行记录或认证。
Feign provides a RequestInterceptor interface. With this, we can add request headers.
Feign提供了一个RequestInterceptor接口。有了这个接口,我们可以添加请求头。
It makes sense to add a request interceptor when it’s known that the header should be included in every call. This pattern removes the dependency of the invoking code to implement non-functional requirements like authentication or tracing.
当知道头信息应该包含在每个调用中时,添加一个请求拦截器是有意义的。这种模式消除了调用代码对实现非功能需求的依赖性,如认证或追踪。
Let’s try this out by implementing an AuthorisationService which we’ll use to generate the authorization token:
让我们通过实现一个AuthorisationService来试试,我们将用它来生成授权令牌。
public class ApiAuthorisationService implements AuthorisationService {
@Override
public String getAuthToken() {
return "Bearer " + UUID.randomUUID();
}
}
Now, let’s implement our custom request interceptor:
现在,让我们来实现我们的自定义请求拦截器。
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());
}
}
We should note that request interceptors can read, remove or mutate any part of the request template.
我们应该注意,请求拦截器可以读取、移除或突变请求模板的任何部分。
Now, let’s add the AuthInterceptor to the BookClient using the builder method:
现在,让我们使用builder方法将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");
Then, let’s test the BookClient API with the Authorisation header:
然后,让我们用Authorisation头来测试BookClientAPI。
bookClient.findByIsbn("0151072558").getBook();
Now, let’s verify the header in the output logger:
现在,让我们验证一下输出记录器中的标题。
18:06:06.135 [main] DEBUG c.b.f.c.h.staticheader.BookClient - [BookClient#findByIsbn] Authorisation: Bearer 629e0af7-513d-4385-a5ef-cb9b341cedb5
Multiple Request interceptors can also be applied to the Feign client. Though no guarantees are given concerning the order that they are applied.
多个请求拦截器也可以应用到Feign客户端。虽然我们不能保证它们的应用顺序。
6. Conclusion
6.结语
In this article, we’ve discussed how Feign client supports setting request headers. We implemented that using the @Headers, @HeaderMaps annotation, and request interceptors.
在这篇文章中,我们已经讨论了Feign客户端如何支持设置请求头信息。我们使用@Headers、@HeaderMaps注解和请求拦截器来实现。
As always, all code samples shown in this tutorial are available over on GitHub.
一如既往,本教程中显示的所有代码样本都可以在GitHub上获得。