Repository: square/retrofit
Issue: Add URL to callback responses.

It would be a good idea to add the URL that was was in the server object to the callback responses. 

For instance if a changeable server is used and changed during asynchronous request before the callback is triggered it would be great to know what URL the response came from.


Issue comments:
Dupe #232. The server URL is a bit easier to include, however.


Pull request review comments:

Patch that resolved the issue:
diff --git a/retrofit-mock/src/main/java/retrofit/MockHttpException.java b/retrofit-mock/src/main/java/retrofit/MockHttpException.java
index 87d18b3bf8..908cd562d3 100644
--- a/retrofit-mock/src/main/java/retrofit/MockHttpException.java
+++ b/retrofit-mock/src/main/java/retrofit/MockHttpException.java
@@ -102,6 +102,6 @@ public MockHttpException withHeader(String name, String value) {
   }
 
   Response toResponse(Converter converter) {
-    return new Response(code, reason, headers, new MockTypedInput(converter, responseBody));
+    return new Response("", code, reason, headers, new MockTypedInput(converter, responseBody));
   }
 }
diff --git a/retrofit/src/main/java/retrofit/Utils.java b/retrofit/src/main/java/retrofit/Utils.java
index 4fbe02039a..49c0cbe755 100644
--- a/retrofit/src/main/java/retrofit/Utils.java
+++ b/retrofit/src/main/java/retrofit/Utils.java
@@ -93,7 +93,8 @@ static Response readBodyToBytesIfNecessary(Response response) throws IOException
   }
 
   static Response replaceResponseBody(Response response, TypedInput body) {
-    return new Response(response.getStatus(), response.getReason(), response.getHeaders(), body);
+    return new Response(response.getUrl(), response.getStatus(), response.getReason(),
+        response.getHeaders(), body);
   }
 
   static <T> void validateServiceClass(Class<T> service) {
diff --git a/retrofit/src/main/java/retrofit/client/ApacheClient.java b/retrofit/src/main/java/retrofit/client/ApacheClient.java
index e65ff060ad..236e7005ec 100644
--- a/retrofit/src/main/java/retrofit/client/ApacheClient.java
+++ b/retrofit/src/main/java/retrofit/client/ApacheClient.java
@@ -62,7 +62,7 @@ public ApacheClient(HttpClient client) {
   @Override public Response execute(Request request) throws IOException {
     HttpUriRequest apacheRequest = createRequest(request);
     HttpResponse apacheResponse = execute(client, apacheRequest);
-    return parseResponse(apacheResponse);
+    return parseResponse(request.getUrl(), apacheResponse);
   }
 
   /** Execute the specified {@code request} using the provided {@code client}. */
@@ -74,7 +74,7 @@ static HttpUriRequest createRequest(Request request) {
     return new GenericHttpRequest(request);
   }
 
-  static Response parseResponse(HttpResponse response) throws IOException {
+  static Response parseResponse(String url, HttpResponse response) throws IOException {
     StatusLine statusLine = response.getStatusLine();
     int status = statusLine.getStatusCode();
     String reason = statusLine.getReasonPhrase();
@@ -97,7 +97,7 @@ static Response parseResponse(HttpResponse response) throws IOException {
       body = new TypedByteArray(contentType, bytes);
     }
 
-    return new Response(status, reason, headers, body);
+    return new Response(url, status, reason, headers, body);
   }
 
   private static class GenericHttpRequest extends HttpEntityEnclosingRequestBase {
diff --git a/retrofit/src/main/java/retrofit/client/Response.java b/retrofit/src/main/java/retrofit/client/Response.java
index 44c1406b9a..9c605f232d 100644
--- a/retrofit/src/main/java/retrofit/client/Response.java
+++ b/retrofit/src/main/java/retrofit/client/Response.java
@@ -22,11 +22,34 @@
 
 /** An HTTP response. */
 public final class Response {
+  private final String url;
   private final int status;
   private final String reason;
   private final List<Header> headers;
   private final TypedInput body;
 
+  public Response(String url, int status, String reason, List<Header> headers, TypedInput body) {
+    if (url == null) {
+      throw new IllegalArgumentException("url == null");
+    }
+    if (status < 200) {
+      throw new IllegalArgumentException("Invalid status code: " + status);
+    }
+    if (reason == null) {
+      throw new IllegalArgumentException("reason == null");
+    }
+    if (headers == null) {
+      throw new IllegalArgumentException("headers == null");
+    }
+
+    this.url = url;
+    this.status = status;
+    this.reason = reason;
+    this.headers = Collections.unmodifiableList(new ArrayList<Header>(headers));
+    this.body = body;
+  }
+
+  @Deprecated
   public Response(int status, String reason, List<Header> headers, TypedInput body) {
     if (status < 200) {
       throw new IllegalArgumentException("Invalid status code: " + status);
@@ -38,12 +61,18 @@ public Response(int status, String reason, List<Header> headers, TypedInput body
       throw new IllegalArgumentException("headers == null");
     }
 
+    this.url = null;
     this.status = status;
     this.reason = reason;
     this.headers = Collections.unmodifiableList(new ArrayList<Header>(headers));
     this.body = body;
   }
 
+  /** Request URL. */
+  public String getUrl() {
+    return url;
+  }
+
   /** Status line code. */
   public int getStatus() {
     return status;
diff --git a/retrofit/src/main/java/retrofit/client/UrlConnectionClient.java b/retrofit/src/main/java/retrofit/client/UrlConnectionClient.java
index 10eaf37238..97fbbceff2 100644
--- a/retrofit/src/main/java/retrofit/client/UrlConnectionClient.java
+++ b/retrofit/src/main/java/retrofit/client/UrlConnectionClient.java
@@ -111,7 +111,7 @@ Response readResponse(HttpURLConnection connection) throws IOException {
       stream = connection.getInputStream();
     }
     TypedInput responseBody = new TypedInputStream(mimeType, length, stream);
-    return new Response(status, reason, headers, responseBody);
+    return new Response(connection.getURL().toString(), status, reason, headers, responseBody);
   }
 
   private static class TypedInputStream implements TypedInput {


Test patch:
diff --git a/retrofit/src/test/java/retrofit/ErrorHandlerTest.java b/retrofit/src/test/java/retrofit/ErrorHandlerTest.java
index 981b3b05b1..4230d5b96a 100644
--- a/retrofit/src/test/java/retrofit/ErrorHandlerTest.java
+++ b/retrofit/src/test/java/retrofit/ErrorHandlerTest.java
@@ -30,7 +30,7 @@ static class TestException extends Exception {
   /* An HTTP client which always returns a 400 response */
   static class MockInvalidResponseClient implements Client {
     @Override public Response execute(Request request) throws IOException {
-      return new Response(400, "invalid request", Collections.<Header>emptyList(), null);
+      return new Response("", 400, "invalid request", Collections.<Header>emptyList(), null);
     }
   }
 
diff --git a/retrofit/src/test/java/retrofit/RestAdapterTest.java b/retrofit/src/test/java/retrofit/RestAdapterTest.java
index 67fa3cf1fb..4239c693cc 100644
--- a/retrofit/src/test/java/retrofit/RestAdapterTest.java
+++ b/retrofit/src/test/java/retrofit/RestAdapterTest.java
@@ -127,7 +127,7 @@ private interface InvalidExample extends Example {
     Object data = new Object();
     when(mockProfiler.beforeCall()).thenReturn(data);
     when(mockClient.execute(any(Request.class))) //
-        .thenReturn(new Response(200, "OK", NO_HEADERS, null));
+        .thenReturn(new Response("http://example.com/", 200, "OK", NO_HEADERS, null));
 
     example.something();
 
@@ -155,7 +155,7 @@ public void log(String message) {
         .create(Example.class);
 
     when(mockClient.execute(any(Request.class))) //
-        .thenReturn(new Response(200, "OK", TWO_HEADERS, new TypedString("{}")));
+        .thenReturn(new Response("http://example.com/", 200, "OK", TWO_HEADERS, new TypedString("{}")));
 
     example.something();
     assertThat(logMessages).hasSize(2);
@@ -182,7 +182,7 @@ public void log(String message) {
         .create(Example.class);
 
     when(mockClient.execute(any(Request.class))) //
-        .thenReturn(new Response(200, "OK", TWO_HEADERS, new TypedString("{}")));
+        .thenReturn(new Response("http://example.com/", 200, "OK", TWO_HEADERS, new TypedString("{}")));
 
     example.something();
     assertThat(logMessages).hasSize(7);
@@ -214,7 +214,7 @@ public void log(String message) {
         .create(Example.class);
 
     when(mockClient.execute(any(Request.class))) //
-        .thenReturn(new Response(200, "OK", TWO_HEADERS, new TypedString("{}")));
+        .thenReturn(new Response("http://example.com/", 200, "OK", TWO_HEADERS, new TypedString("{}")));
 
     example.something(new TypedString("Hi"));
     assertThat(logMessages).hasSize(13);
@@ -252,7 +252,7 @@ public void log(String message) {
         .create(Example.class);
 
     when(mockClient.execute(any(Request.class))) //
-        .thenReturn(new Response(200, "OK", TWO_HEADERS, null));
+        .thenReturn(new Response("http://example.com/", 200, "OK", TWO_HEADERS, null));
 
     example.something();
     assertThat(logMessages).hasSize(7);
@@ -267,7 +267,7 @@ public void log(String message) {
 
   @Test public void successfulRequestResponseWhenMimeTypeMissing() throws Exception {
     when(mockClient.execute(any(Request.class))) //
-        .thenReturn(new Response(200, "OK", NO_HEADERS, NO_MIME_BODY));
+        .thenReturn(new Response("http://example.com/", 200, "OK", NO_HEADERS, NO_MIME_BODY));
 
     example.something();
   }
@@ -291,7 +291,7 @@ public void log(String message) {
         .create(Example.class);
 
     when(mockClient.execute(any(Request.class))) //
-        .thenReturn(new Response(200, "OK", TWO_HEADERS, NO_MIME_BODY));
+        .thenReturn(new Response("http://example.com/", 200, "OK", TWO_HEADERS, NO_MIME_BODY));
 
     example.something();
     assertThat(logMessages).hasSize(9);
@@ -308,7 +308,7 @@ public void log(String message) {
 
   @Test public void synchronousDoesNotUseExecutors() throws Exception {
     when(mockClient.execute(any(Request.class))) //
-        .thenReturn(new Response(200, "OK", NO_HEADERS, null));
+        .thenReturn(new Response("http://example.com/", 200, "OK", NO_HEADERS, null));
 
     example.something();
 
@@ -317,7 +317,7 @@ public void log(String message) {
   }
 
   @Test public void asynchronousUsesExecutors() throws Exception {
-    Response response = new Response(200, "OK", NO_HEADERS, new TypedString("{}"));
+    Response response = new Response("http://example.com/", 200, "OK", NO_HEADERS, new TypedString("{}"));
     when(mockClient.execute(any(Request.class))).thenReturn(response);
     Callback<Object> callback = mock(Callback.class);
 
@@ -330,7 +330,7 @@ public void log(String message) {
 
   @Test public void malformedResponseThrowsConversionException() throws Exception {
     when(mockClient.execute(any(Request.class))) //
-        .thenReturn(new Response(200, "OK", NO_HEADERS, new TypedString("{")));
+        .thenReturn(new Response("http://example.com/", 200, "OK", NO_HEADERS, new TypedString("{")));
 
     try {
       example.something();
@@ -344,7 +344,7 @@ public void log(String message) {
 
   @Test public void errorResponseThrowsHttpError() throws Exception {
     when(mockClient.execute(any(Request.class))) //
-        .thenReturn(new Response(500, "Internal Server Error", NO_HEADERS, null));
+        .thenReturn(new Response("http://example.com/", 500, "Internal Server Error", NO_HEADERS, null));
 
     try {
       example.something();
@@ -373,7 +373,7 @@ public void log(String message) {
         .create(Example.class);
 
     Response responseMissingMimeType = //
-        new Response(403, "Forbidden", TWO_HEADERS, NO_MIME_BODY);
+        new Response("http://example.com/", 403, "Forbidden", TWO_HEADERS, NO_MIME_BODY);
 
     when(mockClient.execute(any(Request.class))).thenReturn(responseMissingMimeType);
 
@@ -415,7 +415,7 @@ public void log(String message) {
         .create(Example.class);
 
     when(mockClient.execute(any(Request.class))) //
-        .thenReturn(new Response(500, "Internal Server Error", TWO_HEADERS, null));
+        .thenReturn(new Response("http://example.com/", 500, "Internal Server Error", TWO_HEADERS, null));
 
     try {
       example.something();
@@ -456,7 +456,7 @@ public void log(String message) {
     doReturn(bodyStream).when(body).in();
 
     when(mockClient.execute(any(Request.class))) //
-        .thenReturn(new Response(200, "OK", NO_HEADERS, body));
+        .thenReturn(new Response("http://example.com/", 200, "OK", NO_HEADERS, body));
 
     try {
       example.something();
@@ -481,7 +481,7 @@ public void log(String message) {
   }
 
   @Test public void getResponseDirectly() throws Exception {
-    Response response = new Response(200, "OK", NO_HEADERS, null);
+    Response response = new Response("http://example.com/", 200, "OK", NO_HEADERS, null);
     when(mockClient.execute(any(Request.class))) //
         .thenReturn(response);
     assertThat(example.direct()).isSameAs(response);
@@ -502,7 +502,7 @@ public void log(String message) {
     ByteArrayInputStream is = spy(new ByteArrayInputStream("hello".getBytes()));
     TypedInput typedInput = mock(TypedInput.class);
     when(typedInput.in()).thenReturn(is);
-    Response response = new Response(200, "OK", NO_HEADERS, typedInput);
+    Response response = new Response("http://example.com/", 200, "OK", NO_HEADERS, typedInput);
     when(mockClient.execute(any(Request.class))) //
         .thenReturn(response);
     example.something();
@@ -510,7 +510,7 @@ public void log(String message) {
   }
 
   @Test public void getResponseDirectlyAsync() throws Exception {
-    Response response = new Response(200, "OK", NO_HEADERS, null);
+    Response response = new Response("http://example.com/", 200, "OK", NO_HEADERS, null);
     when(mockClient.execute(any(Request.class))) //
         .thenReturn(response);
     Callback<Response> callback = mock(Callback.class);
@@ -524,7 +524,7 @@ public void log(String message) {
 
   @Test public void observableCallsOnNext() throws Exception {
     when(mockClient.execute(any(Request.class))) //
-        .thenReturn(new Response(200, "OK", NO_HEADERS, new TypedString("hello")));
+        .thenReturn(new Response("http://example.com/", 200, "OK", NO_HEADERS, new TypedString("hello")));
     Action1<String> action = mock(Action1.class);
     example.observable("Howdy").subscribe(action);
     verify(action).call(eq("hello"));
@@ -532,7 +532,7 @@ public void log(String message) {
 
   @Test public void observableCallsOnError() throws Exception {
     when(mockClient.execute(any(Request.class))) //
-        .thenReturn(new Response(300, "FAIL", NO_HEADERS, new TypedString("bummer")));
+        .thenReturn(new Response("http://example.com/", 300, "FAIL", NO_HEADERS, new TypedString("bummer")));
     Action1<String> onSuccess = mock(Action1.class);
     Action1<Throwable> onError = mock(Action1.class);
     example.observable("Howdy").subscribe(onSuccess, onError);
@@ -543,7 +543,7 @@ public void log(String message) {
   @Test public void observableHandlesParams() throws Exception {
     ArgumentCaptor<Request> requestCaptor = ArgumentCaptor.forClass(Request.class);
     when(mockClient.execute(requestCaptor.capture())) //
-        .thenReturn(new Response(200, "OK", NO_HEADERS, new TypedString("hello")));
+        .thenReturn(new Response("http://example.com/", 200, "OK", NO_HEADERS, new TypedString("hello")));
     ArgumentCaptor<Response> responseCaptor = ArgumentCaptor.forClass(Response.class);
     Action1<Response> action = mock(Action1.class);
     example.observable("X", "Y").subscribe(action);
@@ -557,7 +557,7 @@ public void log(String message) {
   }
 
   @Test public void observableUsesHttpExecutor() throws IOException {
-    Response response = new Response(200, "OK", NO_HEADERS, new TypedString("hello"));
+    Response response = new Response("http://example.com/", 200, "OK", NO_HEADERS, new TypedString("hello"));
     when(mockClient.execute(any(Request.class))).thenReturn(response);
 
     example.observable("Howdy").subscribe(mock(Action1.class));
diff --git a/retrofit/src/test/java/retrofit/client/ApacheClientTest.java b/retrofit/src/test/java/retrofit/client/ApacheClientTest.java
index 82e51fb96d..74cc4c3bb8 100644
--- a/retrofit/src/test/java/retrofit/client/ApacheClientTest.java
+++ b/retrofit/src/test/java/retrofit/client/ApacheClientTest.java
@@ -100,8 +100,9 @@ public class ApacheClientTest {
     apacheResponse.addHeader("Content-Type", "text/plain");
     apacheResponse.addHeader("foo", "bar");
     apacheResponse.addHeader("kit", "kat");
-    Response response = ApacheClient.parseResponse(apacheResponse);
+    Response response = ApacheClient.parseResponse(HOST + "/foo/bar/", apacheResponse);
 
+    assertThat(response.getUrl()).isEqualTo(HOST + "/foo/bar/");
     assertThat(response.getStatus()).isEqualTo(200);
     assertThat(response.getReason()).isEqualTo("OK");
     assertThat(response.getHeaders()).hasSize(3) //
@@ -115,8 +116,9 @@ public class ApacheClientTest {
     HttpResponse apacheResponse = new BasicHttpResponse(statusLine);
     apacheResponse.addHeader("foo", "bar");
     apacheResponse.addHeader("kit", "kat");
-    Response response = ApacheClient.parseResponse(apacheResponse);
+    Response response = ApacheClient.parseResponse(HOST + "/foo/bar/", apacheResponse);
 
+    assertThat(response.getUrl()).isEqualTo(HOST + "/foo/bar/");
     assertThat(response.getStatus()).isEqualTo(200);
     assertThat(response.getReason()).isEqualTo("OK");
     assertThat(response.getHeaders()).hasSize(2) //
diff --git a/retrofit/src/test/java/retrofit/client/UrlConnectionClientTest.java b/retrofit/src/test/java/retrofit/client/UrlConnectionClientTest.java
index e00f321ce9..4adb93fc75 100644
--- a/retrofit/src/test/java/retrofit/client/UrlConnectionClientTest.java
+++ b/retrofit/src/test/java/retrofit/client/UrlConnectionClientTest.java
@@ -113,6 +113,7 @@ public class UrlConnectionClientTest {
     connection.setInputStream(new ByteArrayInputStream("hello".getBytes("UTF-8")));
     Response response = client.readResponse(connection);
 
+    assertThat(response.getUrl()).isEqualTo(HOST);
     assertThat(response.getStatus()).isEqualTo(200);
     assertThat(response.getReason()).isEqualTo("OK");
     assertThat(response.getHeaders()).hasSize(3) //
@@ -131,6 +132,7 @@ public class UrlConnectionClientTest {
     connection.setInputStream(new ByteArrayInputStream("hello".getBytes("UTF-8")));
     Response response = client.readResponse(connection);
 
+    assertThat(response.getUrl()).isEqualTo(HOST);
     assertThat(response.getStatus()).isEqualTo(201);
     assertThat(response.getReason()).isEqualTo("OK");
     assertThat(response.getHeaders()).hasSize(3) //
@@ -159,6 +161,7 @@ public class UrlConnectionClientTest {
     connection.addResponseHeader("kit", "kat");
     Response response = client.readResponse(connection);
 
+    assertThat(response.getUrl()).isEqualTo(HOST);
     assertThat(response.getStatus()).isEqualTo(200);
     assertThat(response.getReason()).isEqualTo("OK");
     assertThat(response.getHeaders()).hasSize(2) //