Feign client обработка ошибок

I have some fiegn client to send request other micro service.

@FeignClient(name="userservice")
public interface UserClient {

    @RequestMapping(
        method= RequestMethod.GET,
        path = "/userlist"
    )
    String getUserByid(@RequestParam(value ="id") String id);
}

Now I am sending request like this

try {
    String responseData = userClient.getUserByid(id);
    return responseData;
} catch(FeignException e) {
    logger.error("Failed to get user", id);
} catch (Exception e) {
    logger.error("Failed to get user", id);
}

Here the problem is if any FeignException happens I don’t get any error code.

I need to send a corresponding error codes in other APIS to send to caller

So how to extract the error code? I want to extract error code and build a responseEntity

I got this code but dont know how exactly I can use in my function.

aSemy's user avatar

aSemy

4,8942 gold badges23 silver badges46 bronze badges

asked Mar 6, 2019 at 10:05

scoder's user avatar

I’m late to party but here are my 2 cents. We had same use case to handle exceptions based on error code and we used custom ErrorDecoder.

public class CustomErrorDecoder implements ErrorDecoder {

    @Override
    public Exception decode(String methodKey, Response response) {
        String requestUrl = response.request().url();
        Response.Body responseBody = response.body();
        HttpStatus responseStatus = HttpStatus.valueOf(response.status());

        if (responseStatus.is5xxServerError()) {
            return new RestApiServerException(requestUrl, responseBody);
        } else if (responseStatus.is4xxClientError()) {
            return new RestApiClientException(requestUrl, responseBody);
        } else {
            return new Exception("Generic exception");
        }
    }
}

Return @Bean of above class in FeignClientConfiguration class.

public class MyFeignClientConfiguration {

    @Bean
    public ErrorDecoder errorDecoder() {
        return new CustomErrorDecoder();
    }
}

Use this as your config class for FeignClient.

@FeignClient(
    value = "myFeignClient", 
    configuration = MyFeignClientConfiguration.class
)

Then you can handle these exceptions using GlobalExceptionHandler.

aSemy's user avatar

aSemy

4,8942 gold badges23 silver badges46 bronze badges

answered May 22, 2021 at 15:07

VaibS's user avatar

VaibSVaibS

1,5371 gold badge14 silver badges20 bronze badges

1

Not the same issue, but this helped in my situation.
OpenFeign’s FeignException doesn’t bind to a specific HTTP status (i.e. doesn’t use Spring’s @ResponseStatus annotation), which makes Spring default to 500 whenever faced with a FeignException. That’s okay because a FeignException can have numerous causes that can’t be related to a particular HTTP status.

However you can change the way that Spring handles FeignExceptions. Simply define an ExceptionHandler that handles the FeignException

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(FeignException.class)
    public String handleFeignStatusException(FeignException e, HttpServletResponse response) {
        response.setStatus(e.status());
        return "feignError";
    }

}

This example makes Spring return the same HTTP status that you received

aSemy's user avatar

aSemy

4,8942 gold badges23 silver badges46 bronze badges

answered May 21, 2019 at 14:59

Srinath's user avatar

SrinathSrinath

1571 silver badge3 bronze badges

5

did you try to implement FallbackFactory on your feign client ?

https://cloud.spring.io/spring-cloud-netflix/multi/multi_spring-cloud-feign.html#spring-cloud-feign-hystrix-fallback

On the create method, before return, you can retrieve the http status code with this snippet :

String httpStatus = cause instanceof FeignException ? Integer.toString(((FeignException) cause).status()) : "";

Exemple :

    @FeignClient(name="userservice", fallbackFactory = UserClientFallbackFactory.class)
    public interface UserClient {
    
        @RequestMapping(
                method= RequestMethod.GET,
                          path = "/userlist")
        String getUserByid(@RequestParam(value ="id") String id);
    
    }
    
    
    @Component
    static class UserClientFallbackFactory implements FallbackFactory<UserClient> {
        @Override
        public UserClient create(Throwable cause) {
    
         String httpStatus = cause instanceof FeignException ? Integer.toString(((FeignException) cause).status()) : "";
    
         return new UserClient() {
            @Override
            public String getUserByid() {
                logger.error(httpStatus);
                // what you want to answer back (logger, exception catch by a ControllerAdvice, etc)
            }
        };
    }
   }

ikhvjs's user avatar

ikhvjs

5,2242 gold badges12 silver badges35 bronze badges

answered Mar 6, 2019 at 22:59

rphlmr's user avatar

rphlmrrphlmr

7185 silver badges12 bronze badges

2

I’m kinda late too, but I wanted to make sure you check out other potential solutions as well.

Feign with Spring has a notoriously bad way of handling exceptions so I came up with a custom solution that creates this robust environment to define your business exceptions the way you want.

It’s utilizing a custom ErrorDecoder registered to the Feign clients and adds the possibility to customize your exception handling based on method or class level.

Check it out: Maintainable error handling with Feign clients? Not a dream anymore

answered Dec 3, 2021 at 19:57

Arnold Galovics's user avatar

Arnold GalovicsArnold Galovics

3,1963 gold badges21 silver badges31 bronze badges

1

There’s a ErrorDecored interface for that like it says in the documentation

The answer above about FallbackFactory is viable but falls into some other layer of abstraction.

leaqui's user avatar

leaqui

5336 silver badges22 bronze badges

answered Aug 26, 2020 at 13:54

kpankov's user avatar

1

String errorJson = e.contentUTF8();

HttpStatus status = HttpStatus.valueOf(e.status());

Map<String, List> errorResponse = objectMapper.readValue(errorJson, Map.class);

new ResponseDTO(status,e.status(), errorResponse.get(«errors»).get(0));

answered Apr 6 at 20:43

java code point's user avatar

Over the last couple of years, I’ve been using Feign to invoke HTTP APIs, let it be external or internal. If you are not familiar with Feign, here’s a very brief intro. Feign is a declarative HTTP client. You define an interface, take some magical annotations and you have yourself a fully functioning client that you can use to communicate via HTTP.

Feign is a standalone library, anybody can use it on a project. It comes with its own annotations, types and configuration. Here’s an example client from the docs:

interface GitHub {
  @RequestLine("GET /repos/{owner}/{repo}/contributors")
  List<Contributor> contributors(@Param("owner") String owner, @Param("repo") String repo);

  @RequestLine("POST /repos/{owner}/{repo}/issues")
  void createIssue(Issue issue, @Param("owner") String owner, @Param("repo") String repo);

}

Having a tool to define APIs like this is a great way to reduce application complexity. Think about writing the same thing with Apache HttpComponents. Let me give you an idea:

CloseableHttpClient httpClient = HttpClients.createDefault();
try {
	String owner = ...
	String repo = ...
	HttpGet request = new HttpGet("https://api.github.com/repos/" 
					               + owner + "/" + repo +"/contributors");
	CloseableHttpResponse response = httpClient.execute(request);

	try {
		HttpEntity entity = response.getEntity();
		if (entity != null) {
			String result = EntityUtils.toString(entity);
			Gson gson = new Gson();
			List<Contributor> contributors = gson.fromJson(result, 
							                      new TypeToken<ArrayList<Contributor>>(){}.getType());
			...
		}
	} finally {
		response.close();
	}
} finally {
	httpClient.close();
}

I hope the difference is obvious. There’s tons of boilerplate code in the latter example.

Since Spring is one of the most used base frameworks in any Java project, there’s another level of abstraction that the Spring ecosystem provides. What if you don’t need to rely on the custom Feign annotations but you use the same Spring annotations just like when a controller is defined, e.g. @RequestMapping, @PathVariable and so on.

Well, Spring Cloud adds this capability to your application. The former example with Spring annotations looks the following:

@FeignClient("github")
interface GitHub {
  @RequestMapping(value = "/repos/{owner}/{repo}/contributors", method = GET)
  List<Contributor> contributors(@PathVariable("owner") String owner, @PathVariable("repo") String repo);

  @RequestMapping(value = "/repos/{owner}/{repo}/issues", method = POST)
  void createIssue(Issue issue, @PathVariable("owner") String owner, @PathVariable("repo") String repo);

}

Quite neat compared to the previous examples.

Although, there’s one downside to any abstraction. Or maybe downside is not even the best word to describe it, its rather a trade-off that we – engineers – often forget.

One of the points of an abstraction is to hide details from its users to ease development, which is absolutely spot on in case of Feign. However, the trade-off you are going to make is less control over that particular piece of code. For normal use-cases it’s often perfectly fine but as soon as you hit a wall and you need some specific behavior, you have to start digging. Where? I guess most of us just Google for some time, hoping that somebody has asked a similar question on Stackoverflow. Sometimes it’s just not the case, and you have to jump right into the code to figure out how to work-around the framework.

Error handling in Feign

Fortunately the framework creators have thought about having some way of reacting to errors during an API call. The ErrorDecoder interface is used for that purpose.

public interface ErrorDecoder {
  public Exception decode(String methodKey, Response response);
}

Such an interface implementation can be tied to creating a particular Feign client. In the standard Feign world, you can specify it during the Builder calls, like:

Feign.builder()
.decoder(new CustomDecoder())

So you can customize the Decoder you’d like to use on a per client basis, but not on a method basis. That’s just a limitation of the capabilities the library is providing.

How does this look in the Spring world? If you’d like to use an ErrorDecoder, you can just simply register it as a bean and the framework will automatically pick it up and assign it to every Feign client.

@Configuration
public class CustomFeignConfiguration {
    @Bean
    public CustomDecoder customDecoder() {
        return new CustomDecoder();
    }
}

Very neat, but since within an application there could be several Feign clients used, does it make sense to use a single decoder for all clients? Probably not. But even if you go one level deeper, you might need to have different error handling logic for different API calls within the same client.

The junior implementation

So if you were carefully reading, you might have noticed a parameter in the signature of the ErrorDecoder, methodKey. The methodKey is automatically generated by the Feign library whenever an error response is received from the downstream API. An example methodKey looks the following:

UserServiceClient#findById(UUID)

It starts with the Feign client class name, then a hash symbol and then the name of the method, followed by its parameter types within parentheses. The key generation can be found here: Feign#configKey

The first implementation one might think would be some kind of string magic on the methodKey:

@Override
    public Exception decode(String methodKey, Response response) {
        if (methodKey.startsWith("UserServiceClient")) {
           // dosomething specific to UserServiceClient
        } else if (...) {
           ...
        }
    }

Obviously this is going to work, but it won’t scale and as soon as you rename a class/method, you’ll end up in some functional problems in your application since the String renaming might be easily messed up (even though IDEs are very smart these days).

Testing is going to be hell with an implementation like this, cyclomatic complexity is going to increase with the number of clients and API methods. There must be a solution out there and somebody must have figured it out already.

Well, I thought the same, but so far I haven’t found anything on this.

The proper solution

First of all, this solution is aiming to address Spring only, when using the bare Feign client library, there are some minor tweaks required.

For a Spring Cloud based Feign client, you need to use the @FeignClient annotation on the interface like this:

@FeignClient(name = "user-service", url = "http://localhost:9002")
public interface UserServiceClient {
    @GetMapping("/users/{id}")
    UserResponse findById(@PathVariable("id") UUID id)
}

Pretty easy I’d say. So what happens when there’s an error, for example 404 returned by the API?

2020-09-29 20:31:29.510 ERROR 26412 --- [ctor-http-nio-2] a.w.r.e.AbstractErrorWebExceptionHandler : [c04278db-1]  500 Server Error for HTTP GET "/users/d6535843-effe-4eb7-b9ff-1689420921a3"

feign.FeignException$NotFound: [404] during [GET] to [http://localhost:9002/users/d6535843-effe-4eb7-b9ff-1689420921a3] [UserServiceClient#findById(UUID)]: []

As you can see, the original API I was calling resulted in a 500 Internal Server Error because the downstream user-service was responding with a 404. Not good.

So what can I do if I want to translate this 404 response into a UserNotFoundException within the service? And what if I’d like to do something else for another method within the same client?

Well, let’s create a generic ErrorDecoder that can defer the error handling to other classes, similarly how we do with a @ControllerAdvice and @ExceptionHandler class in the Sprint MVC world.

I’m going to use a custom annotation to mark methods or the client class which needs special treatment on its error handling:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface HandleFeignError {
    Class<? extends FeignHttpExceptionHandler> value();
}

FeignHttpExceptionHandler is a simple interface with a single method:

public interface FeignHttpExceptionHandler {
    Exception handle(Response response);
}

The usage is going to look the following:

@FeignClient(name = "user-service", url = "http://localhost:9002")
public interface UserServiceClient {
    @GetMapping("/users/{id}")
    @HandleFeignError(UserServiceClientExceptionHandler.class)
    UserResponse findById(@PathVariable("id") UUID id) throws UserNotFoundException;
}

The implementation for UserServiceClientExceptionHandler is very simple:

@Component
public class UserServiceClientExceptionHandler implements FeignHttpExceptionHandler {
    @Override
    public Exception handle(Response response) {
        HttpStatus httpStatus = HttpStatus.resolve(response.status());
        String body = FeignUtils.readBody(response.body());
        if (HttpStatus.NOT_FOUND.equals(httpStatus)) {
            return new UserNotFoundException(body);
        }
        return new RuntimeException(body);
    }
}

Of course you can make it more sophisticated, this is just an example.

So how does the annotation work? As I said, we are going to use a special ErrorDecoder. First of all, we have to understand the signature of the ErrorDecoder interface. Since there’s no information within the decoder which client’s which method was called, somehow we have to figure it out so we can invoke the corresponding error handler.

One way to do it is to utilize the methodKey parameter and build a map based on that with the error handlers. But before that, we need to somehow get a reference to all the Feign clients registered within the application:

@Component
@RequiredArgsConstructor
public class ExceptionHandlingFeignErrorDecoder implements ErrorDecoder {
    private final ApplicationContext applicationContext;

    private final Map<String, FeignHttpExceptionHandler> exceptionHandlerMap = new HashMap<>();

    @EventListener
    public void onApplicationEvent(ContextRefreshedEvent event) {
        Map<String, Object> feignClients = applicationContext.getBeansWithAnnotation(FeignClient.class);
        List<Method> clientMethods = feignClients.values().stream()
                .map(Object::getClass)
                .map(aClass -> aClass.getInterfaces()[0])
                .map(ReflectionUtils::getDeclaredMethods)
                .flatMap(Arrays::stream)
                .collect(Collectors.toList());
        for (Method m : clientMethods) {
            String configKey = Feign.configKey(m.getDeclaringClass(), m);
            HandleFeignError handlerAnnotation = getHandleFeignErrorAnnotation(m);
            if (handlerAnnotation != null) {
                FeignHttpExceptionHandler handler = applicationContext.getBean(handlerAnnotation.value());
                exceptionHandlerMap.put(configKey, handler);
            }
        }
    }

    private HandleFeignError getHandleFeignErrorAnnotation(Method m) {
        HandleFeignError result = m.getAnnotation(HandleFeignError.class);
        if (result == null) {
            result = m.getDeclaringClass().getAnnotation(HandleFeignError.class);
        }
        return result;
    }
}

First off, it’s loading all the Spring beans with the @FeignClient annotation. Since Feign is based on interfaces, there are JDK proxies involved, that’s why we need to call aClass.getInterfaces()[0] to get the actual interface with its methods.

Then the only trick is to calculate the methodKey which I’m doing using the Feign.configKey method call (that’s the one Feign is also using). And as well we’re searching for the HandleFeignError annotation on method and on class level in the same order.

So as soon as the Spring context is set up, we’ll have a map of methodKeys to actual exception handlers.

The second thing we need to do is to make sure this class implements the ErrorDecoder interface.

@Component
@RequiredArgsConstructor
public class ExceptionHandlingFeignErrorDecoder implements ErrorDecoder {
    private final ErrorDecoder.Default defaultDecoder = new Default();
    
    // rest is omitted for simplicity

    @Override
    public Exception decode(String methodKey, Response response) {
        FeignHttpExceptionHandler handler = exceptionHandlerMap.get(methodKey);
        if (handler != null) {
            return handler.handle(response);
        }
        return defaultDecoder.decode(methodKey, response);
    }
}

So the decode method is very simple. If the map contains the methodKey with its corresponding exception handler, we’ll use that for resolving the proper Exception, otherwise the solution is falling back to the Default ErrorDecoder.

Using the Feign client afterwards is quite easy:

            try {
                UserResponse userResponse = userServiceClient.findById(id);
                // do something with the UserResponse
            } catch (UserNotFoundException e) {
                // Oops, the user is not found in the system
                // let's do some error handling
            }

Conclusion

With this solution in place, you can easily define proper error handling on the level of Feign client methods with any custom logic/custom exceptions you want to use. It allows you to build a more maintainable and robust application.

As usual, if you are interested in more, follow me on Twitter for updates.

UPDATE: the code can be found on my GitHub here.

In this tutorial, I will share with you how you can use Feign ErrorDecoder to handle errors that occur when using Feign client in Microservices communication.

For step-by-step video beginner lessons demonstrating how to do Feign error handling and how to build Microservices with Spring Boot and Spring Cloud, have a look at this page: Spring Boot Microservices and Spring Cloud.

Setup Feign

To make sure your Feign client works well and the errors you are getting are not caused by an incorrect setup of your Feign client, please have a look at the following tutorial to learn how to add Feign to your Spring Boot project and make it work: Feign Client to Call Another Microservice.

Create ErrorDecoder

To be able to use ErrorDecoder, you will need to create a new Java class and make it implement ErrorDecoder interface. Implementing ErrorDecoder interface gives you access to Method Key and Response objects.

  • methodKey – will contain a Feign client class name and a method name,
  • Response – will allow you to access the HTTP status code, the Body of HTTP Response and the Request object. You can use these details when handling an error message and preparing a response.

Below is an example of ErrorDecoder interface being implemented.

@Component
public class FeignErrorDecoder implements ErrorDecoder {

    Logger logger = LoggerFactory.getLogger(this.getClass());
    
    @Override
    public Exception decode(String methodKey, Response response) {
 
       
        switch (response.status()){
            case 400:
                     logger.error("Status code " + response.status() + ", methodKey = " + methodKey);
            case 404:
            {
                     logger.error("Error took place when using Feign client to send HTTP Request. Status code " + response.status() + ", methodKey = " + methodKey);
                    return new ResponseStatusException(HttpStatus.valueOf(response.status()), "<You can add error message description here>"); 
            }
            default:
                return new Exception(response.reason());
        } 
    }
    
}

Please note the use of @Component annotation. If not using @Component annotation, you can also create Feign ErrorDecoder as a @Bean the following way:

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class PhotoAppApiApplication {

    public static void main(String[] args) {
        SpringApplication.run(PhotoAppApiApplication.class, args);
    }
 
    @Bean
    public FeignErrorDecoder errorDecoder() {
        return new FeignErrorDecoder();
    }
    
} 

Now that your Feign ErrorDecoder interface is implemented, you can try using Feign client to send HTTP Request to a Web Service endpoint that does not exist and see if the 404 HTTP Status code is handled. You get a correct switch case executed, and the error message is logged.

Feign Client HTTP Requests Logging

When working with Feign clients, it is also very helpful to enable HTTP Requests logging. Read the following tutorial to learn how to enable Feign logging in your Spring Boot application:

  • Feign HTTP Requests Logging

If you need to see how it is all done in step-by-step video lessons, checkout out at this page: Spring Boot Microservices and Spring Cloud.

I hope this tutorial was helpful to you.

There are many very good online video courses that teach how to build Spring Boot Microservices with Spring Cloud. Have a look at the list below and see if you like any of them.

Introduction

Feign is a declarative web service client. It makes the client implementation process fast. You can simply define a Java interface with a readable method names and annotations, and make it a functioning web client. You can refer to the readme[1] to have the basic knowledge on Feign. Also there are ample of blogs that you can refer. Through this post, I am going to explain on how you can achieve Error Decoding, and Retrying functionality in a Feign client.

Setting up dependencies

This post uses spring-cloud-starter Hoxton.RELEASE version of spring cloud. In the pom file, you need to add the spring-cloud-starter-parent as the parent-pom file and spring-cloud-dependencies as the dependency management. Spring-cloud-dependencies provide the spring-cloud dependency versions according to the parent pom version. Thereafter you need to add the following dependencies for the rest of implementation:

  • spring-boot-starter
  • spring-boot-starter-web
  • spring-cloud-starter-openfeign

It’s worth mentioning that feign was created and maintained by Netflix OSS and currently maintained separately from Nextflix. Once you wire-up all dependencies, the final pom file would look like below:

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.cloud</groupId>
		<artifactId>spring-cloud-starter-parent</artifactId>
		<version>Hoxton.RELEASE</version>
	</parent>
	<groupId>com.buddhima.testing</groupId>
	<artifactId>acn-demo</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>acn-demo</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-openfeign</artifactId>
		</dependency>
	</dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

Enable feign clients and define a feign client

To enable feign clients, you need to use @EnableFeignClients annotation in the main class definition. Then you can simply create an interface to the external web-services. In this post I’m not going to talk about the annotations because you can find a good documentation here [1][2]. Following is a sample interface I created for this post:

DrmServiceClient.java

package com.buddhima.testing.demo.client;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.util.List;

@FeignClient(value="drmClient", url="http://www.mocky.io/v2/5e2d966a3000006200e77d2d") // to produce 400 HTTP status
public interface DrmServiceClient {

    @RequestMapping(method = RequestMethod.GET, value = "/posts")
    List<Object> getObjects();
}

Enabling logging for feign client

First I decided to talk about logging as this helps to demonstrate the behaviors in next steps.
To enable extended logging for feign clients, you need to follow two steps.

  1. Enabling DEBUG log-level for feign client
  2. Change feign client log-level (valid values are NONE, BASIC, HEADERS, FULL)

application.yml

logging:
  level:
    com.buddhima.testing.demo.client: DEBUG

feign:
  client:
    config:
      default:
        loggerLevel: BASIC

After this configuration, you can view Request-Response logs in the microservice log. You can further increase logging by changing the logger-level to HEADER or FULL.
Throughout this post, I am discussing about configuring the feign client through application.yml file. But there are multiple ways of doing the same.

Error Decoder for Feign client

You can use error-decoder to act based on the erroneous HTTP responses. I have observed that error-decoder does not get triggered on success scenarios. To implement an error-decoder, you need to implement a class using ErrorDecoder interface and add that in the configuration.

DrmClientErrorDecoder.java

package com.buddhima.testing.demo.client;

import feign.Response;
import feign.RetryableException;
import feign.codec.ErrorDecoder;

public class DrmClientErrorDecoder implements ErrorDecoder {
    private final ErrorDecoder defaultErrorDecoder = new Default();

    @Override
    public Exception decode(String s, Response response) {
        System.out.println("Error Response!!!");

        if (400 == response.status()) {
            System.out.println("It's a 400 Error!!!");
        }

        return defaultErrorDecoder.decode(s, response);
    }
}

application.yml

logging:
  level:
    com.buddhima.testing.demo.client: DEBUG

feign:
  client:
    config:
      default:
        errorDecoder: com.buddhima.testing.demo.client.DrmClientErrorDecoder
        loggerLevel: BASIC

Retryer for Feign client

Retryer could be a useful entity to retry your request in case of failure (network failures by default). You can configure default retryer with parameters by extending Retryer.Default class.

DrmClientRetryer.java

package com.buddhima.testing.demo.client;

import feign.Retryer;

public class DrmClientRetryer extends Retryer.Default {
    public  DrmClientRetryer() {
        super();
    }
}

application.yml

feign:
  client:
    config:
      default:
        errorDecoder: com.buddhima.testing.demo.client.DrmClientErrorDecoder
        loggerLevel: BASIC
        retryer: com.buddhima.testing.demo.client.DrmClientRetryer

Furthermore, if you need to retry based on HTTP status, you can throw RetryableException at error-decoder. Same goes, if you want to retry based on HTTP-headers.

DrmClientErrorDecoder.java

package com.buddhima.testing.demo.client;

import feign.Response;
import feign.RetryableException;
import feign.codec.ErrorDecoder;

public class DrmClientErrorDecoder implements ErrorDecoder {
    private final ErrorDecoder defaultErrorDecoder = new Default();

    @Override
    public Exception decode(String s, Response response) {
        System.out.println("Error Response!!!");

        if (400 == response.status()) {
            return new RetryableException(400, response.reason(), response.request().httpMethod(), null, response.request());
        }

        return defaultErrorDecoder.decode(s, response);
    }
}

Following is a sample log, which demonstrate re-trying 5 times before giving-up.

2020-01-27 23:20:41.317 DEBUG 7429 --- [nio-8080-exec-1] c.b.t.demo.client.DrmServiceClient       : [DrmServiceClient#getObjects] ---> GET http://www.mocky.io/v2/5e2d966a3000006200e77d2d/posts HTTP/1.1
2020-01-27 23:20:42.153 DEBUG 7429 --- [nio-8080-exec-1] c.b.t.demo.client.DrmServiceClient       : [DrmServiceClient#getObjects] <--- HTTP/1.1 400 Bad Request (836ms)
Error Response!!!
2020-01-27 23:20:42.317 DEBUG 7429 --- [nio-8080-exec-1] c.b.t.demo.client.DrmServiceClient       : [DrmServiceClient#getObjects] ---> RETRYING
2020-01-27 23:20:42.318 DEBUG 7429 --- [nio-8080-exec-1] c.b.t.demo.client.DrmServiceClient       : [DrmServiceClient#getObjects] ---> GET http://www.mocky.io/v2/5e2d966a3000006200e77d2d/posts HTTP/1.1
2020-01-27 23:20:42.551 DEBUG 7429 --- [nio-8080-exec-1] c.b.t.demo.client.DrmServiceClient       : [DrmServiceClient#getObjects] <--- HTTP/1.1 400 Bad Request (231ms)
Error Response!!!
2020-01-27 23:20:42.776 DEBUG 7429 --- [nio-8080-exec-1] c.b.t.demo.client.DrmServiceClient       : [DrmServiceClient#getObjects] ---> RETRYING
2020-01-27 23:20:42.777 DEBUG 7429 --- [nio-8080-exec-1] c.b.t.demo.client.DrmServiceClient       : [DrmServiceClient#getObjects] ---> GET http://www.mocky.io/v2/5e2d966a3000006200e77d2d/posts HTTP/1.1
2020-01-27 23:20:43.062 DEBUG 7429 --- [nio-8080-exec-1] c.b.t.demo.client.DrmServiceClient       : [DrmServiceClient#getObjects] <--- HTTP/1.1 400 Bad Request (285ms)
Error Response!!!
2020-01-27 23:20:43.400 DEBUG 7429 --- [nio-8080-exec-1] c.b.t.demo.client.DrmServiceClient       : [DrmServiceClient#getObjects] ---> RETRYING
2020-01-27 23:20:43.400 DEBUG 7429 --- [nio-8080-exec-1] c.b.t.demo.client.DrmServiceClient       : [DrmServiceClient#getObjects] ---> GET http://www.mocky.io/v2/5e2d966a3000006200e77d2d/posts HTTP/1.1
2020-01-27 23:20:43.678 DEBUG 7429 --- [nio-8080-exec-1] c.b.t.demo.client.DrmServiceClient       : [DrmServiceClient#getObjects] <--- HTTP/1.1 400 Bad Request (276ms)
Error Response!!!
2020-01-27 23:20:44.185 DEBUG 7429 --- [nio-8080-exec-1] c.b.t.demo.client.DrmServiceClient       : [DrmServiceClient#getObjects] ---> RETRYING
2020-01-27 23:20:44.186 DEBUG 7429 --- [nio-8080-exec-1] c.b.t.demo.client.DrmServiceClient       : [DrmServiceClient#getObjects] ---> GET http://www.mocky.io/v2/5e2d966a3000006200e77d2d/posts HTTP/1.1
2020-01-27 23:20:44.395 DEBUG 7429 --- [nio-8080-exec-1] c.b.t.demo.client.DrmServiceClient       : [DrmServiceClient#getObjects] <--- HTTP/1.1 400 Bad Request (208ms)

In the above log, you can see that same request attempted 5 times back-to-back before it was declared as a failure. You can change the default values through the Retryer implementation to desired. Furthermore, you can alter the error-decoder to refer specific HTTP headers (eg: Retry-After header).

Conclusion

Through this post, I discussed on Feign clients with two different use-cases. First you have seen, how to react based on HTTP error statuses and later about retrying requests. I hope this will be beneficial for your spring boot application development.

I have uploaded the source-code used in this post at here: https://github.com/Buddhima/feign-demo

References

[1] https://github.com/OpenFeign/feign/blob/master/README.md

[2] https://cloud.spring.io/spring-cloud-openfeign/reference/html/

[3] http://www.matez.de/index.php/2017/04/12/exploring-feign-retrying/

Как сочетание Feign и Hystrix предоставит вам полный контроль над возвращаемыми ошибками

«Все, что может пойти не так, пойдет не так» — Закон Мерфи

Я думаю, что это одна из лучших особенностей Feign. Точно так же, как мы можем определить Encoder, Decoder, Logger, нам разрешено устанавливать наши резервные стратегии на случай, если сервер вернет некоторую ошибку. Это очень распространенный сценарий в распределенных приложениях.

Помните аннотацию @FeignClient? У него есть два настраиваемых параметра для резервных стратегий:

  • Резервный: класс, реализующий наши методы интерфейса, вызываемый на случай, если что-то пойдет не так (возможно, вернет некоторые данные по умолчанию).
  • FallbackFactory: класс, реализующий интерфейс FallbackFactory, для обработки текущего исключения и определения более настраиваемого поведения.

Но откат не включен по умолчанию. Если у вас есть опыт работы с облаком Spring, вы, должно быть, думаете в данный момент о Hystrix, и да, мы будем его использовать. Так же, как и при включении клиентов Feign, нам нужно включить шаблон автоматического выключателя в нашем классе приложения (или в любом классе, помеченном @Configuration):

В этом примере мы будем использовать резервный фабричный класс, который будет вызывать наш резервный класс с информацией об исключении, например:

Обратите внимание, как перезаписанный метод create() позволяет нам определить, какой резервный класс возвращается в зависимости от сгенерированного исключения. В этом случае у нас есть только один резервный класс, но это мощная функция интерфейса FallbackFactory.

Теперь наш чистый резервный класс:

О некоторых вещах, которые вы должны помнить при реализации резервных вариантов:

  • Должны быть помечены как @Component, они уникальны для всего приложения.
  • Резервный компонент должен иметь область видимости прототипа, потому что мы хотим, чтобы для каждого исключения создавался новый компонент.
  • Используйте внедрение конструктора в целях тестирования.

На данный момент наше приложение может выполнять запросы к внешним службам с настроенной политикой устойчивости, и мы знаем, как бороться с распространенными ошибками и что делать в таких случаях. Это все? Нет, у Фейна последнее золотое яйцо.

Настраиваемые политики обработки ошибок и повторных попыток

Мы знаем, как поступать с неожиданными ошибками, возвращаемыми с сервера, Hystrix позаботится об этом и позволяет нам выбирать, какой резервный класс возвращает. Это единственный способ справиться с ошибками? Рассмотрим особый сценарий:

Каждый раз, когда мы сохраняем нового питомца для усыновления, мы должны возвращать пользователю статус успеха. Если возникла какая-то ошибка, нам нужно применить разные политики:

  • Если код состояния ответа — 4XX, мы должны вернуть пользователю сообщение об ошибке.
  • В случае статуса 5XX мы должны повторить запрос максимум 5 раз.

Hystrix и резервные классы полезны для работы с ошибками, в случае, если мы хотим создать политики повторных попыток, возможно, нам потребуется настроить аннотацию Spring Retry в самом методе службы в зависимости от типа ошибки и оценить код ошибки, возвращаемый сервером. в каждом методе… так много абстракции. Это также относится к сообщению об исключении, во многих случаях нам нужно вызвать BusinessException, чтобы иметь дело с конкретной ошибкой, которую мы хотим выбросить, обработать и зарегистрировать, но проблема в том, что Hystrix оборачивает все исключения в HystrixRuntimeException.

Аналогично интерфейсам Encoder и Decoder, Feign предоставляет интерфейс ErrorDecoder. Угадайте, что, его можно настроить, чтобы настроить обработку ошибок. В большинстве случаев использование настраиваемых исключений зависит от кода состояния запроса. Приведем пример:

И объявите это в своем классе конфигурации клиента:

Декодер ошибки активируется, когда мы получаем ответ от сервера. Если текущий сервер недоступен или просто не существует, декодер ошибок не вызывается. Это означает, что ErrorDecoder во всех случаях требует ответа 4XX или 5XX.

И чтобы включить повторную попытку, мы должны иметь в виду две основные концепции:

  • Повторная попытка активируется только при подаче сигнала RetryableException.
  • Retryer bean настроен в вашем классе конфигурации.

Итак, добавив больше логики к классу MyErrorDecoder, синтаксис должен быть примерно таким:

И в bean-компоненте конфигурации:

И, конечно же, разрешена настройка Retryer:

Наконец, наш bean-компонент Retryer будет таким:

Помните: чтобы активировать резервные методы, Hystrix помещает текущую ошибку в HystrixRuntimeException. Если вы ждете появления исключения Retryable, я никогда не приду.

Мои последние мысли после использования Feign в течение некоторого времени

  • Легко перейти с разных остальных клиентов на Feign. Фактически, Feign полностью независим и не мешает другим.
  • Позволяет мне избежать большого количества шаблонного кода в случае обработки исключений.
  • Легко понять и поддерживать, Feign абстрагирует реализацию того, как выполняются запросы, и вам просто нужно позаботиться о конфигурации.
  • В этой статье не рассматриваются очень продвинутые функции, такие как сохранение токена аутентификации, установка настраиваемых заголовков и т. Д.

В завершение, вот несколько статей с дополнительной информацией о Feign:

Увидимся в следующей статье;)

Понравилась статья? Поделить с друзьями:
  • Ffr 03281 10 ошибка ман тга
  • Ford focus ошибка p1131
  • File already exists ошибка
  • Fdc1008hes3 код ошибки
  • Ffr 03279 04 ошибка ман tgl