How to add a custom interceptor to FeignClient in SpringBoot - spring-boot

In RestTemplate I have a custom interceptor which will log some request response details and saves to database.
my custom Interceptor:
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
#Component
public class LogServices implements ClientHttpRequestInterceptor {
#Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
final String uri = request.getURI().toString();
final ClientHttpResponse response = execution.execute(request, body);
//log request response details and save to database
return response;
RestTemplate bean configuration in springboot:
#Bean
public RestTemplate restTemplate(final RestTemplateBuilder restTemplateBuilder) {
return restTemplateBuilder
.setConnectTimeout(Duration.ofMillis(connectTimeout))
.setReadTimeout(Duration.ofMillis(readTimeout))
.build();
Add the interceptor to restTemplate bean:
#Configuration
public class LogInterceptorConfiguration {
#Autowired
public void configureLogger(final RestTemplate restTemplate, final LogServices logServices) {
final var interceptors = restTemplate.getInterceptors();
interceptors.add(logServices);
restTemplate.setInterceptors(interceptors);
}
How can I add this interceptor to FeignClient?
In application.yml:
feign:
client:
config:
default:
connectTimeout: 5000
readTimeout: 5000
request-interceptors[0]: com.api.restclient.InterceptorOne
request-interceptors[1]: com.api.log.LogServices
InterceptorOne which adds a header to every request in feign client:
#Configuration
public class InterceptorOne implements RequestInterceptor {
#Override
public void apply(RequestTemplate requestTemplate) {
requestTemplate.header("some-header", "value");
}
But I cannot add the LogServices interceptor since it does not work due to the error cannot be cast to class feign.RequestInterceptor
My guess is that the interceptor I am trying to add is a generic interceptor and not specifically request interceptor. So I want to know how do I add a generic interceptor to FeignClient similar to RestTemplate

You can add multiple interceptors as follows
feign:
client:
config:
feignName:
connectTimeout: 5000
readTimeout: 5000
loggerLevel: full
errorDecoder: com.example.SimpleErrorDecoder
retryer: com.example.SimpleRetryer
requestInterceptors:
- com.example.InterceptorOne
- com.example.LogServices
decode404: false
encoder: com.example.SimpleEncoder
decoder: com.example.SimpleDecoder
contract: com.example.SimpleContract

Related

How to mock external rest services when writing integration test in spring boot

I have a controller from which gateway(Spring integration) is being called. Inside gateway I have several flows where I'm doing some outboundgateway calls. I've written my integration test as below -
#Tag("integrationtest")
#ExtendWith(SpringExtension.class)
#SpringBootTest(
classes = MyWebApplication.class,
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class IntegrationTest {
#LocalServerPort private int port;
TestRestTemplate testRestTemplate = new TestRestTemplate();
HttpHeaders headers = new HttpHeaders();
#Test
void testEntireApplication() {
HttpEntity<LoanProvisionRequest> entity =
new HttpEntity(TestHelper.generateValidLionRequest(), headers);
ResponseEntity<LoanProvisionResponse> response =
testRestTemplate.exchange(
createURLWithPort("/provision"), HttpMethod.POST, entity, LionResponse.class);
assertEquals(1, response.getBody().getASMCreditScoreResultCd());
}
private String createURLWithPort(String uri) {
return "http://localhost:" + port + "/lion-service/v1/decisions" + uri;
}
}
It's running the application and proceeding through from controller to the gateway and running the flows as expected. But for the outboundgateway calls it's failing by saying Caused by: org.springframework.web.client.ResourceAccessException: I/O error on POST request for "http://someurl" because it's not able to access the url that's used in the outboundgateway. I want to stub/mock those url somehow. How do I do that?
I tried doing something below in the same class to mock the url -
MockRestServiceServer mockServer;
#BeforeEach
void setUp() throws JsonProcessingException {
RestTemplate restTemplate = new RestTemplate();
mockServer = MockRestServiceServer.bindTo(restTemplate).build();
DecisionResponse decisionResponse = new DecisionResponse();
creditDecisionResponse.setId("0013478");
creditDecisionResponse.setResponse(null);
creditDecisionResponse.setDescription("dummy Response");
mockServer
.expect(
requestTo(
"http://xyz-some-url:8080/some-other-service/v1/do-decisions/decision"))
.andExpect(method(HttpMethod.POST))
.andRespond(
withStatus(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON)
.body(new ObjectMapper().writeValueAsString(decisionResponse )));
mockServer.verify();
}
But still the same error showing and somehow it's not getting called when it's hitting the outboundgateway call inside the gateway flows.
below is the controller code -
public ResponseEntity<LionResponse> getLionsNames(
#RequestBody final #Valid LionRequest req,
BindingResult bindingResult,
#RequestHeader HttpHeaders httpHeaders)
throws JsonProcessingException {
Long dbId = new SequenceGenerator().nextId();
lionsGateway.processLionRequest(
MessageBuilder.withPayload(req).build(),
dbId,
SourceSystem.ONE.getSourceSystemCode()));
below is the gateway -
#MessagingGateway
public interface LoansGateway {
#Gateway(requestChannel = "flow.input")
List<Object> processLoanRequest(
#Payload Message lionRequest,
#Header("dbID") Long dbID,
#Header("sourceSystemCode") String sourceSystemCode);
}
below is the SpringIntegrationConfiguration class -
#Bean
public IntegrationFlow flow() {
return flow ->
flow.handle(validatorService, "validateRequest")
.split()
.channel(c -> c.executor(Executors.newCachedThreadPool()))
.scatterGather(
scatterer ->
scatterer
.applySequence(true)
.recipientFlow(savingLionRequestToTheDB())
.recipientFlow(callingANativeMethod())
.recipientFlow(callingAExternalService()),
gatherer -> gatherer.outputProcessor(prepareCDRequest()))
.gateway(getDecision(), f -> f.errorChannel("lionDecisionErrorChannel"))
.to(getDataResp());
}
public IntegrationFlow callingAExternalService() {
return flow ->
flow.handle(
Http.outboundGateway(externalServiceURL)
.httpMethod(HttpMethod.POST)
.expectedResponseType(String.class))
.logAndReply("Cd response");
}
.... same way I have other flows that are using outboundgateway but I've not wired the Restemplate instance anywhere.
So, you do in your mock server setup:
RestTemplate restTemplate = new RestTemplate();
mockServer = MockRestServiceServer.bindTo(restTemplate).build();
And that's it. The mocked RestTemplate instance is not used anywhere.
The HttpRequestExecutingMessageHandler has a configuration based on the RestTemplate:
/**
* Create a handler that will send requests to the provided URI using a provided RestTemplate.
* #param uri The URI.
* #param restTemplate The rest template.
*/
public HttpRequestExecutingMessageHandler(String uri, RestTemplate restTemplate) {
So, you just need to instrument exactly that RestTemplate which you provide for your HTTP outbound gateway.
Right now your mocking code is dead end.

How can I use RequestHeader with actuator endpoint?

I have customized my actuator/info endpoint and I want to use information from the header to authorize a RestTemplate call to another service.
I am implementing the InfoContributor as here:
https://www.baeldung.com/spring-boot-info-actuator-custom
I want to accept request headers in the contribute() method. For any user defined REST endpoint, I can define a #RequestHeader parameter and access headers.
But unfortunately, the InfoContributor's contribute() method takes only one parameter.
How can I access a request header inside the contribute() method?
You can autowire HttpServletRequest into your InfoContributor
import javax.servlet.http.HttpServletRequest;
#Component
public class Custom implements InfoContributor {
#Autowired
private HttpServletRequest request;
#Override
public void contribute(Info.Builder builder) {
...
request.getHeader("your header");
...
}
}
Or you can use RequestContextHolder to get hold of it
#Component
public class Custom implements InfoContributor {
#Override
public void contribute(Info.Builder builder) {
...
HttpServletRequest request =
((ServletRequestAttributes)RequestContextHolder.getRequestAttributes())
.getRequest();
request.getHeader("your header");
...
}
}

FeignClient is passing on headers

I have about 10 microservices all built with Spring boot 2 using Eureka and FeignClients. My microservices use certain header values to keep track of data so when a FeignClient is used it needs to pass on certain values that are in the incoming request. So if Microservice 1 does a call to Microservice 2 it must pass on the headers from the incoming request onto microservice 2. I haven't been able to find out how I can do that. I understand their is #Header however if you have 20 FeignClients then you don't want to have to manually add the #header to all the FeignClients. Can you indicate that FeignClients must read a certain header from the incoming request and pass it on in the FeignClient?
You can use request interceptor in Feign.
Example Implementation:
Request Interceptor:
#Component
public class MyRequestInterceptor implements RequestInterceptor {
#Override
public void apply(RequestTemplate template) {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
String authorization = requestAttributes.getRequest().getHeader(HttpHeaders.AUTHORIZATION);
if(null != authorization) {
template.header(HttpHeaders.AUTHORIZATION, authorization);
}
}
}
Bean Configuration:
#Configuration
public class CustomFeignConfig {
#Bean
public Contract feignContract() {
return new feign.Contract.Default();
}
#Bean
public MyRequestInterceptor basicAuthRequestInterceptor() {
return new MyRequestInterceptor();
}
#Bean
public OkHttpClient client() {
return new OkHttpClient();
}
}

Spring Cloud Gateway pass bean to custom filter

We are attempting to use Spring Cloud Gateway to setup a microservice based architecture. Currently, we have defined a route programatically:
#ServletComponentScan
#SpringBootApplication
public class GatewayApplication {
// to be passed to and used by custom filter
#Autowired
RestTemplate restTemplate;
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class, args);
}
#Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
.route("status", r -> r
.method(HttpMethod.GET)
.and()
.path("/status")
.filters(f -> f.rewritePath("/status", "/v2/status")
.filter(new AuthorizationFilter(restTemplate).apply(new Config(""))))
.uri("http://localhost:8081/"))
.build();
}
}
The above would route an incoming request /status via GET to another endpoint. We would like to apply a custom filter, which we have implemented in AuthorizationFilter. This filter, as the name implies, is another microservice which will either allow or deny an incoming request based on credentials and permissions.
Currently, the pattern we are following, which works, is to inject a Spring RestTemplate into the gateway class above, and then to pass this RestTemplate to the constructor of the filter.
However, how can this be done if we wanted to switch to using a YAML file for defining all the routes? Presumably in both cases Spring would be constructing a new filter for each incoming request. But in the case of YAML, how can we pass something in the construtor? If this cannot be done, is there any other way to inject a RestTemplate, or any other resource into a custom Spring gateway filter?
You can register your own custom GatewayFilterFactory. This allows you to provide a custom configuration, and within that configuration, you can use SpEL to reference a bean.
For example:
#Component
public class AuthenticationGatewayFilterFactory extends AbstractGatewayFilterFactory<AuthenticationGatewayFilterFactory.Config> {
public AuthenticationGatewayFilterFactory() {
super(Config.class);
}
#Override
public GatewayFilter apply(Config config) {
// TODO: Implement
}
public static class Config {
private RestTemplate restTemplate;
// TODO: Getters + Setters
}
}
Now you can use SpEL to properly reference a RestTemplate bean:
spring:
cloud:
gateway:
routes:
- id: status
uri: http://localhost:8081/
filters:
- name: Authentication
args:
restTemplate: "#{#nameOfRestTemplateBean}"
predicates:
- Path=/status
Alternatively, you could inject a RestTemplate bean within your gateway filter. For example:
#Component
public class AuthenticationGatewayFilterFactory extends AbstractGatewayFilterFactory<AuthenticationGatewayFilterFactory.Config> {
private RestTemplate restTemplate;
public AuthenticationGatewayFilterFactory(RestTemplate restTemplate) {
super(Config.class);
this.restTemplate = restTemplate;
}
#Override
public GatewayFilter apply(Config config) {
// TODO: Implement
}
public static class Config {
// TODO: Implement
}
}
The code/configuration necessary to do the inject is less complex, but it also makes it more difficult if you ever decide to put AuthenticationGatewayFilterFactory in a separate library, as the "consumers" of this library won't have any control over which RestTemplate is being injected.

How to Create or configure Rest Template using #Bean in Spring Boot

I want to define RestTemplate as an application bean using #Bean annotation in my configuration class in a spring boot application.
I am calling 4 rest services in different places in my application flow. Currently I am creating RestTemplate every time every request. Is there a way I can define that as application bean using #Bean and inject that using #Autowired?
Main reason for this question is I can able to define RestTemplate using #Bean but when I inject it with #Autowired I am loosing all defined interceptors (Interceptors are not getting called.)
Configuration Class
#Bean(name = "appRestClient")
public RestTemplate getRestClient() {
RestTemplate restClient = new RestTemplate(
new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()));
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<ClientHttpRequestInterceptor>();
interceptors.add(new RestServiceLoggingInterceptor());
restClient.setInterceptors(interceptors);
return restClient;
}
Service Class
public class MyServiceClass {
#Autowired
private RestTemplate appRestClient;
public String callRestService() {
// create uri, method response objects
String restResp = appRestClient.getForObject(uri, method, response);
// do something with the restResp
// return String
}
}
It seems my Interceptors are not getting called at all with this configuration. But RestTemplate is able to make a call to the REST service and get a response.
Answer for Spring boot 2.*.* version.
I am using Spring boot 2.1.2.RELEASE and I also added RestTemplate in my project in a class where mail method exists.
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.setConnectTimeout(Duration.ofMillis(300000))
.setReadTimeout(Duration.ofMillis(300000)).build();
}
and Used in my service or other classes like this
#Autowired
RestTemplate res;
and in methods
HttpEntity<String> entity = new HttpEntity<>(str, headers);
return res.exchange(url, HttpMethod.POST, entity, Object.class);
Judging form the name of the interceptor, I'm guessing you're doing some logging in it? You could of missed logging level configuration. I created a small application to check weather your configuration works, using 1.3.6.RELEASE version.
In this class I define the RestTemplate bean and the interceptor with logging.
package com.example;
// imports...
#SpringBootApplication
public class TestApplication {
private static final Logger LOGGER = LoggerFactory.getLogger(TestApplication.class);
public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
#Bean(name = "appRestClient")
public RestTemplate getRestClient() {
RestTemplate restClient = new RestTemplate(
new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()));
// Add one interceptor like in your example, except using anonymous class.
restClient.setInterceptors(Collections.singletonList((request, body, execution) -> {
LOGGER.debug("Intercepting...");
return execution.execute(request, body);
}));
return restClient;
}
}
For logging to work, I also have to set the correct debug level in application.properties.
logging.level.com.example=DEBUG
Then I create a service where I inject this RestTemplate.
#Service
public class SomeService {
private final RestTemplate appRestClient;
#Autowired
public SomeService(#Qualifier("appRestClient") RestTemplate appRestClient) {
this.appRestClient = appRestClient;
}
public String callRestService() {
return appRestClient.getForObject("http://localhost:8080", String.class);
}
}
And also an endpoint to test this out.
#RestController
public class SomeController {
private final SomeService service;
#Autowired
public SomeController(SomeService service) {
this.service = service;
}
#RequestMapping(value = "/", method = RequestMethod.GET)
public String testEndpoint() {
return "hello!";
}
#RequestMapping(value = "/test", method = RequestMethod.GET)
public String test() {
return service.callRestService();
}
}
By performing a GET request to http://localhost:8080/test I should expect to get the String hello! getting printed (the service makes a call to http://localhost:8080 which returns hello! and sends this back to me). The interceptor with logger also prints out Intercepting... in the console.
Edd's solution won't work if you're using Spring Boot 1.4.0 or later. You will have to use RestTemplateBuilder to get this working. Here is the example
#Bean(name="simpleRestTemplate")
#Primary
public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder){
RestTemplate template = restTemplateBuilder.requestFactory(new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()))
.interceptors(logRestRequestInterceptor) //This is your custom interceptor bean
.messageConverters(new MappingJackson2HttpMessageConverter())
.build();
return template;
}
Now you can autowire the bean into your service class
#Autowired
#Qualifier("simpleRestTemplate")
private RestTemplate simpleRestTemplate;
Hope this helps

Resources