1
0
mirror of https://github.com/square/okhttp.git synced 2026-01-24 04:02:07 +03:00

Cache SPDY responses even if the body isn't fully consumed.

https://github.com/square/okhttp/issues/450
This commit is contained in:
jwilson
2014-01-18 12:24:47 -05:00
parent 2a5b9562d2
commit f6f3497ef7
6 changed files with 106 additions and 24 deletions

View File

@@ -119,6 +119,14 @@ public final class Headers {
return result;
}
@Override public String toString() {
StringBuilder result = new StringBuilder();
for (int i = 0; i < size(); i++) {
result.append(name(i)).append(": ").append(value(i)).append("\n");
}
return result.toString();
}
private static String get(String[] namesAndValues, String fieldName) {
for (int i = namesAndValues.length - 2; i >= 0; i -= 2) {
if (fieldName.equalsIgnoreCase(namesAndValues[i])) {

View File

@@ -36,7 +36,7 @@ abstract class AbstractHttpInputStream extends InputStream {
protected final InputStream in;
protected final HttpEngine httpEngine;
private final CacheRequest cacheRequest;
private final OutputStream cacheBody;
protected final OutputStream cacheBody;
protected boolean closed;
AbstractHttpInputStream(InputStream in, HttpEngine httpEngine, CacheRequest cacheRequest)

View File

@@ -35,13 +35,6 @@ import static com.squareup.okhttp.internal.Util.checkOffsetAndCount;
import static com.squareup.okhttp.internal.http.StatusLine.HTTP_CONTINUE;
public final class HttpTransport implements Transport {
/**
* The timeout to use while discarding a stream of input data. Since this is
* used for connection reuse, this timeout should be significantly less than
* the time it takes to establish a new connection.
*/
private static final int DISCARD_STREAM_TIMEOUT_MILLIS = 100;
public static final int DEFAULT_CHUNK_LENGTH = 1024;
private final HttpEngine httpEngine;

View File

@@ -17,10 +17,11 @@
package com.squareup.okhttp.internal.http;
import com.squareup.okhttp.Headers;
import com.squareup.okhttp.Protocol;
import com.squareup.okhttp.Request;
import com.squareup.okhttp.Response;
import com.squareup.okhttp.internal.ByteString;
import com.squareup.okhttp.Protocol;
import com.squareup.okhttp.internal.Util;
import com.squareup.okhttp.internal.spdy.ErrorCode;
import com.squareup.okhttp.internal.spdy.SpdyConnection;
import com.squareup.okhttp.internal.spdy.SpdyStream;
@@ -35,6 +36,8 @@ import java.util.List;
import java.util.Locale;
import java.util.Set;
import static com.squareup.okhttp.internal.Util.checkOffsetAndCount;
public final class SpdyTransport implements Transport {
private static final ByteString HEADER_METHOD = ByteString.encodeUtf8(":method");
private static final ByteString HEADER_PATH = ByteString.encodeUtf8(":path");
@@ -191,23 +194,12 @@ public final class SpdyTransport implements Transport {
}
@Override public InputStream getTransferStream(CacheRequest cacheRequest) throws IOException {
return new UnknownLengthHttpInputStream(stream.getInputStream(), cacheRequest, httpEngine);
return new SpdyInputStream(stream, cacheRequest, httpEngine);
}
@Override public boolean makeReusable(boolean streamCanceled, OutputStream requestBodyOut,
InputStream responseBodyIn) {
if (streamCanceled) {
if (stream != null) {
stream.closeLater(ErrorCode.CANCEL);
return true;
} else {
// If stream is null, it either means that writeRequestHeaders wasn't called
// or that SpdyConnection#newStream threw an IOException. In both cases there's
// nothing to do here and this stream can't be reused.
return false;
}
}
return true;
return true; // SPDY sockets are always reusable.
}
/** When true, this header should not be emitted or consumed. */
@@ -239,4 +231,68 @@ public final class SpdyTransport implements Transport {
}
return prohibited;
}
/** An HTTP message body terminated by the end of the underlying stream. */
private static class SpdyInputStream extends AbstractHttpInputStream {
private final SpdyStream stream;
private boolean inputExhausted;
SpdyInputStream(SpdyStream stream, CacheRequest cacheRequest, HttpEngine httpEngine)
throws IOException {
super(stream.getInputStream(), httpEngine, cacheRequest);
this.stream = stream;
}
@Override public int read(byte[] buffer, int offset, int count) throws IOException {
checkOffsetAndCount(buffer.length, offset, count);
checkNotClosed();
if (in == null || inputExhausted) {
return -1;
}
int read = in.read(buffer, offset, count);
if (read == -1) {
inputExhausted = true;
endOfInput();
return -1;
}
cacheWrite(buffer, offset, read);
return read;
}
@Override public int available() throws IOException {
checkNotClosed();
return in == null ? 0 : in.available();
}
@Override public void close() throws IOException {
if (closed) return;
if (!inputExhausted && cacheBody != null) {
discardStream(); // Could make inputExhausted true!
}
closed = true;
if (!inputExhausted) {
stream.closeLater(ErrorCode.CANCEL);
unexpectedEndOfInput();
}
}
private boolean discardStream() {
try {
long socketTimeout = stream.getReadTimeoutMillis();
stream.setReadTimeout(socketTimeout);
stream.setReadTimeout(DISCARD_STREAM_TIMEOUT_MILLIS);
try {
Util.skipByReading(this, Long.MAX_VALUE, DISCARD_STREAM_TIMEOUT_MILLIS);
return true;
} finally {
stream.setReadTimeout(socketTimeout);
}
} catch (IOException e) {
return false;
}
}
}
}

View File

@@ -24,6 +24,13 @@ import java.io.OutputStream;
import java.net.CacheRequest;
interface Transport {
/**
* The timeout to use while discarding a stream of input data. Since this is
* used for connection reuse, this timeout should be significantly less than
* the time it takes to establish a new connection.
*/
int DISCARD_STREAM_TIMEOUT_MILLIS = 100;
/**
* Returns an output stream where the request body can be written. The
* returned stream will of one of two types:

View File

@@ -282,7 +282,7 @@ public abstract class HttpOverSpdyTest {
}
@Test public void responsesAreCached() throws IOException {
client.setResponseCache(cache);
client.setOkResponseCache(cache);
server.enqueue(new MockResponse().addHeader("cache-control: max-age=60").setBody("A"));
server.play();
@@ -299,7 +299,7 @@ public abstract class HttpOverSpdyTest {
}
@Test public void conditionalCache() throws IOException {
client.setResponseCache(cache);
client.setOkResponseCache(cache);
server.enqueue(new MockResponse().addHeader("ETag: v1").setBody("A"));
server.enqueue(new MockResponse().setResponseCode(HttpURLConnection.HTTP_NOT_MODIFIED));
@@ -315,6 +315,24 @@ public abstract class HttpOverSpdyTest {
assertEquals(1, cache.getHitCount());
}
@Test public void responseCachedWithoutConsumingFullBody() throws IOException {
client.setOkResponseCache(cache);
server.enqueue(new MockResponse().addHeader("cache-control: max-age=60").setBody("ABCD"));
server.enqueue(new MockResponse().addHeader("cache-control: max-age=60").setBody("EFGH"));
server.play();
URLConnection connection1 = client.open(server.getUrl("/"));
InputStream in1 = connection1.getInputStream();
assertEquals("AB", readAscii(in1, 2));
in1.close();
URLConnection connection2 = client.open(server.getUrl("/"));
InputStream in2 = connection2.getInputStream();
assertEquals("ABCD", readAscii(in2, Integer.MAX_VALUE));
in2.close();
}
@Test public void acceptAndTransmitCookies() throws Exception {
CookieManager cookieManager = new CookieManager();
client.setCookieHandler(cookieManager);