Feign ErrorDecoder :检索原始消息
Feign ErrorDecoder : retrieve the original message
我使用 ErrorDecoder return 正确的异常而不是 500 状态代码。
有没有办法在解码器中检索原始消息。我可以看到它在 FeignException 中,但不在 decode 方法中。我只有 'status code' 和一个空的 'reason'.
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":"Access to the file forbidden"
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 接口,所以我不使用任何其他填充了可以封装方法调用的代理的控制器。
@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);
}
这里有一个解决方案,消息实际上是在响应体中作为流。
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;
}
}
如果您像我一样,真的只想从失败的 Feign 调用中提取内容,而不需要所有这些自定义解码器和样板文件,那么有一种 hacky 方法可以做到这一点。
如果我们在创建时查看 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];
如果你想要这个对象,你可以使用像杰克逊这样的东西:
MyResponseBodyPojo errorBody = objectMapper.readValue(responseContent, MyResponseBodyPojo.class);
我不认为这是明智的方法或最佳做法。
如果你想获取response payload body,Feign异常,只需要使用这个方法:
feignException.contentUTF8();
示例:
try {
itemResponse = call(); //method with the feign call
} catch (FeignException e) {
logger.error("ResponseBody: " + e.contentUTF8());
}
原始消息在响应正文中,如已回答。但是,我们可以使用 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);
}
}
建议使用输入流而不是reader并将其映射到您的对象。
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;
}
}
接受答案的一些重构和代码风格:
@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);
}
我使用 ErrorDecoder return 正确的异常而不是 500 状态代码。
有没有办法在解码器中检索原始消息。我可以看到它在 FeignException 中,但不在 decode 方法中。我只有 'status code' 和一个空的 'reason'.
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":"Access to the file forbidden"
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 接口,所以我不使用任何其他填充了可以封装方法调用的代理的控制器。
@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);
}
这里有一个解决方案,消息实际上是在响应体中作为流。
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;
}
}
如果您像我一样,真的只想从失败的 Feign 调用中提取内容,而不需要所有这些自定义解码器和样板文件,那么有一种 hacky 方法可以做到这一点。
如果我们在创建时查看 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];
如果你想要这个对象,你可以使用像杰克逊这样的东西:
MyResponseBodyPojo errorBody = objectMapper.readValue(responseContent, MyResponseBodyPojo.class);
我不认为这是明智的方法或最佳做法。
如果你想获取response payload body,Feign异常,只需要使用这个方法:
feignException.contentUTF8();
示例:
try {
itemResponse = call(); //method with the feign call
} catch (FeignException e) {
logger.error("ResponseBody: " + e.contentUTF8());
}
原始消息在响应正文中,如已回答。但是,我们可以使用 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);
}
}
建议使用输入流而不是reader并将其映射到您的对象。
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;
}
}
接受答案的一些重构和代码风格:
@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);
}