Feign client Request Interceptor Pit falls
Feign client has become the norm for http communication in spring based applications. Today we will discus one common pit fall and how to avoid it.
Normally we create a new feign client per service and also create an interceptor per service to add additional information like auth headers, api keys etc.
Service-A client:
@FeignClient(value = "service-A-client", url = "${service-A-client.url}" , configuration = {ServiceARequestInterceptor.class})
public interface ServiceAClient {
@PostMapping(value = "/service-A/v1/resource")
Response getResource(RequestPojo request);
}
Service-A request Interceptor:
public class ServiceARequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
// interceptor logic
}
}
}
similarly we can have multiple feign clients and their request interceptors.
The issue arises when an interceptor is defined as component/service etc such that component scan is creating the bean for it. This component interceptor will be called for all the request of other services as well and will have unexpected results such as sending two different auth headers meant for two different services.
Please read this github thread for more context:
https://github.com/spring-cloud/spring-cloud-netflix/issues/1211
This issue might not arise if we don’t need our interceptors to be spring service or component.
But in case we need to find a auth token from db and then add this in the request , it becomes necessary to create this interceptor as a component so that spring can manage the bean creation and it’s dependencies.
A Better way:
Instead of creating different interceptor for every service , we can create a single interceptor and have our custom factory to provide interceting logic for different services.
CustomRequestInterceptor:
RequestTemplate contains feignTarget object which is basically the feign client that made that request with some other data as well.
We can leverage template.feignTarget().type() to find which client made this request to identify which interceptor logic to apply.
@Component
@RequiredArgsConstructor
public class CommonRequestInterceptor implements RequestInterceptor {
private final InterceptorProvider interceptorProvider;
@Override
public void apply(RequestTemplate template) {
CustomRequestInterceptor interceptor = interceptorProvider.getInterceptorOfClientType(template.feignTarget().type());
if (interceptor != null) {
interceptor.apply(template);
}
}
If we define all our interceptors as component/service we can use spring context as a factory itself because all beans are already there in application context.
public interface CustomRequestInterceptor {
void apply(RequestTemplate template);
Class applyForFeignClientClass();
}
@Component
@RequiredArgsConstructor
public class InterceptorProvider {
private final List<CustomRequestInterceptor> requestInterceptors;
public CustomRequestInterceptor getInterceptorOfClientType(Class clazz) {
return requestInterceptors.stream()
.filter(interceptor -> clazz.equals(interceptor.applyForFeignClientClass()))
.findFirst()
.get();
}
}
@Component
public class ServiceARequestInterceptor implements CustomRequestInterceptor {
@Override
public void apply(RequestTemplate template) {
// interceptor for service A logic
}
@Override
public Class applyForFeignClientClass() {
return ServiceAClient.class;
}
}
So by using above method we can make sure that only the required interceptor will be called for a feign client.
We can change the above code InterceptorProvider to return multiple interceptors for on feign client in case we need to apply multiple interceptors.
This way there is no spring magic involved and there will be no ambiguity.