Retry feign client properites - spring-boot

I need to retry feign call for certain http status code and after 3 second for maximum 4 time.
Is there any properties that i can define on my application.yml or i need to write my custom Retryer that implement Retry interface

Feign has a build in Retryer however you can not configure the Retryer via application.yml. I guess the Spring Boot Team assumed that people would use the deprecated Hystrix project for this matter.
Instead of configuring Feign by config you could write a bit of code:
https://cloud.spring.io/spring-cloud-openfeign/reference/html/index.html#creating-feign-clients-manually
In addition you have to map the corresponding status code to RetryableException using a custom ErrorDecoder.
public class CustomErrorDecoder implements ErrorDecoder {
private final ErrorDecoder errorDecoder = new Default();
#Override
public Exception decode(String methodKey, Response response) {
Exception exception = defaultErrorDecoder.decode(s, response);
if(exception instanceof RetryableException){
return exception;
}
if(response.status() == 499){
return new RetryableException("499 blub", response.request().httpMethod(), null );
}
return exception;
}
}
public class Example {
public static void main(String[] args) {
MyApi myApi = Feign.builder()
.errorDecoder(new CustomErrorDecoder())
.target(MyApi.class, "https://api.hostname.com");
}
}

You can use retryable annotation.
Ex: You can throw custom exception when http status code is equal to 404
#Service
public interface MyService {
#Retryable(value = CustomException.class, maxAttempts = 2, backoff = #Backoff(delay = 100))
void retry(String str) throws CustomException;
}

Related

Testing a camel route that calls an http server

In my camel context I have a route defined as follows:
from("direct:getPets")
.routeId("getPets")
.to("http://localhost:4321/pets")
The route works well and makes a call to the server. I'd like to test this route and check the headers but I have issues.
This is a spring project so my test class looks as follows:
#RunWith(CamelSpringBootRunner.class)
#DirtiesContext(classMode = DirtiesContext.ClassMode.After_EACH_TEST_METHO)
#UseAdviceWith
public class TestRestEndpoint {
#Autowired
private CamelContext camelContext;
#Produce(uri = "direct:getPets")
private ProducerTemplate callServer;
#EndpointInject(uri = "mock:catchTestEndpoint")
#Test
public void should_return_json() throws Exception {
camelContext.getRouteDefinitions("getPets").adviceWith(camelContext, new AdviceWithRouteBuilder() {
#Override
public void configure() throws Exception {
weaveAddLast().to("mock:catchTestEndpoint");
}
});
camelContext.start();
callServer.sendBody("");
mockEndpoint.expectedMessageCount(1);
mockEndpoint.assertIsSatisfied();
}
}
The exchange fails and returns a null pointer.
org.apache.camel.CamelExecutionException: Exception occurred during execution on the exchange: Exchange[ID-PVJ-DEV97-03-2-1600178584697-0-1]
Sounds like your appended Mock is null.
If the code in your question really is your code, this is probably because you have a typo in your endpoint URI.
Notice the extra t between "catch" and "Test" in mock:catchtTestEndpoint

Catching exception Feign

I want to handle any exception from feign client, even if service is not available. However I can not catch them using try/catch. This is my feign client:
#FeignClient(name = "api-service", url ="localhost:8888")
public interface ClientApi extends SomeApi {
}
Where api is:
#Path("/")
public interface SomeApi {
#GET
#Path("test")
String getValueFromApi();
}
Usage of client with try/catch:
#Slf4j
#Service
#AllArgsConstructor
public class SampleController implements SomeApi {
#Autowired
private final ClientApi clientApi;
#Override
public String getValueFromApi() {
try {
return clientApi.getValueFromApi();
} catch (Throwable e) {
log.error("CAN'T CATCH");
return "";
}
}
}
Dependencies are in versions:
spring-boot 2.2.2.RELEASE
spring-cloud Hoxton.SR1
Code should work according to How to manage Feign errors?.
I received few long stack traces among them exceptions are :
Caused by: java.net.ConnectException: Connection refused (Connection refused)
Caused by: feign.RetryableException: Connection refused (Connection refused) executing GET http://localhost:8888/test
Caused by: com.netflix.hystrix.exception.HystrixRuntimeException: ClientApi#getValueFromApi() failed and no fallback available.
How to properly catch Feign exeptions, even if client service (in this case localhost:8888) is not available?
Ps. When feign client service is available it works, ok. I am just focused on the exceptions aspect.
A better way to handle the situation where your service is not available is to use a circuit breaker pattern. Fortunately, it is easy using Netflix Hystrix as an implementation of the circuit breaker pattern.
First of all, you need to enable Hystrix for feign clients in application configuration.
application.yml
feign:
hystrix:
enabled: true
Then you should write a fallback class for the specified feign client interface.
In this case getValueFormApi method in fallback class will act mostly like catch block that you wrote(with exception when circuit will be in open state and original method will not be attempted).
#Component
public class ClientApiFallback implements ClientApi {
#Override
public String getValueFromApi(){
return "Catch from fallback";
}
}
Lastly, you just need to specify the fallback class for your feign client.
#FeignClient(name = "api-service", url ="localhost:8888", fallback = ClientApiFallback.class)
public interface ClientApi extends SomeApi {
}
That way your method getValueFromApi is fail safe. If,
for any reason, any uncaught exceptions escape from getValueFromApi the ClientApiFallback method will be called.
To enable circuit breaker and also configure your application to deal with unexpected errors, you need to:
1.- Enable the circuit breaker itself
#SpringBootApplication
#EnableFeignClients("com.perritotutorials.feign.client")
#EnableCircuitBreaker
public class FeignDemoClientApplication {
2.- Create your fallback bean
#Slf4j
#Component
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class PetAdoptionClientFallbackBean implements PetAdoptionClient {
#Setter
private Throwable cause;
#Override
public void savePet(#RequestBody Map<String, ?> pet) {
log.error("You are on fallback interface!!! - ERROR: {}", cause);
}
}
Some things you must keep in mind for fallback implementations:
Must be marked as #Component, they are unique across the application.
Fallback bean should have a Prototype scope because we want a new one to be created for each exception.
Use constructor injection for testing purposes.
3.- Your ErrorDecoder, to implement fallback startegies depending on the HTTP error returned:
public class MyErrorDecoder implements ErrorDecoder {
private final ErrorDecoder defaultErrorDecoder = new Default();
#Override
public Exception decode(String methodKey, Response response) {
if (response.status() >= 400 && response.status() <= 499) {
return new MyCustomBadRequestException();
}
if (response.status() >= 500) {
return new RetryableException();
}
return defaultErrorDecoder.decode(methodKey, response);
}
}
4.- In your configuration class, add the Retryer and the ErrorDecoder into the Spring context:
#Bean
public MyErrorDecoder myErrorDecoder() {
return new MyErrorDecoder();
}
#Bean
public Retryer retryer() {
return new Retryer.Default();
}
You can also add customization to the Retryer:
class CustomRetryer implements Retryer {
private final int maxAttempts;
private final long backoff;
int attempt;
public CustomRetryer() {
this(2000, 5); //5 times, each 2 seconds
}
public CustomRetryer(long backoff, int maxAttempts) {
this.backoff = backoff;
this.maxAttempts = maxAttempts;
this.attempt = 1;
}
public void continueOrPropagate(RetryableException e) {
if (attempt++ >= maxAttempts) {
throw e;
}
try {
Thread.sleep(backoff);
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
}
}
#Override
public Retryer clone() {
return new CustomRetryer(backoff, maxAttempts);
}
}
If you want to get a functional example about how to implement Feign in your application, read this article.

Error creating bean with name 'scopedTarget.oauth2ClientContext': Scope 'request' is not active for the current thread for feign client

I am calling another microservice once my current microservice is up and ready using feign client in my current microservice built using Jhipster.
So my Feign Interface is
package com.persistent.integration.client;
import java.util.List;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import com.persistent.integration.service.dto.DataPipelineDTO;
#AuthorizedFeignClient(name = "Integrationconfiguration")
public interface DataPipelinesResourceFeign {
#RequestMapping(value = "/api/data-pipelines", method = RequestMethod.GET)
List<DataPipelineDTO> getAllDataPipelines(#RequestParam(value = "pageable") Pageable pageable );
}
}
And I have implemented ApplicationRunner where I have called feign client method.
#Component
public class ApplicationInitializer implements ApplicationRunner {
#Autowired
private DataPipelinesResourceFeign dataPipelinesResourceFeign;
#Autowired
private ActiveMQListener activeMqListener;
#Override
public void run(ApplicationArguments args) throws Exception {
// TODO Auto-generated method stub
Pageable pageable = PageRequest.of(0, 20);
try {
List <DataPipelineDTO> allStartedDataPipeLines = dataPipelinesResourceFeign.getAllDataPipelines(pageable); //.stream().filter(p->p.getState().equals(State.STARTED)).collect(Collectors.toList());
allStartedDataPipeLines.forEach(datapipe ->
{
try {
activeMqListener.consume(datapipe);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
});
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
But after running this, it gives below exception at dataPipelinesResourceFeign.getAllDataPipelines :
com.netflix.hystrix.exception.HystrixRuntimeException: DataPipelinesResourceFeign#getAllDataPipelines(Pageable) failed and no fallback available.
at com.netflix.hystrix.AbstractCommand$22.call(AbstractCommand.java:819)
at com.netflix.hystrix.AbstractCommand$22.call(AbstractCommand.java:804)
at rx.internal.operators.OperatorOnErrorResumeNextViaFunction$4.onError(OperatorOnErrorResumeNextViaFunction.java:140)
at rx.internal.operators.OnSubscribeDoOnEach$DoOnEachSubscriber.onError(OnSubscribeDoOnEach.java:87)
at rx.internal.operators.OnSubscribeDoOnEach$DoOnEachSubscriber.onError(OnSubscribeDoOnEach.java:87)
at com.netflix.hystrix.AbstractCommand$DeprecatedOnFallbackHookApplication$1.onError(AbstractCommand.java:1472)
Caused by: org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'scopedTarget.oauth2ClientContext':
Scope 'request' is not active for the current thread; consider
defining a scoped proxy for this bean if you intend to refer to it
from a singleton; nested exception is java.lang.IllegalStateException:
No thread-bound request found: Are you referring to request attributes
outside of an actual web request, or processing a request outside of
the originally receiving thread? If you are actually operating within
a web request and still receive this message, your code is probably
running outside of DispatcherServlet/DispatcherPortlet: In this case,
use RequestContextListener or RequestContextFilter to expose the
current request. at
org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(Abstrac>tBeanFactory.java:362)
at
org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractB>eanFactory.java:199)
at
org.springframework.aop.target.SimpleBeanTargetSource.getTarget(SimpleBeanTarge>tSource.java:35)
at
org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.>java:193)
at com.sun.proxy.$Proxy147.getAccessToken(Unknown Source) at
com.persistent.integration.security.oauth2.AuthorizationHeaderUtil.getAuthoriza>tionHeaderFromOAuth2Context(AuthorizationHeaderUtil.java:28)
at
com.persistent.integration.client.TokenRelayRequestInterceptor.apply(TokenRelay>RequestInterceptor.java:23)
at
feign.SynchronousMethodHandler.targetRequest(SynchronousMethodHandler.java:158)
at
feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:88)
at
feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:76)
at
feign.hystrix.HystrixInvocationHandler$1.run(HystrixInvocationHandler.java:108)
at com.netflix.hystrix.HystrixCommand$2.call(HystrixCommand.java:302)
at com.netflix.hystrix.HystrixCommand$2.call(HystrixCommand.java:298)
at
rx.internal.operators.OnSubscribeDefer.call(OnSubscribeDefer.java:46)
... 68 more Caused by: java.lang.IllegalStateException: No
thread-bound request found: Are you referring to request attributes
outside of an actual web request, or processing a request outside of
the originally receiving thread? If you are actually operating within
a web request and still receive this message, your code is probably
running outside of DispatcherServlet/DispatcherPortlet: In this case,
use RequestContextListener or RequestContextFilter to expose the
current request. at
org.springframework.web.context.request.RequestContextHolder.currentRequestAttr>ibutes(RequestContextHolder.java:131)
at
org.springframework.web.context.request.AbstractRequestAttributesScope.get(Abst>ractRequestAttributesScope.java:42)
at
org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(Abstrac>tBeanFactory.java:350)
many suggestions on internet were to add listerner RequestContextListener. But problem persisted even if I added listener in webConfigurer.java in onStartup method.
{
servletContext.addListener(RequestContextListener.class);
}
But of no use.
Any leads would be appreciated.
I found a workaround for this. I don't know why TokenRelayRequestIntercepton isn't working but you can use your own RequestInterceptor based on Spring's SecurityContext.
First, define a RequestInterceptor :
public class MyRequestInterceptor implements RequestInterceptor {
public static final String AUTHORIZATION = "Authorization";
public static final String BEARER = "Bearer";
public MyRequestInterceptor() {
super();
}
#Override
public void apply(RequestTemplate template) {
// demander un token à keycloak et le joindre à la request
Optional<String> header = getAuthorizationHeader();
if (header.isPresent()) {
template.header(AUTHORIZATION, header.get());
}
}
public static Optional<String> getAuthorizationHeader() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.getDetails() != null && authentication.getDetails() instanceof OAuth2AuthenticationDetails) {
OAuth2AuthenticationDetails oAuth2AuthenticationDetails =
(OAuth2AuthenticationDetails) authentication.getDetails();
return Optional.of(String.format("%s %s", oAuth2AuthenticationDetails.getTokenType(),
oAuth2AuthenticationDetails.getTokenValue()));
} else {
return Optional.empty();
}
}
}
and then, declare a config class for your feign client using your RequestInterceptor, it should contains something like this :
#Bean(name = "myRequestInterceptor")
public RequestInterceptor getMyRequestInterceptor() throws IOException {
return new MyRequestInterceptor();
}
Your Feign client shoud look like this:
#FeignClient(name = "SERVICE_NAME", configuration = MyFeignConfiguration.class)
public interface MyRestClient {
I had the same issue with Feign Client running on startup using ApplicationRunner and I came up with following solution.
I defined my FeignClientsConfiguration with OAuth2FeignRequestInterceptor, which accepts predefined bean DefaultOAuth2ClientContext and OAuth2 configuration OAuth2ProtectedResourceDetails:
#Configuration
public class MyConfig extends FeignClientsConfiguration {
#Bean
public RequestInterceptor oauth2FeignRequestInterceptor( DefaultOAuth2ClientContext oAuth2ClientContext, MyOauth2Properties properties) {
return new OAuth2FeignRequestInterceptor(oAuth2ClientContext, resourceDetails(properties));
}
#Bean
public DefaultOAuth2ClientContext oAuth2ClientContext() {
return new DefaultOAuth2ClientContext();
}
private OAuth2ProtectedResourceDetails resourceDetails(MyOauth2Properties oauth2Properties) {
ResourceOwnerPasswordResourceDetails resourceDetails = new ResourceOwnerPasswordResourceDetails();
resourceDetails.setAccessTokenUri(oauth2Properties.getAccessTokenUri());
resourceDetails.setUsername(oauth2Properties.getUsername());
resourceDetails.setPassword(oauth2Properties.getPassword());
resourceDetails.setClientId(oauth2Properties.getClientId());
return resourceDetails;
}
}
Your feign client will look something like this:
#FeignClient(url = "http://localhost:8080/api/v1")
public interface FeignClient {
}
After all this, calling FeignClient from ApplicationRunner.run() works fine.
Spring Boot 2.2.6

Hystrix and Spring #Async in combination

I'm using Hystrix library for the Spring Boot project (spring-cloud-starter-hystrix). I have a #Service class annotated with #HystrixCommand and it works as expected.
But, when I add the method annotated with #Async in that same service class then the Hystrix doesn't work, and fallback method is never called. What could cause this problem and how to fix it?
This is the Application class:
#EnableCircuitBreaker
#EnableHystrixDashboard
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
This is the service class:
#Service
public class TemplateService {
#HystrixCommand(
fallbackMethod = "getGreetingFallback",
commandProperties = {#HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500")}
)
public String getGreeting() {
URI uri = URI.create("http://localhost:8090/greeting");
ResponseEntity<String> response = restTemplate.exchange(uri, HttpMethod.GET, null, String.class);
if (response.getStatusCode().equals(HttpStatus.OK)) {
return response.getBody();
} else {
return null;
}
}
public String getGreetingFallback(Throwable e) {
return null;
}
#Async
public void async(String message) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
logger.info(MessageFormat.format("Received async message {0}", message));
}
}
#EnableAsync annotation is placed in a different class annotated with #Configuration, where I set some other Thread Executor options from properties file.
Given the code for TemplateService (which doesn't implement interface) and assuming the defaults on #EnableAsync it is safe to concur that CGLIB proxies are created by spring.
Thus the #HystrixCommand annotation on getGreeting() isn't inherited by the service proxy class; which explains the reported behavior.
To get past this error keep the #HystrixCommand and #Async method separated in different service because enabling JDK proxies will also not help and I am not sure about AspectJ mode.
Refer this for further information on Spring proxy mechanism.

Spring Boot Testing: exception in REST controller

I have a Spring Boot application and want to cover my REST controllers by integration test.
Here is my controller:
#RestController
#RequestMapping("/tools/port-scan")
public class PortScanController {
private final PortScanService service;
public PortScanController(final PortScanService portScanService) {
service = portScanService;
}
#GetMapping("")
public final PortScanInfo getInfo(
#RequestParam("address") final String address,
#RequestParam(name = "port") final int port)
throws InetAddressException, IOException {
return service.scanPort(address, port);
}
}
In one of test cases I want to test that endpoint throws an exception in some circumstances. Here is my test class:
#RunWith(SpringRunner.class)
#WebMvcTest(PortScanController.class)
public class PortScanControllerIT {
#Autowired
private MockMvc mvc;
private static final String PORT_SCAN_URL = "/tools/port-scan";
#Test
public void testLocalAddress() throws Exception {
mvc.perform(get(PORT_SCAN_URL).param("address", "192.168.1.100").param("port", "53")).andExpect(status().isInternalServerError());
}
}
What is the best way to do that? Current implementation doesn't handle InetAddressException which is thrown from PortScanController.getInfo() and when I start test, I receive and error:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is com.handytools.webapi.exceptions.InetAddressException: Site local IP is not supported
It is not possible to specify expected exception in #Test annotation since original InetAddressException is wrapped with NestedServletException.
Spring Boot Test package comes with AssertJ that has very convenient way of verifying thrown exceptions.
To verify cause:
#Test
public void shouldThrowException() {
assertThatThrownBy(() -> methodThrowingException()).hasCause(InetAddressException .class);
}
There are also few more methods that you may be interested in. I suggest having a look in docs.
In order to test the wrapped exception (i.e., InetAddressException), you can create a JUnit Rule using ExpectedException class and then set the expectMessage() (received from NestedServletException's getMessage(), which contains the actual cause), you can refer the below code for the same:
#Rule
public ExpectedException inetAddressExceptionRule = ExpectedException.none();
#Test
public void testLocalAddress() {
//Set the message exactly as returned by NestedServletException
inetAddressExceptionRule.expectMessage("Request processing failed; nested exception is com.handytools.webapi.exceptions.InetAddressException: Site local IP is not supported");
//or you can check below for actual cause
inetAddressExceptionRule.expectCause(org.hamcrest.Matchers.any(InetAddressException.class))
//code for throwing InetAddressException here (wrapped by Spring's NestedServletException)
}
You can refer the ExpectedException API here:
http://junit.org/junit4/javadoc/4.12/org/junit/rules/ExpectedException.html
You could define an exception handler
#ExceptionHandler(InetAddressException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public Response handledInvalidAddressException(InetAddressException e)
{
log e
return getValidationErrorResponse(e);
}
and then in your test you could do
mvc.perform(get(PORT_SCAN_URL)
.param("address", "192.168.1.100")
.param("port", "53"))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.response").exists())
.andExpect(jsonPath("$.response.code", is(400)))
.andExpect(jsonPath("$.response.errors[0].message", is("Site local IP is not supported")));
I had the same issue and i fix it with org.assertj.core.api.Assertions.assertThatExceptionOfType :
#Test
public void shouldThrowInetAddressException() {
assertThatExceptionOfType(InetAddressException.class)
.isThrownBy(() -> get(PORT_SCAN_URL).param("address", "192.168.1.100").param("port", "53"));
}
I hope it's help you !

Resources