1. 概述
有时,我们需要在使用Feign时在HTTP调用中设置请求标头。Feign允许我们使用声明性语法简单地构建HTTP客户端。
在这个简短的教程中,我们将了解如何使用注解配置请求标头。我们还将了解如何使用拦截器来包含常见的请求标头。
2. 示例
在本教程中,我们将使用公开REST API端点的示例书店应用程序。
我们可以轻松克隆项目并在本地运行它:
$ mvn install spring-boot:run
让我们深入探讨客户端实现。
3. 使用@Headers注解
让我们考虑一个场景,其中特定的API调用应始终包含静态标头。在这种情况下,我们可以将该请求标头配置为客户端的一部分。一个典型的例子是包含一个Content-Type头。
使用@Header注解,我们可以轻松配置静态请求头。我们可以静态或动态地定义此标头值。
3.1 设置静态标头值
让我们在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);
}
在上面的代码中,标头Accept-Language包含在所有API中,因为它应用于BookClient。但是,create方法有一个额外的Content-Type标头。
接下来,让我们看看如何使用Feign的构建器方法创建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");
现在,让我们测试创建方法:
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.t.t.f.c.h.staticheader.BookClient - [BookClient#create] Accept-Language: en-US
18:01:15.039 [main] DEBUG c.t.t.f.c.h.staticheader.BookClient - [BookClient#create] Content-Type: application/json
18:01:15.096 [main] DEBUG c.t.t.f.c.h.staticheader.BookClient - [BookClient#findByIsbn] Accept-Language: en-US
我们应该注意,如果客户端接口和API方法中的标头名称相同,则它们不会相互覆盖。相反,该请求将包括所有此类值。
3.2 设置动态标头值
使用@Headers注解,我们还可以设置动态标头值。为此,我们需要将值表示为占位符。
让我们将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注解指定的标头。
现在,让我们使用x-requester-id标头调用BookClient API:
String requester = "test";
book = bookClient.findByIsbn(requester, isbn).getBook();
然后,让我们在输出记录器中验证请求标头:
18:04:27.515 [main] DEBUG c.t.t.f.c.h.s.parameterized.BookClient - [BookClient#findByIsbn] x-requester-id: test
4. 使用@HeaderMap注解
让我们想象一个场景,其中标头键和值都是动态的。在这种情况下,可能的键范围是提前未知的。此外,标头在同一客户端上的不同方法调用之间可能会有所不同。一个典型的例子是设置某些元数据标头。
使用带有@HeaderMap注解的Map参数设置动态标头:
@RequestLine("POST")
void create(@HeaderMap Map<String, Object> headers, Book book);
现在,让我们尝试使用标头map测试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.t.t.f.c.h.dynamicheader.BookClient - [BookClient#create] metadata-key1: metadata-value1
18:05:03.202 [main] DEBUG c.t.t.f.c.h.dynamicheader.BookClient - [BookClient#create] metadata-key2: metadata-value2
5. 请求拦截器
拦截器可以为每个请求或响应执行各种隐式任务,如日志记录或身份验证。
Feign提供了一个RequestInterceptor接口。有了这个,我们可以添加请求标头。
当已知标头应包含在每个调用中时,添加请求拦截器是有意义的。此模式消除了调用代码对实现非功能性需求(如身份验证或跟踪)的依赖性。
让我们通过实现一个AuthorisationService来尝试一下,我们将使用它来生成授权令牌:
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");
然后,让我们使用Authorization标头测试BookClient API:
bookClient.findByIsbn("0151072558").getBook();
现在,让我们验证输出记录器中的标头:
18:06:06.135 [main] DEBUG c.t.t.f.c.h.staticheader.BookClient - [BookClient#findByIsbn] Authorisation: Bearer 629e0af7-513d-4385-a5ef-cb9b341cedb5
Feign客户端可以应用多个请求拦截器,但是不保证它们的应用顺序。
6. 总结
在本文中,我们讨论了Feign客户端如何支持设置请求标头。我们使用@Headers、@HeaderMaps注解和请求拦截器实现了这一点。
与往常一样,本教程的完整源代码可在GitHub上获得。