Repository: square/retrofit
Issue: HTTP 204, 205 do not have response bodies.

Per the spec, emphasis mine:

> #### 10.2.5 204 No Content
> 
> The server has fulfilled the request but does not need to return an entity-body, and might want to return updated metainformation. The response MAY include new or updated metainformation in the form of entity-headers, which if present SHOULD be associated with the requested variant.
> 
> If the client is a user agent, it SHOULD NOT change its document view from that which caused the request to be sent. This response is primarily intended to allow input for actions to take place without causing a change to the user agent's active document view, although any new or updated metainformation SHOULD be applied to the document currently in the user agent's active view.
> 
> The 204 response **MUST NOT include a message-body**, and thus is always terminated by the first empty line after the header fields.
> #### 10.2.6 205 Reset Content
> 
> The server has fulfilled the request and the user agent SHOULD reset the document view which caused the request to be sent. This response is primarily intended to allow input for actions to take place via user input, followed by a clearing of the form in which the input is given so that the user can easily initiate another input action. The response **MUST NOT include an entity**.

We should not even call the `Converter` for these response codes.


Issue comments:
https://github.com/square/retrofit/pull/815


Pull request review comments:
This is never true of an OkHttp response.

Not a protocol exception? Somewhat harsh to crash the client when the server made the mistake.

(not sure what's default in Retrofit)

Ahhh . . . it gets wrapped. Cool.


Patch that resolved the issue:
diff --git a/retrofit/src/main/java/retrofit/RestAdapter.java b/retrofit/src/main/java/retrofit/RestAdapter.java
index 2da591820f..422806cf32 100644
--- a/retrofit/src/main/java/retrofit/RestAdapter.java
+++ b/retrofit/src/main/java/retrofit/RestAdapter.java
@@ -291,7 +291,12 @@ private Object parseResult(MethodInfo methodInfo, Response response)
       }
 
       ResponseBody body = response.body();
-      if (body == null) {
+      if (statusCode == 204 || statusCode == 205) {
+        // HTTP 204 No Content "...response MUST NOT include a message-body"
+        // HTTP 205 Reset Content "...response MUST NOT include an entity"
+        if (body.contentLength() > 0) {
+          throw new IllegalStateException(statusCode + " response must not include body.");
+        }
         return null;
       }
 


Test patch:
diff --git a/retrofit/src/test/java/retrofit/RestAdapterTest.java b/retrofit/src/test/java/retrofit/RestAdapterTest.java
index 1776c3f12e..171f5c77a2 100644
--- a/retrofit/src/test/java/retrofit/RestAdapterTest.java
+++ b/retrofit/src/test/java/retrofit/RestAdapterTest.java
@@ -12,6 +12,8 @@
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import retrofit.converter.Converter;
+import retrofit.converter.GsonConverter;
 import retrofit.http.Body;
 import retrofit.http.GET;
 import retrofit.http.Headers;
@@ -25,6 +27,8 @@
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static retrofit.Utils.SynchronousExecutor;
 
 public class RestAdapterTest {
@@ -44,14 +48,18 @@ private interface InvalidExample extends Example {
   @Rule public final MockWebServerRule server = new MockWebServerRule();
 
   private Example example;
+  private Converter converter;
 
   @Before public void setUp() {
     OkHttpClient client = new OkHttpClient();
 
+    converter = spy(new GsonConverter());
+
     example = new RestAdapter.Builder() //
         .setClient(client)
         .setCallbackExecutor(new SynchronousExecutor())
         .setEndpoint(server.getUrl("/").toString())
+        .setConverter(converter)
         .build()
         .create(Example.class);
   }
@@ -71,6 +79,56 @@ private interface InvalidExample extends Example {
     }
   }
 
+  @Test public void http204SkipsConverter() {
+    server.enqueue(new MockResponse().setStatus("HTTP/1.1 204 Nothin"));
+    assertThat(example.something()).isNull();
+    verifyNoMoreInteractions(converter);
+  }
+
+  @Test public void http204Response() {
+    server.enqueue(new MockResponse().setStatus("HTTP/1.1 204 Nothin"));
+    Response response = example.direct();
+    assertThat(response.code()).isEqualTo(204);
+  }
+
+  @Test public void http204WithBodyThrows() {
+    server.enqueue(new MockResponse().setStatus("HTTP/1.1 204 Nothin").setBody("Hey"));
+    try {
+      example.something();
+      fail();
+    } catch (RetrofitError e) {
+      assertThat(e).hasMessage("204 response must not include body.");
+      Throwable cause = e.getCause();
+      assertThat(cause).isInstanceOf(IllegalStateException.class);
+      assertThat(cause).hasMessage("204 response must not include body.");
+    }
+  }
+
+  @Test public void http205SkipsConverter() {
+    server.enqueue(new MockResponse().setStatus("HTTP/1.1 205 Nothin"));
+    assertThat(example.something()).isNull();
+    verifyNoMoreInteractions(converter);
+  }
+
+  @Test public void http205Response() {
+    server.enqueue(new MockResponse().setStatus("HTTP/1.1 205 Nothin"));
+    Response response = example.direct();
+    assertThat(response.code()).isEqualTo(205);
+  }
+
+  @Test public void http205WithBodyThrows() {
+    server.enqueue(new MockResponse().setStatus("HTTP/1.1 205 Nothin").setBody("Hey"));
+    try {
+      example.something();
+      fail();
+    } catch (RetrofitError e) {
+      assertThat(e).hasMessage("205 response must not include body.");
+      Throwable cause = e.getCause();
+      assertThat(cause).isInstanceOf(IllegalStateException.class);
+      assertThat(cause).hasMessage("205 response must not include body.");
+    }
+  }
+
   @Test public void successfulRequestResponseWhenMimeTypeMissing() throws Exception {
     server.enqueue(new MockResponse().setBody("Hi").removeHeader("Content-Type"));
     String string = example.something();