Fallback method is not being called when rest call is failed by using feign client - microservices

I am trying to implement fallback by using Feign client but not getting success.Its a simplest code Please find below.
Main Class
#SpringBootApplication
#EnableDiscoveryClient
#RestController
#EnableFeignClients
public class EurekaClient1Application {
#Autowired
public DiscoveryClient discoveryClient;
public static void main(String[] args) {
SpringApplication.run(EurekaClient1Application.class, args);
}
#Autowired
FeingInterface feingInterface;
#GetMapping("/hi/{name}")
public String test(#PathVariable String name)
{
String h = feingInterface.test(name);
return h;
}
}
Feign interface
#FeignClient(name="client22",fallback=FallBack.class)
public interface FeingInterface {
#GetMapping("/hiname/{name}")
public String test(#PathVariable("name") String name);
}
fallback class
#Component
class FallBack implements FeingInterface{
#Override
public String test(String name) {
// TODO Auto-generated method stub
return "fall back methord being called";
}
}
Getting Error in rest client but not from fallback method
"timestamp": 1501950134118,
"status": 500,
"error": "Internal Server Error",
"exception": "java.lang.RuntimeException",
"message": "com.netflix.client.ClientException: Load balancer does not have available server for client: client22",
To get the fallback method message I passed client22 eureka id which is not there in eureka server. I have stater-feign in pom. Can someone look into this.

Fallbacks are actually not handled by Feign itself, but by a circuit breaker. So, you need to put Hystrix (which is the Netflix circuit breaker) on your classpath and enable it in your application.yml file like this:
feign:
hystrix:
enabled: true
If you're using 'cloud:spring-cloud-starter-openfeign' in your build.gradle or pom.xml file, Hystrix should be automatically on your classpath.

Related

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.

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.

Testing Hystrix fallback through Feign API: com.netflix.client.ClientException: Load balancer does not have available server for client

When testing the Hystrix fallback behavior of my Feign API, I get an error, when I expect it to succeed.
Feign interface:
This is the api to the external service.
#FeignClient(name = "book", fallback = BookAPI.BookAPIFallback.class)
public interface BookAPI {
#RequestMapping("/")
Map<String, String> getBook();
#Component
class BookAPIFallback implements BookAPI {
#Override
#RequestMapping("/")
public Map<String, String> getBook() {
Map<String, String> fallbackmap = new HashMap<>();
fallbackmap.put("book", "fallback book");
return fallbackmap;
}
}
}
Test class
This test exists just to verify fallback behavior:
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment = NONE)
public class BookServiceClientTest {
#MockBean
RestTemplate restTemplate;// <---- #LoadBalanced bean
#Autowired
private BookServiceClient bookServiceClient;
#Before
public void setup() {
when(restTemplate.getForObject(anyString(), any()))
.thenThrow(new RuntimeException("created a mock failure"));
}
#Test
public void fallbackTest() {
assertThat(bookServiceClient.getBook())
.isEqualTo(new BookAPI.BookAPIFallback().getBook().get("book")); // <--- I thought this should work
}
}
config files
application.yml
These files show configuration that might be relevant:
feign:
hystrix:
enabled: true
test/application.yml
eureka:
client:
enabled: false
The Question
Everything works fine when running the apps.
But when running this test, I get the below error.
Naturally, it's a test, so I'm trying to bypass the lookup anyway.
java.lang.RuntimeException: com.netflix.client.ClientException: Load balancer does not have available server for client: book
at org.springframework.cloud.netflix.feign.ribbon.LoadBalancerFeignClient.execute(LoadBalancerFeignClient.java:71)
at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:97)
What am I missing?
Addendums
Application class
#SpringBootApplication
#EnableCircuitBreaker
#EnableDiscoveryClient
#EnableFeignClients
public class LibraryApplication {
public static void main(String[] args) {
SpringApplication.run(LibraryApplication.class, args);
}
}
LibraryController
#Controller
public class LibraryController {
private final BookServiceClient bookService;
public LibraryController(BookServiceClient bookServiceClient) {
this.bookService = bookServiceClient;
}
#GetMapping("/")
String getLibrary(Model model) {
model.addAttribute("msg", "Welcome to the Library");
model.addAttribute("book", bookService.getBook());
return "library";
}
}
There are no other classes.
so! I was able to recreate the issue, thanks for adding more code, had to play about with it a tad as I was unsure what the BookClientService looked like and it wouldn't make sense for it to implement the BookAPI as that would be an internal call e.g. in your application and not an external API call with Feign.
Anyway,
I pushed my version of what you provided here.
https://github.com/Flaw101/feign-testing
The issue was resolved when I renamed the second application.yml which lives in the src/test/resources folder to application-test.yml which will merge the properties.
The issue was caused by the fact the second property source, the testing one, overrides the initial application.yml and disables hystrix, because Hystrix is disabled there is no fallback to go to and it throws the root cause of what would cause the fallback, a lack of a server to call to for the Book API. Renaming it to application-test will always be loaded into spring test contexts. You could resolve it with the use of inlined properties or profiles.
I've added another test disabling feign /w hystrix within the test which re-creates the error you are recieving.

Spring Microservices issue with #HystrixCommand

We are facing a problem with Hystrix Command in a Spring Boot / Cloud microservice. We have a Spring Component containing a method annotated with #RabbitListener. When a new message arrives, the method delegates the invocation to NotificationService::processNotification().
The NotificationService is a bean annotated with #Service. The method processNotification() can request third party applications. We want to wrap the invocation of third party applications using #HystrixCommand to provide fault tolerance, but due to some reasons the Hystrix Command annotated method is not working.
If we invoke a Controller and the Controller delegates the invocation to a Service method, which in turns have a Hystrix Command , everything works perfectly. The only problem with Hystrix Command arises when the microservices consume a messages and it seems to be Hystrix Command doesn’t trigger the fallback method.
Here is the non-working code:
#Component
public class MessageProcessor {
#Autowired
private NotificationService notificationService;
#RabbitListener(queues = "abc.xyz-queue")
public void onNewNotification(String payload) {
this.notificationService.processNotification(payload);
}
}
#Service
public class NotificationService {
public void processNotification(String payload) {
...
this.notifyThirdPartyApp(notificationDTO);
...
}
#HystrixCommand(fallbackMethod = "notifyThirdPartyAppFallback")
public void notifyThirdPartyApp(NotificationDTO notificationDTO) {
//Do stuff here that could fail
}
public void notifyThirdPartyAppFallback(NotificationDTO notificationDTO) {
// Fallbacl impl goes here
}
}
#SpringBootApplication
#EnableCaching
#EnableCircuitBreaker
#EnableDiscoveryClient
#EnableRabbit
public class NotificationApplication {
public static void main(String[] args) {
SpringApplication.run(NotificationApplication.class, args);
}
}
I'm not sure about your problem without looking at the code.
As another approach you can take: instead of describing this calls with annotations in your service, just extend HystrixCommand and implement api calling logic in it (read more):
public class CommandHelloWorld extends HystrixCommand<String> {
private final String name;
public CommandHelloWorld(String name) {
super(HystrixCommandGroupKey.Factory.asKey("ExampleGroup"));
this.name = name;
}
#Override
protected String run() {
// a real example would do work like a network call here
return "Hello " + name + "!";
}
}

Apache Camel Spring Javaconfig Unit Test No consumers available on endpoint

I have the following route configuration:
#Component
public class MyRoute extends RouteBuilder {
#Override
public void configure() throws Exception {
from("direct:in").to("direct:out");
}
}
When I try to test it:
#RunWith(CamelSpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { MyRouteTest.TestConfig.class }, loader = CamelSpringDelegatingTestContextLoader.class)
#MockEndpoints
public class MyRouteTest {
#EndpointInject(uri = "mock:direct:out")
private MockEndpoint mockEndpoint;
#Produce(uri = "direct:in")
private ProducerTemplate producerTemplate;
#Configuration
public static class TestConfig extends SingleRouteCamelConfiguration {
#Bean
#Override
public RouteBuilder route() {
return new MyRoute();
}
}
#Test
public void testRoute() throws Exception {
mockEndpoint.expectedBodiesReceived("Test Message");
producerTemplate.sendBody("Test Message");
mockEndpoint.assertIsSatisfied();
}
}
I get this exception:
org.apache.camel.component.direct.DirectConsumerNotAvailableException:
No consumers available on endpoint: Endpoint[direct://out].
Exchange[Message: Test Message]
It looks like the Mock is not picking up the message from the endpoint.
What am I doing wrong?
The problem is that mock endpoints just intercept the message before delegating to the actual endpoint. Quoted from the docs:
Important: The endpoints are still in action. What happens differently
is that a Mock endpoint is injected and receives the message first and
then delegates the message to the target endpoint. You can view this
as a kind of intercept and delegate or endpoint listener.
The solution to your problem is to tell certain endpoints (the ones that expect a consumer in your case) not to delegate to the actual endpoint. This can easily be done using #MockEndpointsAndSkip instead of #MockEndpoints:
#RunWith(CamelSpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { MyRouteTest.TestConfig.class }, loader = CamelSpringDelegatingTestContextLoader.class)
#MockEndpointsAndSkip("direct:out") // <-- turns unit test from red to green ;)
public class MyRouteTest {
// ....
}
This issue because, in your route configuration, there is no route with "direct:out" consumer endpoint.
add a line like some thing below,
from("direct:out").("Anything you want to log");
So that direct:out will consume the exchange and In your test, mock will be able check the received text without any issues. Hope this helps !!

Resources