Feign ErrorDecoder:检索原始消息

时间:2018-11-28 17:44:00

标签: spring-cloud-feign

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

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

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);
    }
}

原始消息:“ message”:“禁止访问文件”

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"}

我也像RestController一样使用我的FeignClient接口,因此我不使用其他任何填充有可以封装方法调用的代理的Controler。

   @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);

    }

6 个答案:

答案 0 :(得分:7)

如果要获得响应有效负载正文(假装例外),请使用以下方法:

feignException.contentUTF8();

示例:

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

答案 1 :(得分: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;

    }
}

答案 2 :(得分: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;
        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;

    }
}

答案 3 :(得分:0)

如果您像我一样,并且真的只想从失败的Feign调用中获取内容,而没有所有这些自定义解码器和样板,则可以采用一种不可靠的方法。

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

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

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

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

如果需要该对象,则可以使用类似Jackson的东西

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

我不认为这是明智的方法或最佳做法。

答案 4 :(得分:0)

原始消息已在响应正文中,已得到答复。但是,我们可以使用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 :(得分:0)

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

@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);
}