diff --git a/CHANGELOG.md b/CHANGELOG.md
index 220fa35c1..c26ac6596 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,8 @@
### Version 10.0
* Feign baseline is now JDK 8
* Removed @Deprecated methods marked for removal on feign 10
+* `RetryException` includes the `Method` used for the offending `Request`
+* `Response` objects now contain the `Request` used.
### Version 9.6
* Feign builder now supports flag `doNotCloseAfterDecode` to support lazy iteration of responses.
diff --git a/README.md b/README.md
index d38540f61..46c6e3a24 100644
--- a/README.md
+++ b/README.md
@@ -470,6 +470,37 @@ MyApi myApi = Feign.builder()
.target(MyApi.class, "https://api.hostname.com");
```
+### Error Handling
+If you need more control over handling unexpected responses, Feign instances can
+register a custom `ErrorDecoder` via the builder.
+
+```java
+MyApi myApi = Feign.builder()
+ .errorDecoder(new MyErrorDecoder())
+ .target(MyApi.class, "https://api.hostname.com");
+```
+
+All responses that result in an HTTP status not in the 2xx range will trigger the `ErrorDecoder`'s `decode` method, allowing
+you to handle the response, wrap the failure into a custom exception or perform any additional processing.
+If you want to retry the request again, throw a `RetryableException`. This will invoke the registered
+`Retyer`.
+
+### Retry
+Feign, by default, will automatically retry `IOException`s, regardless of HTTP method, treating them as transient network
+related exceptions, and any `RetryableException` thrown from an `ErrorDecoder`. To customize this
+behavior, register a custom `Retryer` instance via the builder.
+
+```java
+MyApi myApi = Feign.builder()
+ .retryer(new MyRetryer())
+ .target(MyApi.class, "https://api.hostname.com");
+```
+
+`Retryer`s are responsible for determining if a retry should occur by returning either a `true` or
+`false` from the method `continueOrPropagate(RetryableException e);` A `Retryer` instance will be
+created for each `Client` execution, allowing you to maintain state bewteen each request if desired.
+If the retry is determined to be unsucessful, the last `RetryException` will be thrown.
+
#### Static and Default Methods
Interfaces targeted by Feign may have static or default methods (if using Java 8+).
These allows Feign clients to contain logic that is not expressly defined by the underlying API.
diff --git a/benchmark/pom.xml b/benchmark/pom.xml
index 4857b42e1..b77a3bf83 100644
--- a/benchmark/pom.xml
+++ b/benchmark/pom.xml
@@ -66,17 +66,17 @@
io.reactivex
rxnetty
- 0.4.14
+ 0.5.1
- io.reactivex
- rxjava
- 1.0.17
+ io.netty
+ netty-buffer
+ 4.1.0.Beta7
- io.netty
- netty-codec-http
- 4.1.0.Beta8
+ io.reactivex
+ rxjava
+ 1.0.14
org.openjdk.jmh
diff --git a/benchmark/src/main/java/feign/benchmark/DecoderIteratorsBenchmark.java b/benchmark/src/main/java/feign/benchmark/DecoderIteratorsBenchmark.java
index 79ed9ad71..d8827eb5f 100644
--- a/benchmark/src/main/java/feign/benchmark/DecoderIteratorsBenchmark.java
+++ b/benchmark/src/main/java/feign/benchmark/DecoderIteratorsBenchmark.java
@@ -14,6 +14,7 @@
package feign.benchmark;
import com.fasterxml.jackson.core.type.TypeReference;
+import feign.Request;
import feign.Response;
import feign.Util;
import feign.codec.Decoder;
@@ -78,6 +79,7 @@ public void buildResponse() {
response = Response.builder()
.status(200)
.reason("OK")
+ .request(Request.create("GET", "/", Collections.emptyMap(), null, Util.UTF_8))
.headers(Collections.emptyMap())
.body(carsJson(Integer.valueOf(size)), Util.UTF_8)
.build();
diff --git a/benchmark/src/main/java/feign/benchmark/RealRequestBenchmarks.java b/benchmark/src/main/java/feign/benchmark/RealRequestBenchmarks.java
index 0fcbd7307..57d3e6c46 100644
--- a/benchmark/src/main/java/feign/benchmark/RealRequestBenchmarks.java
+++ b/benchmark/src/main/java/feign/benchmark/RealRequestBenchmarks.java
@@ -13,6 +13,16 @@
*/
package feign.benchmark;
+import feign.Logger;
+import feign.Logger.Level;
+import feign.Retryer;
+import io.netty.buffer.ByteBuf;
+import io.reactivex.netty.RxNetty;
+import io.reactivex.netty.protocol.http.server.HttpServer;
+import io.reactivex.netty.protocol.http.server.HttpServerRequest;
+import io.reactivex.netty.protocol.http.server.HttpServerResponse;
+import io.reactivex.netty.protocol.http.server.RequestHandler;
+import io.reactivex.netty.server.ErrorHandler;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import org.openjdk.jmh.annotations.Benchmark;
@@ -30,12 +40,7 @@
import java.util.concurrent.TimeUnit;
import feign.Feign;
import feign.Response;
-import io.netty.buffer.ByteBuf;
-import io.reactivex.netty.RxNetty;
-import io.reactivex.netty.protocol.http.server.HttpServer;
-import io.reactivex.netty.protocol.http.server.HttpServerRequest;
-import io.reactivex.netty.protocol.http.server.HttpServerResponse;
-import io.reactivex.netty.protocol.http.server.RequestHandler;
+import rx.Observable;
@Measurement(iterations = 5, time = 1)
@Warmup(iterations = 10, time = 1)
@@ -53,17 +58,15 @@ public class RealRequestBenchmarks {
@Setup
public void setup() {
- server = RxNetty.createHttpServer(SERVER_PORT, new RequestHandler() {
- public rx.Observable handle(HttpServerRequest request,
- HttpServerResponse response) {
- return response.flush();
- }
- });
+ server = RxNetty.createHttpServer(SERVER_PORT, (request, response) -> response.flush());
server.start();
client = new OkHttpClient();
client.retryOnConnectionFailure();
okFeign = Feign.builder()
.client(new feign.okhttp.OkHttpClient(client))
+ .logLevel(Level.NONE)
+ .logger(new Logger.ErrorLogger())
+ .retryer(new Retryer.Default())
.target(FeignTestInterface.class, "http://localhost:" + SERVER_PORT);
queryRequest = new Request.Builder()
.url("http://localhost:" + SERVER_PORT + "/?Action=GetUser&Version=2010-05-08&limit=1")
@@ -89,7 +92,10 @@ public okhttp3.Response query_baseCaseUsingOkHttp() throws IOException {
* How fast can we execute get commands synchronously using Feign?
*/
@Benchmark
- public Response query_feignUsingOkHttp() {
- return okFeign.query();
+ public boolean query_feignUsingOkHttp() {
+ /* auto close the response */
+ try (Response ignored = okFeign.query()) {
+ return true;
+ }
}
}
diff --git a/core/src/main/java/feign/Client.java b/core/src/main/java/feign/Client.java
index 02fb8d599..431711486 100644
--- a/core/src/main/java/feign/Client.java
+++ b/core/src/main/java/feign/Client.java
@@ -65,7 +65,7 @@ public Default(SSLSocketFactory sslContextFactory, HostnameVerifier hostnameVeri
@Override
public Response execute(Request request, Options options) throws IOException {
HttpURLConnection connection = convertAndSend(request, options);
- return convertResponse(connection).toBuilder().request(request).build();
+ return convertResponse(connection, request);
}
HttpURLConnection convertAndSend(Request request, Options options) throws IOException {
@@ -84,7 +84,7 @@ HttpURLConnection convertAndSend(Request request, Options options) throws IOExce
connection.setReadTimeout(options.readTimeoutMillis());
connection.setAllowUserInteraction(false);
connection.setInstanceFollowRedirects(options.isFollowRedirects());
- connection.setRequestMethod(request.method());
+ connection.setRequestMethod(request.httpMethod().name());
Collection contentEncodingValues = request.headers().get(CONTENT_ENCODING);
boolean gzipEncodedRequest =
@@ -139,7 +139,7 @@ HttpURLConnection convertAndSend(Request request, Options options) throws IOExce
return connection;
}
- Response convertResponse(HttpURLConnection connection) throws IOException {
+ Response convertResponse(HttpURLConnection connection, Request request) throws IOException {
int status = connection.getResponseCode();
String reason = connection.getResponseMessage();
@@ -170,6 +170,7 @@ Response convertResponse(HttpURLConnection connection) throws IOException {
.status(status)
.reason(reason)
.headers(headers)
+ .request(request)
.body(stream, length)
.build();
}
diff --git a/core/src/main/java/feign/FeignException.java b/core/src/main/java/feign/FeignException.java
index 59cf7c866..95d55a9c7 100644
--- a/core/src/main/java/feign/FeignException.java
+++ b/core/src/main/java/feign/FeignException.java
@@ -13,8 +13,8 @@
*/
package feign;
-import java.io.IOException;
import static java.lang.String.format;
+import java.io.IOException;
/**
* Origin exception type for all Http Apis.
@@ -43,7 +43,7 @@ public int status() {
static FeignException errorReading(Request request, Response ignored, IOException cause) {
return new FeignException(
- format("%s reading %s %s", cause.getMessage(), request.method(), request.url()),
+ format("%s reading %s %s", cause.getMessage(), request.httpMethod(), request.url()),
cause);
}
@@ -61,7 +61,9 @@ public static FeignException errorStatus(String methodKey, Response response) {
static FeignException errorExecuting(Request request, IOException cause) {
return new RetryableException(
- format("%s executing %s %s", cause.getMessage(), request.method(), request.url()), cause,
+ format("%s executing %s %s", cause.getMessage(), request.httpMethod(), request.url()),
+ request.httpMethod(),
+ cause,
null);
}
}
diff --git a/core/src/main/java/feign/Logger.java b/core/src/main/java/feign/Logger.java
index e34dd551c..dd4f99e1d 100644
--- a/core/src/main/java/feign/Logger.java
+++ b/core/src/main/java/feign/Logger.java
@@ -44,7 +44,7 @@ protected static String methodTag(String configKey) {
protected abstract void log(String configKey, String format, Object... args);
protected void logRequest(String configKey, Level logLevel, Request request) {
- log(configKey, "---> %s %s HTTP/1.1", request.method(), request.url());
+ log(configKey, "---> %s %s HTTP/1.1", request.httpMethod().name(), request.url());
if (logLevel.ordinal() >= Level.HEADERS.ordinal()) {
for (String field : request.headers().keySet()) {
diff --git a/core/src/main/java/feign/Request.java b/core/src/main/java/feign/Request.java
index f3b2aa12c..3bec9cf6d 100644
--- a/core/src/main/java/feign/Request.java
+++ b/core/src/main/java/feign/Request.java
@@ -13,48 +13,89 @@
*/
package feign;
+import static feign.Util.checkNotNull;
+import static feign.Util.valuesOrEmpty;
import java.net.HttpURLConnection;
import java.nio.charset.Charset;
import java.util.Collection;
import java.util.Map;
-import static feign.Util.checkNotNull;
-import static feign.Util.valuesOrEmpty;
/**
* An immutable request to an http server.
*/
public final class Request {
+ public enum HttpMethod {
+ GET, HEAD, POST, PUT, DELETE, CONNECT, OPTIONS, TRACE, PATCH
+ }
+
/**
* No parameters can be null except {@code body} and {@code charset}. All parameters must be
* effectively immutable, via safe copies, not mutating or otherwise.
+ *
+ * @deprecated {@link #create(HttpMethod, String, Map, byte[], Charset)}
*/
public static Request create(String method,
String url,
Map> headers,
byte[] body,
Charset charset) {
- return new Request(method, url, headers, body, charset);
+ checkNotNull(method, "httpMethod of %s", method);
+ HttpMethod httpMethod = HttpMethod.valueOf(method.toUpperCase());
+ return create(httpMethod, url, headers, body, charset);
}
- private final String method;
+ /**
+ * Builds a Request. All parameters must be effectively immutable, via safe copies.
+ *
+ * @param httpMethod for the request.
+ * @param url for the request.
+ * @param headers to include.
+ * @param body of the request, can be {@literal null}
+ * @param charset of the request, can be {@literal null}
+ * @return a Request
+ */
+ public static Request create(HttpMethod httpMethod,
+ String url,
+ Map> headers,
+ byte[] body,
+ Charset charset) {
+ return new Request(httpMethod, url, headers, body, charset);
+
+ }
+
+ private final HttpMethod httpMethod;
private final String url;
private final Map> headers;
private final byte[] body;
private final Charset charset;
- Request(String method, String url, Map> headers, byte[] body,
+ Request(HttpMethod method, String url, Map> headers, byte[] body,
Charset charset) {
- this.method = checkNotNull(method, "method of %s", url);
+ this.httpMethod = checkNotNull(method, "httpMethod of %s", method.name());
this.url = checkNotNull(url, "url");
this.headers = checkNotNull(headers, "headers of %s %s", method, url);
this.body = body; // nullable
this.charset = charset; // nullable
}
- /* Method to invoke on the server. */
+ /**
+ * Http Method for this request.
+ *
+ * @return the HttpMethod string
+ * @deprecated @see {@link #httpMethod()}
+ */
public String method() {
- return method;
+ return httpMethod.name();
+ }
+
+ /**
+ * Http Method for the request.
+ *
+ * @return the HttpMethod.
+ */
+ public HttpMethod httpMethod() {
+ return this.httpMethod;
}
/* Fully resolved URL including query. */
@@ -89,7 +130,7 @@ public byte[] body() {
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
- builder.append(method).append(' ').append(url).append(" HTTP/1.1\n");
+ builder.append(httpMethod).append(' ').append(url).append(" HTTP/1.1\n");
for (String field : headers.keySet()) {
for (String value : valuesOrEmpty(headers, field)) {
builder.append(field).append(": ").append(value).append('\n');
diff --git a/core/src/main/java/feign/RequestTemplate.java b/core/src/main/java/feign/RequestTemplate.java
index f59294f93..a6611ea17 100644
--- a/core/src/main/java/feign/RequestTemplate.java
+++ b/core/src/main/java/feign/RequestTemplate.java
@@ -166,7 +166,7 @@ public static String expand(String template, Map variables) {
}
private static Map> parseAndDecodeQueries(String queryLine) {
- Map> map = new LinkedHashMap>();
+ Map> map = new LinkedHashMap<>();
if (emptyToNull(queryLine) == null) {
return map;
}
diff --git a/core/src/main/java/feign/Response.java b/core/src/main/java/feign/Response.java
index 661bb80cd..366f27518 100644
--- a/core/src/main/java/feign/Response.java
+++ b/core/src/main/java/feign/Response.java
@@ -45,11 +45,12 @@ public final class Response implements Closeable {
private Response(Builder builder) {
checkState(builder.status >= 200, "Invalid status code: %s", builder.status);
+ checkState(builder.request != null, "original request is required");
this.status = builder.status;
+ this.request = builder.request;
this.reason = builder.reason; // nullable
this.headers = Collections.unmodifiableMap(caseInsensitiveCopyOf(builder.headers));
this.body = builder.body; // nullable
- this.request = builder.request; // nullable
}
public Builder toBuilder() {
@@ -121,11 +122,9 @@ public Builder body(String text, Charset charset) {
/**
* @see Response#request
- *
- * NOTE: will add null check in version 10 which may require changes to custom feign.Client
- * or loggers
*/
public Builder request(Request request) {
+ checkNotNull(request, "request is required");
this.request = request;
return this;
}
@@ -168,7 +167,7 @@ public Body body() {
}
/**
- * if present, the request that generated this response
+ * the request that generated this response
*/
public Request request() {
return request;
diff --git a/core/src/main/java/feign/RetryableException.java b/core/src/main/java/feign/RetryableException.java
index 79b8eafd9..8fd32c432 100644
--- a/core/src/main/java/feign/RetryableException.java
+++ b/core/src/main/java/feign/RetryableException.java
@@ -13,6 +13,7 @@
*/
package feign;
+import feign.Request.HttpMethod;
import java.util.Date;
/**
@@ -24,20 +25,24 @@ public class RetryableException extends FeignException {
private static final long serialVersionUID = 1L;
private final Long retryAfter;
+ private final HttpMethod httpMethod;
/**
* @param retryAfter usually corresponds to the {@link feign.Util#RETRY_AFTER} header.
*/
- public RetryableException(String message, Throwable cause, Date retryAfter) {
+ public RetryableException(String message, HttpMethod httpMethod, Throwable cause,
+ Date retryAfter) {
super(message, cause);
+ this.httpMethod = httpMethod;
this.retryAfter = retryAfter != null ? retryAfter.getTime() : null;
}
/**
* @param retryAfter usually corresponds to the {@link feign.Util#RETRY_AFTER} header.
*/
- public RetryableException(String message, Date retryAfter) {
+ public RetryableException(String message, HttpMethod httpMethod, Date retryAfter) {
super(message);
+ this.httpMethod = httpMethod;
this.retryAfter = retryAfter != null ? retryAfter.getTime() : null;
}
@@ -48,4 +53,8 @@ public RetryableException(String message, Date retryAfter) {
public Date retryAfter() {
return retryAfter != null ? new Date(retryAfter) : null;
}
+
+ public HttpMethod method() {
+ return this.httpMethod;
+ }
}
diff --git a/core/src/main/java/feign/codec/ErrorDecoder.java b/core/src/main/java/feign/codec/ErrorDecoder.java
index 6482203f8..2da7aefba 100644
--- a/core/src/main/java/feign/codec/ErrorDecoder.java
+++ b/core/src/main/java/feign/codec/ErrorDecoder.java
@@ -93,7 +93,11 @@ public Exception decode(String methodKey, Response response) {
FeignException exception = errorStatus(methodKey, response);
Date retryAfter = retryAfterDecoder.apply(firstOrNull(response.headers(), RETRY_AFTER));
if (retryAfter != null) {
- return new RetryableException(exception.getMessage(), exception, retryAfter);
+ return new RetryableException(
+ exception.getMessage(),
+ response.request().httpMethod(),
+ exception,
+ retryAfter);
}
return exception;
}
diff --git a/core/src/test/java/feign/FeignTest.java b/core/src/test/java/feign/FeignTest.java
index 0ddcf744a..e3cc35b4b 100644
--- a/core/src/test/java/feign/FeignTest.java
+++ b/core/src/test/java/feign/FeignTest.java
@@ -15,6 +15,7 @@
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
+import feign.Request.HttpMethod;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.SocketPolicy;
import okhttp3.mockwebserver.MockWebServer;
@@ -483,7 +484,7 @@ public void retryableExceptionInDecoder() throws Exception {
public Object decode(Response response, Type type) throws IOException {
String string = super.decode(response, type).toString();
if ("retry!".equals(string)) {
- throw new RetryableException(string, null);
+ throw new RetryableException(string, HttpMethod.POST, null);
}
return string;
}
@@ -524,7 +525,7 @@ public void ensureRetryerClonesItself() {
.errorDecoder(new ErrorDecoder() {
@Override
public Exception decode(String methodKey, Response response) {
- return new RetryableException("play it again sam!", null);
+ return new RetryableException("play it again sam!", HttpMethod.POST, null);
}
}).target(TestInterface.class, "http://localhost:" + server.getPort());
@@ -541,6 +542,7 @@ public void whenReturnTypeIsResponseNoErrorHandling() {
.status(302)
.reason("Found")
.headers(headers)
+ .request(Request.create("GET", "/", Collections.emptyMap(), null, Util.UTF_8))
.body(new byte[0])
.build();
@@ -740,6 +742,7 @@ private Response responseWithText(String text) {
return Response.builder()
.body(text, Util.UTF_8)
.status(200)
+ .request(Request.create("GET", "/api", Collections.emptyMap(), null, Util.UTF_8))
.headers(new HashMap>())
.build();
}
diff --git a/core/src/test/java/feign/ResponseTest.java b/core/src/test/java/feign/ResponseTest.java
index ae5ee00e3..1d80c3f42 100644
--- a/core/src/test/java/feign/ResponseTest.java
+++ b/core/src/test/java/feign/ResponseTest.java
@@ -30,6 +30,7 @@ public void reasonPhraseIsOptional() {
Response response = Response.builder()
.status(200)
.headers(Collections.>emptyMap())
+ .request(Request.create("GET", "/api", Collections.emptyMap(), null, Util.UTF_8))
.body(new byte[0])
.build();
@@ -45,6 +46,7 @@ public void canAccessHeadersCaseInsensitively() {
Response response = Response.builder()
.status(200)
.headers(headersMap)
+ .request(Request.create("GET", "/api", Collections.emptyMap(), null, Util.UTF_8))
.body(new byte[0])
.build();
assertThat(response.headers().get("content-type")).isEqualTo(valueList);
@@ -60,6 +62,7 @@ public void headerValuesWithSameNameOnlyVaryingInCaseAreMerged() {
Response response = Response.builder()
.status(200)
.headers(headersMap)
+ .request(Request.create("GET", "/api", Collections.emptyMap(), null, Util.UTF_8))
.body(new byte[0])
.build();
diff --git a/core/src/test/java/feign/client/DefaultClientTest.java b/core/src/test/java/feign/client/DefaultClientTest.java
index 3ebb9b3f6..1eef8e6c8 100644
--- a/core/src/test/java/feign/client/DefaultClientTest.java
+++ b/core/src/test/java/feign/client/DefaultClientTest.java
@@ -13,6 +13,8 @@
*/
package feign.client;
+import static org.hamcrest.core.Is.isA;
+import static org.junit.Assert.assertEquals;
import java.io.IOException;
import java.net.ProtocolException;
import javax.net.ssl.HostnameVerifier;
@@ -24,8 +26,6 @@
import feign.RetryableException;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.SocketPolicy;
-import static org.hamcrest.core.Is.isA;
-import static org.junit.Assert.assertEquals;
/**
* Tests client-specific behavior, such as ensuring Content-Length is sent when specified.
diff --git a/core/src/test/java/feign/codec/DefaultDecoderTest.java b/core/src/test/java/feign/codec/DefaultDecoderTest.java
index d646d53f2..99a827834 100644
--- a/core/src/test/java/feign/codec/DefaultDecoderTest.java
+++ b/core/src/test/java/feign/codec/DefaultDecoderTest.java
@@ -13,20 +13,22 @@
*/
package feign.codec;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.w3c.dom.Document;
+import static feign.Util.UTF_8;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.w3c.dom.Document;
+import feign.Request;
import feign.Response;
-import static feign.Util.UTF_8;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
+import feign.Util;
public class DefaultDecoderTest {
@@ -73,6 +75,7 @@ private Response knownResponse() {
.status(200)
.reason("OK")
.headers(headers)
+ .request(Request.create("GET", "/api", Collections.emptyMap(), null, Util.UTF_8))
.body(inputStream, content.length())
.build();
}
@@ -82,6 +85,7 @@ private Response nullBodyResponse() {
.status(200)
.reason("OK")
.headers(Collections.>emptyMap())
+ .request(Request.create("GET", "/api", Collections.emptyMap(), null, Util.UTF_8))
.build();
}
}
diff --git a/core/src/test/java/feign/codec/DefaultErrorDecoderTest.java b/core/src/test/java/feign/codec/DefaultErrorDecoderTest.java
index 417297b68..e2e3103c2 100644
--- a/core/src/test/java/feign/codec/DefaultErrorDecoderTest.java
+++ b/core/src/test/java/feign/codec/DefaultErrorDecoderTest.java
@@ -13,6 +13,9 @@
*/
package feign.codec;
+import feign.Request;
+import feign.Util;
+import java.util.Collections;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
@@ -43,6 +46,7 @@ public void throwsFeignException() throws Throwable {
Response response = Response.builder()
.status(500)
.reason("Internal server error")
+ .request(Request.create("GET", "/api", Collections.emptyMap(), null, Util.UTF_8))
.headers(headers)
.build();
@@ -57,6 +61,7 @@ public void throwsFeignExceptionIncludingBody() throws Throwable {
Response response = Response.builder()
.status(500)
.reason("Internal server error")
+ .request(Request.create("GET", "/api", Collections.emptyMap(), null, Util.UTF_8))
.headers(headers)
.body("hello world", UTF_8)
.build();
@@ -69,6 +74,7 @@ public void testFeignExceptionIncludesStatus() throws Throwable {
Response response = Response.builder()
.status(400)
.reason("Bad request")
+ .request(Request.create("GET", "/api", Collections.emptyMap(), null, Util.UTF_8))
.headers(headers)
.build();
@@ -87,6 +93,7 @@ public void retryAfterHeaderThrowsRetryableException() throws Throwable {
Response response = Response.builder()
.status(503)
.reason("Service Unavailable")
+ .request(Request.create("GET", "/api", Collections.emptyMap(), null, Util.UTF_8))
.headers(headers)
.build();
diff --git a/core/src/test/java/feign/stream/StreamDecoderTest.java b/core/src/test/java/feign/stream/StreamDecoderTest.java
index 909a5d2d1..3bf5fc725 100644
--- a/core/src/test/java/feign/stream/StreamDecoderTest.java
+++ b/core/src/test/java/feign/stream/StreamDecoderTest.java
@@ -16,8 +16,10 @@
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import feign.Feign;
+import feign.Request;
import feign.RequestLine;
import feign.Response;
+import feign.Util;
import java.io.BufferedReader;
import java.io.Closeable;
import java.io.IOException;
@@ -81,6 +83,7 @@ public void shouldCloseIteratorWhenStreamClosed() throws IOException {
.status(200)
.reason("OK")
.headers(Collections.emptyMap())
+ .request(Request.create("GET", "/api", Collections.emptyMap(), null, Util.UTF_8))
.body("", UTF_8)
.build();
diff --git a/gson/src/test/java/feign/gson/GsonCodecTest.java b/gson/src/test/java/feign/gson/GsonCodecTest.java
index ddfc47038..4efe8d619 100644
--- a/gson/src/test/java/feign/gson/GsonCodecTest.java
+++ b/gson/src/test/java/feign/gson/GsonCodecTest.java
@@ -17,6 +17,8 @@
import com.google.gson.reflect.TypeToken;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
+import feign.Request;
+import feign.Util;
import org.junit.Test;
import java.io.IOException;
import java.util.Arrays;
@@ -57,6 +59,7 @@ public void decodesMapObjectNumericalValuesAsInteger() throws Exception {
Response response = Response.builder()
.status(200)
.reason("OK")
+ .request(Request.create("GET", "/api", Collections.emptyMap(), null, Util.UTF_8))
.headers(Collections.>emptyMap())
.body("{\"foo\": 1}", UTF_8)
.build();
@@ -115,6 +118,7 @@ public void decodes() throws Exception {
.status(200)
.reason("OK")
.headers(Collections.>emptyMap())
+ .request(Request.create("GET", "/api", Collections.emptyMap(), null, Util.UTF_8))
.body(zonesJson, UTF_8)
.build();
assertEquals(zones,
@@ -127,6 +131,7 @@ public void nullBodyDecodesToNull() throws Exception {
.status(204)
.reason("OK")
.headers(Collections.>emptyMap())
+ .request(Request.create("GET", "/api", Collections.emptyMap(), null, Util.UTF_8))
.build();
assertNull(new GsonDecoder().decode(response, String.class));
}
@@ -137,6 +142,7 @@ public void emptyBodyDecodesToNull() throws Exception {
.status(204)
.reason("OK")
.headers(Collections.>emptyMap())
+ .request(Request.create("GET", "/api", Collections.emptyMap(), null, Util.UTF_8))
.body(new byte[0])
.build();
assertNull(new GsonDecoder().decode(response, String.class));
@@ -189,6 +195,7 @@ public void customDecoder() throws Exception {
.status(200)
.reason("OK")
.headers(Collections.>emptyMap())
+ .request(Request.create("GET", "/api", Collections.emptyMap(), null, Util.UTF_8))
.body(zonesJson, UTF_8)
.build();
assertEquals(zones, decoder.decode(response, new TypeToken>() {}.getType()));
@@ -224,6 +231,7 @@ public void notFoundDecodesToEmpty() throws Exception {
.status(404)
.reason("NOT FOUND")
.headers(Collections.>emptyMap())
+ .request(Request.create("GET", "/api", Collections.emptyMap(), null, Util.UTF_8))
.build();
assertThat((byte[]) new GsonDecoder().decode(response, byte[].class)).isEmpty();
}
diff --git a/httpclient/src/main/java/feign/httpclient/ApacheHttpClient.java b/httpclient/src/main/java/feign/httpclient/ApacheHttpClient.java
index 6766b0cc2..0a16ac619 100644
--- a/httpclient/src/main/java/feign/httpclient/ApacheHttpClient.java
+++ b/httpclient/src/main/java/feign/httpclient/ApacheHttpClient.java
@@ -82,7 +82,7 @@ public Response execute(Request request, Request.Options options) throws IOExcep
throw new IOException("URL '" + request.url() + "' couldn't be parsed into a URI", e);
}
HttpResponse httpResponse = client.execute(httpUriRequest);
- return toFeignResponse(httpResponse).toBuilder().request(request).build();
+ return toFeignResponse(httpResponse, request);
}
HttpUriRequest toHttpUriRequest(Request request, Request.Options options)
@@ -167,7 +167,7 @@ private ContentType getContentType(Request request) {
return contentType;
}
- Response toFeignResponse(HttpResponse httpResponse) throws IOException {
+ Response toFeignResponse(HttpResponse httpResponse, Request request) throws IOException {
StatusLine statusLine = httpResponse.getStatusLine();
int statusCode = statusLine.getStatusCode();
@@ -190,6 +190,7 @@ Response toFeignResponse(HttpResponse httpResponse) throws IOException {
.status(statusCode)
.reason(reason)
.headers(headers)
+ .request(request)
.body(toFeignBody(httpResponse))
.build();
}
diff --git a/jackson-jaxb/src/test/java/feign/jackson/jaxb/JacksonJaxbCodecTest.java b/jackson-jaxb/src/test/java/feign/jackson/jaxb/JacksonJaxbCodecTest.java
index ccd9533fd..96628b81e 100644
--- a/jackson-jaxb/src/test/java/feign/jackson/jaxb/JacksonJaxbCodecTest.java
+++ b/jackson-jaxb/src/test/java/feign/jackson/jaxb/JacksonJaxbCodecTest.java
@@ -13,6 +13,8 @@
*/
package feign.jackson.jaxb;
+import feign.Request;
+import feign.Util;
import org.junit.Test;
import java.util.Collection;
import java.util.Collections;
@@ -42,6 +44,7 @@ public void decodeTest() throws Exception {
Response response = Response.builder()
.status(200)
.reason("OK")
+ .request(Request.create("GET", "/api", Collections.emptyMap(), null, Util.UTF_8))
.headers(Collections.>emptyMap())
.body("{\"value\":\"Test\"}", UTF_8)
.build();
@@ -57,6 +60,7 @@ public void notFoundDecodesToEmpty() throws Exception {
Response response = Response.builder()
.status(404)
.reason("NOT FOUND")
+ .request(Request.create("GET", "/api", Collections.emptyMap(), null, Util.UTF_8))
.headers(Collections.>emptyMap())
.build();
assertThat((byte[]) new JacksonJaxbJsonDecoder().decode(response, byte[].class)).isEmpty();
diff --git a/jackson/src/test/java/feign/jackson/JacksonCodecTest.java b/jackson/src/test/java/feign/jackson/JacksonCodecTest.java
index 3aca54cbe..12db55cc5 100644
--- a/jackson/src/test/java/feign/jackson/JacksonCodecTest.java
+++ b/jackson/src/test/java/feign/jackson/JacksonCodecTest.java
@@ -23,6 +23,8 @@
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
+import feign.Request;
+import feign.Util;
import org.junit.Test;
import java.io.Closeable;
import java.io.IOException;
@@ -95,6 +97,7 @@ public void decodes() throws Exception {
Response response = Response.builder()
.status(200)
.reason("OK")
+ .request(Request.create("GET", "/api", Collections.emptyMap(), null, Util.UTF_8))
.headers(Collections.>emptyMap())
.body(zonesJson, UTF_8)
.build();
@@ -107,6 +110,7 @@ public void nullBodyDecodesToNull() throws Exception {
Response response = Response.builder()
.status(204)
.reason("OK")
+ .request(Request.create("GET", "/api", Collections.emptyMap(), null, Util.UTF_8))
.headers(Collections.>emptyMap())
.build();
assertNull(new JacksonDecoder().decode(response, String.class));
@@ -117,6 +121,7 @@ public void emptyBodyDecodesToNull() throws Exception {
Response response = Response.builder()
.status(204)
.reason("OK")
+ .request(Request.create("GET", "/api", Collections.emptyMap(), null, Util.UTF_8))
.headers(Collections.>emptyMap())
.body(new byte[0])
.build();
@@ -136,6 +141,7 @@ public void customDecoder() throws Exception {
Response response = Response.builder()
.status(200)
.reason("OK")
+ .request(Request.create("GET", "/api", Collections.emptyMap(), null, Util.UTF_8))
.headers(Collections.>emptyMap())
.body(zonesJson, UTF_8)
.build();
@@ -172,6 +178,7 @@ public void decodesIterator() throws Exception {
Response response = Response.builder()
.status(200)
.reason("OK")
+ .request(Request.create("GET", "/api", Collections.emptyMap(), null, Util.UTF_8))
.headers(Collections.>emptyMap())
.body(zonesJson, UTF_8)
.build();
@@ -194,6 +201,7 @@ public void nullBodyDecodesToNullIterator() throws Exception {
Response response = Response.builder()
.status(204)
.reason("OK")
+ .request(Request.create("GET", "/api", Collections.emptyMap(), null, Util.UTF_8))
.headers(Collections.>emptyMap())
.build();
assertNull(JacksonIteratorDecoder.create().decode(response, Iterator.class));
@@ -204,6 +212,7 @@ public void emptyBodyDecodesToNullIterator() throws Exception {
Response response = Response.builder()
.status(204)
.reason("OK")
+ .request(Request.create("GET", "/api", Collections.emptyMap(), null, Util.UTF_8))
.headers(Collections.>emptyMap())
.body(new byte[0])
.build();
@@ -275,6 +284,7 @@ public void notFoundDecodesToEmpty() throws Exception {
Response response = Response.builder()
.status(404)
.reason("NOT FOUND")
+ .request(Request.create("GET", "/api", Collections.emptyMap(), null, Util.UTF_8))
.headers(Collections.>emptyMap())
.build();
assertThat((byte[]) new JacksonDecoder().decode(response, byte[].class)).isEmpty();
@@ -286,6 +296,7 @@ public void notFoundDecodesToEmptyIterator() throws Exception {
Response response = Response.builder()
.status(404)
.reason("NOT FOUND")
+ .request(Request.create("GET", "/api", Collections.emptyMap(), null, Util.UTF_8))
.headers(Collections.>emptyMap())
.build();
assertThat((byte[]) JacksonIteratorDecoder.create().decode(response, byte[].class)).isEmpty();
diff --git a/jackson/src/test/java/feign/jackson/JacksonIteratorTest.java b/jackson/src/test/java/feign/jackson/JacksonIteratorTest.java
index 7ba81aa21..5fe83a635 100644
--- a/jackson/src/test/java/feign/jackson/JacksonIteratorTest.java
+++ b/jackson/src/test/java/feign/jackson/JacksonIteratorTest.java
@@ -14,7 +14,9 @@
package feign.jackson;
import com.fasterxml.jackson.databind.ObjectMapper;
+import feign.Request;
import feign.Response;
+import feign.Util;
import feign.codec.DecodeException;
import feign.jackson.JacksonIteratorDecoder.JacksonIterator;
import org.junit.Rule;
@@ -86,6 +88,7 @@ public void close() throws IOException {
Response response = Response.builder()
.status(200)
.reason("OK")
+ .request(Request.create("GET", "/api", Collections.emptyMap(), null, Util.UTF_8))
.headers(Collections.>emptyMap())
.body(inputStream, jsonBytes.length)
.build();
@@ -109,6 +112,7 @@ public void close() throws IOException {
Response response = Response.builder()
.status(200)
.reason("OK")
+ .request(Request.create("GET", "/api", Collections.emptyMap(), null, Util.UTF_8))
.headers(Collections.>emptyMap())
.body(inputStream, jsonBytes.length)
.build();
@@ -138,6 +142,7 @@ JacksonIterator iterator(Class type, String json) throws IOException {
Response response = Response.builder()
.status(200)
.reason("OK")
+ .request(Request.create("GET", "/api", Collections.emptyMap(), null, Util.UTF_8))
.headers(Collections.>emptyMap())
.body(json, UTF_8)
.build();
diff --git a/jaxb/src/test/java/feign/jaxb/JAXBCodecTest.java b/jaxb/src/test/java/feign/jaxb/JAXBCodecTest.java
index 915de3178..c784d70e6 100644
--- a/jaxb/src/test/java/feign/jaxb/JAXBCodecTest.java
+++ b/jaxb/src/test/java/feign/jaxb/JAXBCodecTest.java
@@ -13,6 +13,8 @@
*/
package feign.jaxb;
+import feign.Request;
+import feign.Util;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
@@ -164,6 +166,7 @@ public void decodesXml() throws Exception {
Response response = Response.builder()
.status(200)
.reason("OK")
+ .request(Request.create("GET", "/api", Collections.emptyMap(), null, Util.UTF_8))
.headers(Collections.>emptyMap())
.body(mockXml, UTF_8)
.build();
@@ -188,6 +191,7 @@ class ParameterizedHolder {
Response response = Response.builder()
.status(200)
.reason("OK")
+ .request(Request.create("GET", "/api", Collections.emptyMap(), null, Util.UTF_8))
.headers(Collections.>emptyMap())
.body("", UTF_8)
.build();
@@ -201,6 +205,7 @@ public void notFoundDecodesToEmpty() throws Exception {
Response response = Response.builder()
.status(404)
.reason("NOT FOUND")
+ .request(Request.create("GET", "/api", Collections.emptyMap(), null, Util.UTF_8))
.headers(Collections.>emptyMap())
.build();
assertThat((byte[]) new JAXBDecoder(new JAXBContextFactory.Builder().build())
diff --git a/okhttp/src/main/java/feign/okhttp/OkHttpClient.java b/okhttp/src/main/java/feign/okhttp/OkHttpClient.java
index 7eae32e2d..94064c709 100644
--- a/okhttp/src/main/java/feign/okhttp/OkHttpClient.java
+++ b/okhttp/src/main/java/feign/okhttp/OkHttpClient.java
@@ -90,12 +90,14 @@ static Request toOkHttpRequest(feign.Request input) {
return requestBuilder.build();
}
- private static feign.Response toFeignResponse(Response input) throws IOException {
+ private static feign.Response toFeignResponse(Response response, feign.Request request)
+ throws IOException {
return feign.Response.builder()
- .status(input.code())
- .reason(input.message())
- .headers(toMap(input.headers()))
- .body(toBody(input.body()))
+ .status(response.code())
+ .reason(response.message())
+ .request(request)
+ .headers(toMap(response.headers()))
+ .body(toBody(response.body()))
.build();
}
@@ -159,6 +161,6 @@ public feign.Response execute(feign.Request input, feign.Request.Options options
}
Request request = toOkHttpRequest(input);
Response response = requestScoped.newCall(request).execute();
- return toFeignResponse(response).toBuilder().request(input).build();
+ return toFeignResponse(response, input).toBuilder().request(input).build();
}
}
diff --git a/sax/src/test/java/feign/sax/SAXDecoderTest.java b/sax/src/test/java/feign/sax/SAXDecoderTest.java
index c57f88028..3755fec15 100644
--- a/sax/src/test/java/feign/sax/SAXDecoderTest.java
+++ b/sax/src/test/java/feign/sax/SAXDecoderTest.java
@@ -13,6 +13,8 @@
*/
package feign.sax;
+import feign.Request;
+import feign.Util;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
@@ -72,6 +74,7 @@ private Response statusFailedResponse() {
return Response.builder()
.status(200)
.reason("OK")
+ .request(Request.create("GET", "/api", Collections.emptyMap(), null, Util.UTF_8))
.headers(Collections.>emptyMap())
.body(statusFailed, UTF_8)
.build();
@@ -82,6 +85,7 @@ public void nullBodyDecodesToNull() throws Exception {
Response response = Response.builder()
.status(204)
.reason("OK")
+ .request(Request.create("GET", "/api", Collections.emptyMap(), null, Util.UTF_8))
.headers(Collections.>emptyMap())
.build();
assertNull(decoder.decode(response, String.class));
@@ -93,6 +97,7 @@ public void notFoundDecodesToEmpty() throws Exception {
Response response = Response.builder()
.status(404)
.reason("NOT FOUND")
+ .request(Request.create("GET", "/api", Collections.emptyMap(), null, Util.UTF_8))
.headers(Collections.>emptyMap())
.build();
assertThat((byte[]) decoder.decode(response, byte[].class)).isEmpty();
diff --git a/slf4j/src/test/java/feign/slf4j/Slf4jLoggerTest.java b/slf4j/src/test/java/feign/slf4j/Slf4jLoggerTest.java
index e4ebb7bd2..b046e27bc 100644
--- a/slf4j/src/test/java/feign/slf4j/Slf4jLoggerTest.java
+++ b/slf4j/src/test/java/feign/slf4j/Slf4jLoggerTest.java
@@ -13,6 +13,7 @@
*/
package feign.slf4j;
+import feign.Util;
import org.junit.Rule;
import org.junit.Test;
import org.slf4j.LoggerFactory;
@@ -33,6 +34,7 @@ public class Slf4jLoggerTest {
Response.builder()
.status(200)
.reason("OK")
+ .request(Request.create("GET", "/api", Collections.emptyMap(), null, Util.UTF_8))
.headers(Collections.>emptyMap())
.body(new byte[0])
.build();