1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.bremersee.exception.feign;
18
19 import static feign.Util.RETRY_AFTER;
20 import static java.util.concurrent.TimeUnit.SECONDS;
21 import static org.bremersee.http.HttpHeadersHelper.buildHttpHeaders;
22 import static org.bremersee.http.HttpHeadersHelper.getContentCharset;
23
24 import feign.Request.HttpMethod;
25 import feign.Response;
26 import feign.RetryableException;
27 import feign.Util;
28 import feign.codec.ErrorDecoder;
29 import java.nio.charset.Charset;
30 import java.nio.charset.StandardCharsets;
31 import java.time.OffsetDateTime;
32 import java.time.format.DateTimeFormatter;
33 import java.util.Collections;
34 import java.util.Date;
35 import lombok.extern.slf4j.Slf4j;
36 import org.bremersee.exception.RestApiExceptionParser;
37 import org.bremersee.exception.RestApiExceptionParserImpl;
38 import org.bremersee.exception.model.RestApiException;
39 import org.springframework.http.HttpHeaders;
40
41
42
43
44
45
46 @Slf4j
47 public class FeignClientExceptionErrorDecoder implements ErrorDecoder {
48
49 private final RestApiExceptionParser parser;
50
51
52
53
54 @SuppressWarnings("WeakerAccess")
55 public FeignClientExceptionErrorDecoder() {
56 this.parser = new RestApiExceptionParserImpl();
57 }
58
59
60
61
62
63
64 @SuppressWarnings("unused")
65 public FeignClientExceptionErrorDecoder(
66 final RestApiExceptionParser parser) {
67 this.parser = parser != null ? parser : new RestApiExceptionParserImpl();
68 }
69
70 @Override
71 public Exception decode(final String methodKey, final Response response) {
72
73 if (log.isDebugEnabled()) {
74 log.debug("msg=[Decoding error at {}]", methodKey);
75 }
76 final String body = readBody(response);
77 final String message = String.format("status %s reading %s", response.status(), methodKey);
78 final RestApiException restApiException = parser.parseException(
79 body, response.headers());
80 if (log.isDebugEnabled() && body != null) {
81 log.debug("msg=[Is error formatted as rest api exception? {}]",
82 restApiException != null && !body.equals(restApiException.getMessage()));
83 }
84 final FeignClientExceptionFeignClientException">FeignClientException feignClientException = new FeignClientException(
85 response.request(),
86 response.headers() != null ? Collections.unmodifiableMap(response.headers()) : null,
87 response.status(),
88 message,
89 restApiException);
90 final HttpHeaders httpHeaders = buildHttpHeaders(response.headers());
91 final Date retryAfter = determineRetryAfter(httpHeaders.getFirst(RETRY_AFTER));
92 if (retryAfter != null) {
93 return new RetryableException(
94 response.status(),
95 feignClientException.getMessage(),
96 findHttpMethod(response),
97 feignClientException,
98 retryAfter,
99 response.request());
100 }
101 return feignClientException;
102 }
103
104 static String readBody(final Response response) {
105 if (response == null || response.body() == null) {
106 return null;
107 }
108 Charset charset = getContentCharset(buildHttpHeaders(response.headers()), StandardCharsets.UTF_8);
109 try {
110 return Util.toString(response.body().asReader(charset));
111 } catch (Exception ignored) {
112 return null;
113 }
114 }
115
116 static Date determineRetryAfter(final String retryAfter) {
117 if (retryAfter == null) {
118 return null;
119 }
120 if (retryAfter.matches("^[0-9]+$")) {
121 long deltaMillis = SECONDS.toMillis(Long.parseLong(retryAfter));
122 return new Date(System.currentTimeMillis() + deltaMillis);
123 }
124 try {
125 return Date.from(OffsetDateTime.parse(retryAfter,
126 DateTimeFormatter.RFC_1123_DATE_TIME).toInstant());
127 } catch (Exception ignored) {
128 return null;
129 }
130 }
131
132 static HttpMethod findHttpMethod(final Response response) {
133 if (response == null || response.request() == null) {
134 return null;
135 }
136 return response.request().httpMethod();
137 }
138
139 }