Skip to content

Commit 33d31d0

Browse files
authored
Improve handling of HEAD requests with a Content-Length specified. (square#3572)
Rather than getting the content length from a header on demand, this gets it at the time that the RealResponseBody is created. This means that HEAD requests can have a non-zero Content-Length header while their bodies have no content. Closes: square#3365
1 parent 6663f06 commit 33d31d0

File tree

6 files changed

+34
-25
lines changed

6 files changed

+34
-25
lines changed

okhttp-tests/src/test/java/okhttp3/CallTest.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
import static java.net.CookiePolicy.ACCEPT_ORIGINAL_SERVER;
8484
import static okhttp3.TestUtil.awaitGarbageCollection;
8585
import static okhttp3.TestUtil.defaultClient;
86+
import static org.junit.Assert.assertArrayEquals;
8687
import static org.junit.Assert.assertEquals;
8788
import static org.junit.Assert.assertFalse;
8889
import static org.junit.Assert.assertNotSame;
@@ -243,10 +244,9 @@ public final class CallTest {
243244
.url(server.url("/"))
244245
.head()
245246
.build();
246-
executeSynchronously(headRequest)
247-
.assertCode(200)
248-
.assertHeader("Content-Length", "100")
249-
.assertBody("");
247+
Response response = client.newCall(headRequest).execute();
248+
assertEquals(200, response.code());
249+
assertArrayEquals(new byte[0], response.body().bytes());
250250

251251
Request getRequest = new Request.Builder()
252252
.url(server.url("/"))

okhttp/src/main/java/okhttp3/internal/cache/CacheInterceptor.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,8 +207,10 @@ private Response cacheWritingResponse(final CacheRequest cacheRequest, Response
207207
}
208208
};
209209

210+
String contentType = response.header("Content-Type");
211+
long contentLength = response.body().contentLength();
210212
return response.newBuilder()
211-
.body(new RealResponseBody(response.headers(), Okio.buffer(cacheWritingSource)))
213+
.body(new RealResponseBody(contentType, contentLength, Okio.buffer(cacheWritingSource)))
212214
.build();
213215
}
214216

okhttp/src/main/java/okhttp3/internal/http/BridgeInterceptor.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,8 @@ public BridgeInterceptor(CookieJar cookieJar) {
106106
.removeAll("Content-Length")
107107
.build();
108108
responseBuilder.headers(strippedHeaders);
109-
responseBuilder.body(new RealResponseBody(strippedHeaders, Okio.buffer(responseBody)));
109+
String contentType = networkResponse.header("Content-Type");
110+
responseBuilder.body(new RealResponseBody(contentType, -1L, Okio.buffer(responseBody)));
110111
}
111112

112113
return responseBuilder.build();

okhttp/src/main/java/okhttp3/internal/http/RealResponseBody.java

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,27 +15,33 @@
1515
*/
1616
package okhttp3.internal.http;
1717

18-
import okhttp3.Headers;
18+
import javax.annotation.Nullable;
1919
import okhttp3.MediaType;
2020
import okhttp3.ResponseBody;
2121
import okio.BufferedSource;
2222

2323
public final class RealResponseBody extends ResponseBody {
24-
private final Headers headers;
24+
/**
25+
* Use a string to avoid parsing the content type until needed. This also defers problems caused
26+
* by malformed content types.
27+
*/
28+
private final @Nullable String contentTypeString;
29+
private final long contentLength;
2530
private final BufferedSource source;
2631

27-
public RealResponseBody(Headers headers, BufferedSource source) {
28-
this.headers = headers;
32+
public RealResponseBody(
33+
@Nullable String contentTypeString, long contentLength, BufferedSource source) {
34+
this.contentTypeString = contentTypeString;
35+
this.contentLength = contentLength;
2936
this.source = source;
3037
}
3138

3239
@Override public MediaType contentType() {
33-
String contentType = headers.get("Content-Type");
34-
return contentType != null ? MediaType.parse(contentType) : null;
40+
return contentTypeString != null ? MediaType.parse(contentTypeString) : null;
3541
}
3642

3743
@Override public long contentLength() {
38-
return HttpHeaders.contentLength(headers);
44+
return contentLength;
3945
}
4046

4147
@Override public BufferedSource source() {

okhttp/src/main/java/okhttp3/internal/http1/Http1Codec.java

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -130,28 +130,25 @@ public Http1Codec(OkHttpClient client, StreamAllocation streamAllocation, Buffer
130130

131131
@Override public ResponseBody openResponseBody(Response response) throws IOException {
132132
streamAllocation.eventListener.responseBodyStart(streamAllocation.call);
133-
Source source = getTransferStream(response);
134-
return new RealResponseBody(response.headers(), Okio.buffer(source));
135-
}
133+
String contentType = response.header("Content-Type");
136134

137-
private Source getTransferStream(Response response) throws IOException {
138135
if (!HttpHeaders.hasBody(response)) {
139-
return newFixedLengthSource(0);
136+
Source source = newFixedLengthSource(0);
137+
return new RealResponseBody(contentType, 0, Okio.buffer(source));
140138
}
141139

142140
if ("chunked".equalsIgnoreCase(response.header("Transfer-Encoding"))) {
143-
return newChunkedSource(response.request().url());
141+
Source source = newChunkedSource(response.request().url());
142+
return new RealResponseBody(contentType, -1L, Okio.buffer(source));
144143
}
145144

146145
long contentLength = HttpHeaders.contentLength(response);
147146
if (contentLength != -1) {
148-
return newFixedLengthSource(contentLength);
147+
Source source = newFixedLengthSource(contentLength);
148+
return new RealResponseBody(contentType, contentLength, Okio.buffer(source));
149149
}
150150

151-
// Wrap the input stream from the connection (rather than just returning
152-
// "socketIn" directly here), so that we can control its use after the
153-
// reference escapes.
154-
return newUnknownLengthSource();
151+
return new RealResponseBody(contentType, -1L, Okio.buffer(newUnknownLengthSource()));
155152
}
156153

157154
/** Returns true if this connection is closed. */

okhttp/src/main/java/okhttp3/internal/http2/Http2Codec.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import okhttp3.internal.Util;
3333
import okhttp3.internal.connection.StreamAllocation;
3434
import okhttp3.internal.http.HttpCodec;
35+
import okhttp3.internal.http.HttpHeaders;
3536
import okhttp3.internal.http.RealResponseBody;
3637
import okhttp3.internal.http.RequestLine;
3738
import okhttp3.internal.http.StatusLine;
@@ -186,8 +187,10 @@ public static Response.Builder readHttp2HeadersList(List<Header> headerBlock) th
186187

187188
@Override public ResponseBody openResponseBody(Response response) throws IOException {
188189
streamAllocation.eventListener.responseBodyStart(streamAllocation.call);
190+
String contentType = response.header("Content-Type");
191+
long contentLength = HttpHeaders.contentLength(response);
189192
Source source = new StreamFinishingSource(stream.getSource());
190-
return new RealResponseBody(response.headers(), Okio.buffer(source));
193+
return new RealResponseBody(contentType, contentLength, Okio.buffer(source));
191194
}
192195

193196
@Override public void cancel() {

0 commit comments

Comments
 (0)