Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1048 simple illustration - more details in the default feignexception… #1095

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
128 changes: 126 additions & 2 deletions core/src/main/java/feign/FeignException.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,20 @@
*/
package feign;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.Buffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.util.Collection;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static feign.Util.UTF_8;
import static feign.Util.checkNotNull;
import static java.lang.String.format;
import java.io.IOException;

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

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

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

String message = new FeignExceptionMessageBuilder()
.withResponse(response)
.withMethodKey(methodKey)
.withBody(body).build();

return errorStatus(response.status(), message, response.request(), body);
}

Expand Down Expand Up @@ -222,105 +236,215 @@ public FeignClientException(int status, String message, Request request, byte[]
}
}


public static class BadRequest extends FeignClientException {
public BadRequest(String message, Request request, byte[] body) {
super(400, message, request, body);
}
}


public static class Unauthorized extends FeignClientException {
public Unauthorized(String message, Request request, byte[] body) {
super(401, message, request, body);
}
}


public static class Forbidden extends FeignClientException {
public Forbidden(String message, Request request, byte[] body) {
super(403, message, request, body);
}
}


public static class NotFound extends FeignClientException {
public NotFound(String message, Request request, byte[] body) {
super(404, message, request, body);
}
}


public static class MethodNotAllowed extends FeignClientException {
public MethodNotAllowed(String message, Request request, byte[] body) {
super(405, message, request, body);
}
}


public static class NotAcceptable extends FeignClientException {
public NotAcceptable(String message, Request request, byte[] body) {
super(406, message, request, body);
}
}


public static class Conflict extends FeignClientException {
public Conflict(String message, Request request, byte[] body) {
super(409, message, request, body);
}
}


public static class Gone extends FeignClientException {
public Gone(String message, Request request, byte[] body) {
super(410, message, request, body);
}
}


public static class UnsupportedMediaType extends FeignClientException {
public UnsupportedMediaType(String message, Request request, byte[] body) {
super(415, message, request, body);
}
}


public static class TooManyRequests extends FeignClientException {
public TooManyRequests(String message, Request request, byte[] body) {
super(429, message, request, body);
}
}


public static class UnprocessableEntity extends FeignClientException {
public UnprocessableEntity(String message, Request request, byte[] body) {
super(422, message, request, body);
}
}


public static class FeignServerException extends FeignException {
public FeignServerException(int status, String message, Request request, byte[] body) {
super(status, message, request, body);
}
}


public static class InternalServerError extends FeignServerException {
public InternalServerError(String message, Request request, byte[] body) {
super(500, message, request, body);
}
}


public static class NotImplemented extends FeignServerException {
public NotImplemented(String message, Request request, byte[] body) {
super(501, message, request, body);
}
}


public static class BadGateway extends FeignServerException {
public BadGateway(String message, Request request, byte[] body) {
super(502, message, request, body);
}
}


public static class ServiceUnavailable extends FeignServerException {
public ServiceUnavailable(String message, Request request, byte[] body) {
super(503, message, request, body);
}
}


public static class GatewayTimeout extends FeignServerException {
public GatewayTimeout(String message, Request request, byte[] body) {
super(504, message, request, body);
}
}


private static class FeignExceptionMessageBuilder {

private static final int MAX_BODY_BYTES_LENGTH = 400;
private static final int MAX_BODY_CHARS_LENGTH = 200;

private Response response;

private byte[] body;
private String methodKey;

public FeignExceptionMessageBuilder withResponse(Response response) {
this.response = response;
return this;
}

public FeignExceptionMessageBuilder withBody(byte[] body) {
this.body = body;
return this;
}

public FeignExceptionMessageBuilder withMethodKey(String methodKey) {
this.methodKey = methodKey;
return this;
}

public String build() {
StringBuilder result = new StringBuilder();

if (response.reason() != null) {
result.append(format("[%d %s]", response.status(), response.reason()));
} else {
result.append(format("[%d]", response.status()));
}
result.append(format(" during [%s] to [%s] [%s]", response.request().httpMethod(),
response.request().url(), methodKey));

result.append(format(": [%s]", getBodyAsString(body, response.headers())));

return result.toString();
}

private static String getBodyAsString(byte[] body, Map<String, Collection<String>> headers) {
Charset charset = getResponseCharset(headers);
if (charset == null) {
charset = Util.UTF_8;
}
return getResponseBody(body, charset);
}

private static String getResponseBody(byte[] body, Charset charset) {
if (body.length < MAX_BODY_BYTES_LENGTH) {
return new String(body, charset);
}
return getResponseBodyPreview(body, charset);
}

private static String getResponseBodyPreview(byte[] body, Charset charset) {
try {
Reader reader = new InputStreamReader(new ByteArrayInputStream(body), charset);
CharBuffer result = CharBuffer.allocate(MAX_BODY_CHARS_LENGTH);

reader.read(result);
reader.close();
((Buffer) result).flip();
return result.toString() + "... (" + body.length + " bytes)";
} catch (IOException e) {
return e.toString() + ", failed to parse response";
}
}

private static Charset getResponseCharset(Map<String, Collection<String>> headers) {

Collection<String> strings = headers.get("content-type");
if (strings == null || strings.size() == 0) {
return null;
}

Pattern pattern = Pattern.compile("charset=([^\\s])");
Matcher matcher = pattern.matcher(strings.iterator().next());
if (!matcher.lookingAt()) {
return null;
}

String group = matcher.group(1);
if (!Charset.isSupported(group)) {
return null;
}
return Charset.forName(group);

}
}
}
4 changes: 3 additions & 1 deletion core/src/test/java/feign/client/AbstractClientTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,9 @@ public void reasonPhraseIsOptional() throws IOException, InterruptedException {
@Test
public void parsesErrorResponse() {
thrown.expect(FeignException.class);
thrown.expectMessage("status 500 reading TestInterface#get()");
thrown.expectMessage(
"[500 Server Error] during [GET] to [http://localhost:" + server.getPort()
+ "/] [TestInterface#get()]: [ARGHH]");

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,23 +33,40 @@ public class DefaultErrorDecoderHttpErrorTest {
@Parameterized.Parameters(name = "error: [{0}], exception: [{1}]")
public static Object[][] errorCodes() {
return new Object[][] {
{400, FeignException.BadRequest.class},
{401, FeignException.Unauthorized.class},
{403, FeignException.Forbidden.class},
{404, FeignException.NotFound.class},
{405, FeignException.MethodNotAllowed.class},
{406, FeignException.NotAcceptable.class},
{409, FeignException.Conflict.class},
{429, FeignException.TooManyRequests.class},
{422, FeignException.UnprocessableEntity.class},
{450, FeignException.FeignClientException.class},
{500, FeignException.InternalServerError.class},
{501, FeignException.NotImplemented.class},
{502, FeignException.BadGateway.class},
{503, FeignException.ServiceUnavailable.class},
{504, FeignException.GatewayTimeout.class},
{599, FeignException.FeignServerException.class},
{599, FeignException.class},
{400, FeignException.BadRequest.class,
"[400 anything] during [GET] to [http://example.com/api] [Service#foo()]: [response body]"},
{401, FeignException.Unauthorized.class,
"[401 anything] during [GET] to [http://example.com/api] [Service#foo()]: [response body]"},
{403, FeignException.Forbidden.class,
"[403 anything] during [GET] to [http://example.com/api] [Service#foo()]: [response body]"},
{404, FeignException.NotFound.class,
"[404 anything] during [GET] to [http://example.com/api] [Service#foo()]: [response body]"},
{405, FeignException.MethodNotAllowed.class,
"[405 anything] during [GET] to [http://example.com/api] [Service#foo()]: [response body]"},
{406, FeignException.NotAcceptable.class,
"[406 anything] during [GET] to [http://example.com/api] [Service#foo()]: [response body]"},
{409, FeignException.Conflict.class,
"[409 anything] during [GET] to [http://example.com/api] [Service#foo()]: [response body]"},
{429, FeignException.TooManyRequests.class,
"[429 anything] during [GET] to [http://example.com/api] [Service#foo()]: [response body]"},
{422, FeignException.UnprocessableEntity.class,
"[422 anything] during [GET] to [http://example.com/api] [Service#foo()]: [response body]"},
{450, FeignException.FeignClientException.class,
"[450 anything] during [GET] to [http://example.com/api] [Service#foo()]: [response body]"},
{500, FeignException.InternalServerError.class,
"[500 anything] during [GET] to [http://example.com/api] [Service#foo()]: [response body]"},
{501, FeignException.NotImplemented.class,
"[501 anything] during [GET] to [http://example.com/api] [Service#foo()]: [response body]"},
{502, FeignException.BadGateway.class,
"[502 anything] during [GET] to [http://example.com/api] [Service#foo()]: [response body]"},
{503, FeignException.ServiceUnavailable.class,
"[503 anything] during [GET] to [http://example.com/api] [Service#foo()]: [response body]"},
{504, FeignException.GatewayTimeout.class,
"[504 anything] during [GET] to [http://example.com/api] [Service#foo()]: [response body]"},
{599, FeignException.FeignServerException.class,
"[599 anything] during [GET] to [http://example.com/api] [Service#foo()]: [response body]"},
{599, FeignException.class,
"[599 anything] during [GET] to [http://example.com/api] [Service#foo()]: [response body]"},
};
}

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

@Parameterized.Parameter(2)
public String expectedMessage;

private ErrorDecoder errorDecoder = new ErrorDecoder.Default();

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

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

assertThat(exception).isInstanceOf(expectedExceptionClass);
assertThat(((FeignException) exception).status()).isEqualTo(httpStatus);
assertThat(exception.getMessage()).isEqualTo(expectedMessage);
}

}