【问题标题】:Feign ErrorDecoder : retrieve the original messageFeign ErrorDecoder :检索原始消息
【发布时间】:2019-04-30 16:51:11
【问题描述】:

我使用 ErrorDecoder 返回正确的异常而不是 500 状态代码。

有没有办法在解码器中检索原始消息。我可以看到它在 FeignException 内部,但不在 decode 方法中。我只有“状态代码”和一个空的“原因”。

public class CustomErrorDecoder implements ErrorDecoder {

    private final ErrorDecoder errorDecoder = new Default();

    @Override
    public Exception decode(String s, Response response) {

        switch (response.status()) {

            case 404:
                return new FileNotFoundException("File no found");
            case 403:
                return new ForbiddenAccessException("Forbidden access");
        }

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

这里是原始消息:“消息”:“禁止访问文件”

feign.FeignException: status 403 reading ProxyMicroserviceFiles#getUserRoot(); content:
{"timestamp":"2018-11-28T17:34:05.235+0000","status":403,"error":"Forbidden","message":"Access to the file forbidden","path":"/root"}

我还使用我的 FeignClient 接口,如 RestController,因此我不使用任何其他填充了可以封装方法调用的代理的控制器。

   @RestController
   @FeignClient(name = "zuul-server")
   @RibbonClient(name = "microservice-files")

   public interface ProxyMicroserviceFiles {

                @GetMapping(value = "microservice-files/root")
                Object getUserRoot();

                @GetMapping(value = "microservice-files/file/{id}")
                Object getFileById(@PathVariable("id") int id);

    }

【问题讨论】:

    标签: spring-cloud-feign


    【解决方案1】:

    这里有一个解决方案,消息实际上是在响应体中作为一个流。

    package com.clientui.exceptions;
    
    import com.fasterxml.jackson.databind.DeserializationFeature;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.google.common.io.CharStreams;
    import feign.Response;
    import feign.codec.ErrorDecoder;
    import lombok.*;
    
    import java.io.*;
    
    public class CustomErrorDecoder implements ErrorDecoder {
    
        private final ErrorDecoder errorDecoder = new Default();
    
        @Override
        public Exception decode(String s, Response response) {
    
            String message = null;
            Reader reader = null;
    
            try {
                reader = response.body().asReader();
                //Easy way to read the stream and get a String object
                String result = CharStreams.toString(reader);
                //use a Jackson ObjectMapper to convert the Json String into a 
                //Pojo
                ObjectMapper mapper = new ObjectMapper();
                //just in case you missed an attribute in the Pojo     
              mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
                //init the Pojo
                ExceptionMessage exceptionMessage = mapper.readValue(result, 
                                                    ExceptionMessage.class);
    
                message = exceptionMessage.message;
    
            } catch (IOException e) {
    
                e.printStackTrace();
            }finally {
    
                //It is the responsibility of the caller to close the stream.
                try {
    
                    if (reader != null)
                        reader.close();
    
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
    
            switch (response.status()) {
    
                case 404:
                    return new FileNotFoundException(message == null ? "File no found" : 
                                                                         message);
                case 403:
                    return new ForbiddenAccessException(message == null ? "Forbidden 
                                                                  access" : message);
    
            }
    
            return errorDecoder.decode(s, response);
        }
    
        @Getter
        @Setter
        @NoArgsConstructor
        @AllArgsConstructor
        @ToString
        public static class ExceptionMessage{
    
            private String timestamp;
            private int status;
            private String error;
            private String message;
            private String path;
    
        }
    }
    

    【讨论】:

    • 我也有类似的问题。这真的是找回尸体的唯一方法吗?似乎有相当多的样板开销“只是”从响应中读取正文作为字符串?
    • @msilb 您可以直接以 json 格式返回结果字符串并在最终客户端中反序列化它
    • @kaizokum 我也遇到了同样的问题。但是,当我执行 String result = CharStreams.toString(reader);我收到流已经关闭的异常。
    • @nsivaram90 你找到解决方案了吗?我遇到了同样的错误。
    • 对于任何寻找@kaizokum 问题答案的人:stackoverflow.com/questions/61472139/…
    【解决方案2】:

    如果你和我一样,真的只是想从失败的 Feign 调用中获得内容,而无需所有这些自定义解码器和样板,那么有一种很老套的方法来做到这一点。

    如果我们在创建 FeignException 并且存在响应主体时查看它,它会像这样组装异常消息:

    if (response.body() != null) {
        String body = Util.toString(response.body().asReader());
        message += "; content:\n" + body;
    }
    

    因此,如果您在响应正文之后,您可以通过解析异常消息将其拉出,因为它由换行符分隔。

    String[] feignExceptionMessageParts = e.getMessage().split("\n");
    String responseContent = feignExceptionMessageParts[1];
    

    如果你想要这个对象,你可以使用 Jackson 之类的东西:

    MyResponseBodyPojo errorBody = objectMapper.readValue(responseContent, MyResponseBodyPojo.class);
    

    我并不认为这是一种聪明的方法或最佳实践。

    【讨论】:

    • 原始响应内容也可以(很可能)包含换行符,因此我们要查找的部分不是feignExceptionMessageParts[1],而是整个数组没有第零个元素
    【解决方案3】:

    如果你想得到response payload body,除了Feign异常,使用这个方法即可:

    feignException.contentUTF8();
    

    例子:

        try {
            itemResponse = call(); //method with the feign call
        } catch (FeignException e) {
            logger.error("ResponseBody: " + e.contentUTF8());
        }
    

    【讨论】:

    • 这不适用于旧版本的 Spring Cloud
    • 这就是我要找的。确切的解决方案...
    • 它返回一个 JSON 作为字符串。如何将其转换回 Model 对象?
    • @romeucr 如果你有一个代表这个 JSON 的类,你可以使用 jackson 来解析它。
    • 是的!经过一番搜索,我意识到该怎么做。超级简单:String string = ex.contentUTF8(); MyClass myClass = new ObjectMapper().readValue(string, MyClass.class);
    【解决方案4】:

    原始消息在响应正文中,正如已经回答的那样。但是,我们可以使用 Java 8 Streams 来减少样板的数量:

    public class CustomErrorDecoder implements ErrorDecoder {
    
      private final ErrorDecoder errorDecoder = new Default();
    
      @Override
      public Exception decode(String s, Response response) {
        String body = "4xx client error";
        try {
            body = new BufferedReader(response.body().asReader(StandardCharsets.UTF_8))
              .lines()
              .collect(Collectors.joining("\n"));
        } catch (IOException ignore) {}
    
        switch (response.status()) {
    
            case 404:
                return new FileNotFoundException(body);
            case 403:
                return new ForbiddenAccessException(body);
        }
    
        return errorDecoder.decode(s, response);
      }
    }
    

    【讨论】:

      【解决方案5】:

      建议使用输入流代替阅读器并将其映射到您的对象。

      package com.clientui.exceptions;
      
      import com.fasterxml.jackson.databind.DeserializationFeature;
      import com.fasterxml.jackson.databind.ObjectMapper;
      import com.google.common.io.CharStreams;
      import feign.Response;
      import feign.codec.ErrorDecoder;
      import lombok.*;
      
      import java.io.*;
      
      public class CustomErrorDecoder implements ErrorDecoder {
      
          private final ErrorDecoder errorDecoder = new Default();
      
          @Override
          public Exception decode(String s, Response response) {
      
              String message = null;
              InputStream responseBodyIs = null;
              try {
                  responseBodyIs = response.body().asInputStream();
                  ObjectMapper mapper = new ObjectMapper();
                  ExceptionMessage exceptionMessage = mapper.readValue(responseBodyIs, ExceptionMessage.class);
      
                  message = exceptionMessage.message;
      
              } catch (IOException e) {
      
                  e.printStackTrace();
                  // you could also return an exception
                  return new errorMessageFormatException(e.getMessage());
              }finally {
      
                  //It is the responsibility of the caller to close the stream.
                  try {
                      if (responseBodyIs != null)
                          responseBodyIs.close();
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
              }
      
              switch (response.status()) {
      
                  case 404:
                      return new FileNotFoundException(message == null ? "File no found" :
                              message);
                  case 403:
                      return new ForbiddenAccessException(message == null ? "Forbidden access" : message);
      
              }
      
              return errorDecoder.decode(s, response);
          }
      
          @Getter
          @Setter
          @NoArgsConstructor
          @AllArgsConstructor
          @ToString
          public static class ExceptionMessage{
      
              private String timestamp;
              private int status;
              private String error;
              private String message;
              private String path;
      
          }
      }
      

      【讨论】:

        【解决方案6】:

        接受答案的一些重构和代码风格:

        @Override
        @SneakyThrows
        public Exception decode(String methodKey, Response response) {
          String message;
        
          try (Reader reader = response.body().asReader()) {
            String result = StringUtils.toString(reader);
            message = mapper.readValue(result, ErrorResponse.class).getMessage();
          }
        
          if (response.status() == 401) {
            return new UnauthorizedException(message == null ? response.reason() : message);
          }
          if (response.status() == 403) {
            return new ForbiddenException(message == null ? response.reason() : message);
          }
          return defaultErrorDecoder.decode(methodKey, response);
        }
        

        【讨论】:

          猜你喜欢
          • 2019-06-12
          • 1970-01-01
          • 2020-10-08
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多