Skip to content

Commit 76a4524

Browse files
jerzykrlkvelo
authored andcommitted
1048 simple illustration - more details in the default feignexception… (OpenFeign#1095)
* 1048 simple illustration - more details in the default feignexception message * 1048 response body abbreviated
1 parent 770b579 commit 76a4524

6 files changed

Lines changed: 226 additions & 36 deletions

File tree

core/src/main/java/feign/FeignException.java

Lines changed: 126 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,20 @@
1313
*/
1414
package feign;
1515

16+
import java.io.ByteArrayInputStream;
17+
import java.io.IOException;
18+
import java.io.InputStreamReader;
19+
import java.io.Reader;
20+
import java.nio.Buffer;
21+
import java.nio.CharBuffer;
22+
import java.nio.charset.Charset;
23+
import java.util.Collection;
24+
import java.util.Map;
25+
import java.util.regex.Matcher;
26+
import java.util.regex.Pattern;
1627
import static feign.Util.UTF_8;
1728
import static feign.Util.checkNotNull;
1829
import static java.lang.String.format;
19-
import java.io.IOException;
2030

2131
/**
2232
* Origin exception type for all Http Apis.
@@ -121,7 +131,6 @@ static FeignException errorReading(Request request, Response response, IOExcepti
121131
}
122132

123133
public static FeignException errorStatus(String methodKey, Response response) {
124-
String message = format("status %s reading %s", response.status(), methodKey);
125134

126135
byte[] body = {};
127136
try {
@@ -131,6 +140,11 @@ public static FeignException errorStatus(String methodKey, Response response) {
131140
} catch (IOException ignored) { // NOPMD
132141
}
133142

143+
String message = new FeignExceptionMessageBuilder()
144+
.withResponse(response)
145+
.withMethodKey(methodKey)
146+
.withBody(body).build();
147+
134148
return errorStatus(response.status(), message, response.request(), body);
135149
}
136150

@@ -222,105 +236,215 @@ public FeignClientException(int status, String message, Request request, byte[]
222236
}
223237
}
224238

239+
225240
public static class BadRequest extends FeignClientException {
226241
public BadRequest(String message, Request request, byte[] body) {
227242
super(400, message, request, body);
228243
}
229244
}
230245

246+
231247
public static class Unauthorized extends FeignClientException {
232248
public Unauthorized(String message, Request request, byte[] body) {
233249
super(401, message, request, body);
234250
}
235251
}
236252

253+
237254
public static class Forbidden extends FeignClientException {
238255
public Forbidden(String message, Request request, byte[] body) {
239256
super(403, message, request, body);
240257
}
241258
}
242259

260+
243261
public static class NotFound extends FeignClientException {
244262
public NotFound(String message, Request request, byte[] body) {
245263
super(404, message, request, body);
246264
}
247265
}
248266

267+
249268
public static class MethodNotAllowed extends FeignClientException {
250269
public MethodNotAllowed(String message, Request request, byte[] body) {
251270
super(405, message, request, body);
252271
}
253272
}
254273

274+
255275
public static class NotAcceptable extends FeignClientException {
256276
public NotAcceptable(String message, Request request, byte[] body) {
257277
super(406, message, request, body);
258278
}
259279
}
260280

281+
261282
public static class Conflict extends FeignClientException {
262283
public Conflict(String message, Request request, byte[] body) {
263284
super(409, message, request, body);
264285
}
265286
}
266287

288+
267289
public static class Gone extends FeignClientException {
268290
public Gone(String message, Request request, byte[] body) {
269291
super(410, message, request, body);
270292
}
271293
}
272294

295+
273296
public static class UnsupportedMediaType extends FeignClientException {
274297
public UnsupportedMediaType(String message, Request request, byte[] body) {
275298
super(415, message, request, body);
276299
}
277300
}
278301

302+
279303
public static class TooManyRequests extends FeignClientException {
280304
public TooManyRequests(String message, Request request, byte[] body) {
281305
super(429, message, request, body);
282306
}
283307
}
284308

309+
285310
public static class UnprocessableEntity extends FeignClientException {
286311
public UnprocessableEntity(String message, Request request, byte[] body) {
287312
super(422, message, request, body);
288313
}
289314
}
290315

316+
291317
public static class FeignServerException extends FeignException {
292318
public FeignServerException(int status, String message, Request request, byte[] body) {
293319
super(status, message, request, body);
294320
}
295321
}
296322

323+
297324
public static class InternalServerError extends FeignServerException {
298325
public InternalServerError(String message, Request request, byte[] body) {
299326
super(500, message, request, body);
300327
}
301328
}
302329

330+
303331
public static class NotImplemented extends FeignServerException {
304332
public NotImplemented(String message, Request request, byte[] body) {
305333
super(501, message, request, body);
306334
}
307335
}
308336

337+
309338
public static class BadGateway extends FeignServerException {
310339
public BadGateway(String message, Request request, byte[] body) {
311340
super(502, message, request, body);
312341
}
313342
}
314343

344+
315345
public static class ServiceUnavailable extends FeignServerException {
316346
public ServiceUnavailable(String message, Request request, byte[] body) {
317347
super(503, message, request, body);
318348
}
319349
}
320350

351+
321352
public static class GatewayTimeout extends FeignServerException {
322353
public GatewayTimeout(String message, Request request, byte[] body) {
323354
super(504, message, request, body);
324355
}
325356
}
357+
358+
359+
private static class FeignExceptionMessageBuilder {
360+
361+
private static final int MAX_BODY_BYTES_LENGTH = 400;
362+
private static final int MAX_BODY_CHARS_LENGTH = 200;
363+
364+
private Response response;
365+
366+
private byte[] body;
367+
private String methodKey;
368+
369+
public FeignExceptionMessageBuilder withResponse(Response response) {
370+
this.response = response;
371+
return this;
372+
}
373+
374+
public FeignExceptionMessageBuilder withBody(byte[] body) {
375+
this.body = body;
376+
return this;
377+
}
378+
379+
public FeignExceptionMessageBuilder withMethodKey(String methodKey) {
380+
this.methodKey = methodKey;
381+
return this;
382+
}
383+
384+
public String build() {
385+
StringBuilder result = new StringBuilder();
386+
387+
if (response.reason() != null) {
388+
result.append(format("[%d %s]", response.status(), response.reason()));
389+
} else {
390+
result.append(format("[%d]", response.status()));
391+
}
392+
result.append(format(" during [%s] to [%s] [%s]", response.request().httpMethod(),
393+
response.request().url(), methodKey));
394+
395+
result.append(format(": [%s]", getBodyAsString(body, response.headers())));
396+
397+
return result.toString();
398+
}
399+
400+
private static String getBodyAsString(byte[] body, Map<String, Collection<String>> headers) {
401+
Charset charset = getResponseCharset(headers);
402+
if (charset == null) {
403+
charset = Util.UTF_8;
404+
}
405+
return getResponseBody(body, charset);
406+
}
407+
408+
private static String getResponseBody(byte[] body, Charset charset) {
409+
if (body.length < MAX_BODY_BYTES_LENGTH) {
410+
return new String(body, charset);
411+
}
412+
return getResponseBodyPreview(body, charset);
413+
}
414+
415+
private static String getResponseBodyPreview(byte[] body, Charset charset) {
416+
try {
417+
Reader reader = new InputStreamReader(new ByteArrayInputStream(body), charset);
418+
CharBuffer result = CharBuffer.allocate(MAX_BODY_CHARS_LENGTH);
419+
420+
reader.read(result);
421+
reader.close();
422+
((Buffer) result).flip();
423+
return result.toString() + "... (" + body.length + " bytes)";
424+
} catch (IOException e) {
425+
return e.toString() + ", failed to parse response";
426+
}
427+
}
428+
429+
private static Charset getResponseCharset(Map<String, Collection<String>> headers) {
430+
431+
Collection<String> strings = headers.get("content-type");
432+
if (strings == null || strings.size() == 0) {
433+
return null;
434+
}
435+
436+
Pattern pattern = Pattern.compile("charset=([^\\s])");
437+
Matcher matcher = pattern.matcher(strings.iterator().next());
438+
if (!matcher.lookingAt()) {
439+
return null;
440+
}
441+
442+
String group = matcher.group(1);
443+
if (!Charset.isSupported(group)) {
444+
return null;
445+
}
446+
return Charset.forName(group);
447+
448+
}
449+
}
326450
}

core/src/test/java/feign/client/AbstractClientTest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,9 @@ public void reasonPhraseIsOptional() throws IOException, InterruptedException {
118118
@Test
119119
public void parsesErrorResponse() {
120120
thrown.expect(FeignException.class);
121-
thrown.expectMessage("status 500 reading TestInterface#get()");
121+
thrown.expectMessage(
122+
"[500 Server Error] during [GET] to [http://localhost:" + server.getPort()
123+
+ "/] [TestInterface#get()]: [ARGHH]");
122124

123125
server.enqueue(new MockResponse().setResponseCode(500).setBody("ARGHH"));
124126

core/src/test/java/feign/codec/DefaultErrorDecoderHttpErrorTest.java

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -33,23 +33,40 @@ public class DefaultErrorDecoderHttpErrorTest {
3333
@Parameterized.Parameters(name = "error: [{0}], exception: [{1}]")
3434
public static Object[][] errorCodes() {
3535
return new Object[][] {
36-
{400, FeignException.BadRequest.class},
37-
{401, FeignException.Unauthorized.class},
38-
{403, FeignException.Forbidden.class},
39-
{404, FeignException.NotFound.class},
40-
{405, FeignException.MethodNotAllowed.class},
41-
{406, FeignException.NotAcceptable.class},
42-
{409, FeignException.Conflict.class},
43-
{429, FeignException.TooManyRequests.class},
44-
{422, FeignException.UnprocessableEntity.class},
45-
{450, FeignException.FeignClientException.class},
46-
{500, FeignException.InternalServerError.class},
47-
{501, FeignException.NotImplemented.class},
48-
{502, FeignException.BadGateway.class},
49-
{503, FeignException.ServiceUnavailable.class},
50-
{504, FeignException.GatewayTimeout.class},
51-
{599, FeignException.FeignServerException.class},
52-
{599, FeignException.class},
36+
{400, FeignException.BadRequest.class,
37+
"[400 anything] during [GET] to [http://example.com/api] [Service#foo()]: [response body]"},
38+
{401, FeignException.Unauthorized.class,
39+
"[401 anything] during [GET] to [http://example.com/api] [Service#foo()]: [response body]"},
40+
{403, FeignException.Forbidden.class,
41+
"[403 anything] during [GET] to [http://example.com/api] [Service#foo()]: [response body]"},
42+
{404, FeignException.NotFound.class,
43+
"[404 anything] during [GET] to [http://example.com/api] [Service#foo()]: [response body]"},
44+
{405, FeignException.MethodNotAllowed.class,
45+
"[405 anything] during [GET] to [http://example.com/api] [Service#foo()]: [response body]"},
46+
{406, FeignException.NotAcceptable.class,
47+
"[406 anything] during [GET] to [http://example.com/api] [Service#foo()]: [response body]"},
48+
{409, FeignException.Conflict.class,
49+
"[409 anything] during [GET] to [http://example.com/api] [Service#foo()]: [response body]"},
50+
{429, FeignException.TooManyRequests.class,
51+
"[429 anything] during [GET] to [http://example.com/api] [Service#foo()]: [response body]"},
52+
{422, FeignException.UnprocessableEntity.class,
53+
"[422 anything] during [GET] to [http://example.com/api] [Service#foo()]: [response body]"},
54+
{450, FeignException.FeignClientException.class,
55+
"[450 anything] during [GET] to [http://example.com/api] [Service#foo()]: [response body]"},
56+
{500, FeignException.InternalServerError.class,
57+
"[500 anything] during [GET] to [http://example.com/api] [Service#foo()]: [response body]"},
58+
{501, FeignException.NotImplemented.class,
59+
"[501 anything] during [GET] to [http://example.com/api] [Service#foo()]: [response body]"},
60+
{502, FeignException.BadGateway.class,
61+
"[502 anything] during [GET] to [http://example.com/api] [Service#foo()]: [response body]"},
62+
{503, FeignException.ServiceUnavailable.class,
63+
"[503 anything] during [GET] to [http://example.com/api] [Service#foo()]: [response body]"},
64+
{504, FeignException.GatewayTimeout.class,
65+
"[504 anything] during [GET] to [http://example.com/api] [Service#foo()]: [response body]"},
66+
{599, FeignException.FeignServerException.class,
67+
"[599 anything] during [GET] to [http://example.com/api] [Service#foo()]: [response body]"},
68+
{599, FeignException.class,
69+
"[599 anything] during [GET] to [http://example.com/api] [Service#foo()]: [response body]"},
5370
};
5471
}
5572

@@ -59,6 +76,9 @@ public static Object[][] errorCodes() {
5976
@Parameterized.Parameter(1)
6077
public Class expectedExceptionClass;
6178

79+
@Parameterized.Parameter(2)
80+
public String expectedMessage;
81+
6282
private ErrorDecoder errorDecoder = new ErrorDecoder.Default();
6383

6484
private Map<String, Collection<String>> headers = new LinkedHashMap<>();
@@ -68,14 +88,17 @@ public void testExceptionIsHttpSpecific() throws Throwable {
6888
Response response = Response.builder()
6989
.status(httpStatus)
7090
.reason("anything")
71-
.request(Request.create(HttpMethod.GET, "/api", Collections.emptyMap(), null, Util.UTF_8))
91+
.request(Request.create(HttpMethod.GET, "http://example.com/api", Collections.emptyMap(),
92+
null, Util.UTF_8))
7293
.headers(headers)
94+
.body("response body", Util.UTF_8)
7395
.build();
7496

7597
Exception exception = errorDecoder.decode("Service#foo()", response);
7698

7799
assertThat(exception).isInstanceOf(expectedExceptionClass);
78100
assertThat(((FeignException) exception).status()).isEqualTo(httpStatus);
101+
assertThat(exception.getMessage()).isEqualTo(expectedMessage);
79102
}
80103

81104
}

0 commit comments

Comments
 (0)