diff --git a/src/main/java/libcore/net/http/HttpConnection.java b/src/main/java/libcore/net/http/HttpConnection.java index 4e7800ae5..89a6ec961 100644 --- a/src/main/java/libcore/net/http/HttpConnection.java +++ b/src/main/java/libcore/net/http/HttpConnection.java @@ -214,9 +214,14 @@ final class HttpConnection { throw new IOException("Hostname '" + address.uriHost + "' was not verified"); } - // SSL success. Prepare to hand out Transport instances. + /* + * Buffer the input to mask SSL InputStream's degenerate available() + * implementation. That way we can read the end of a chunked response + * without blocking and will recycle the connection more reliably. + * http://code.google.com/p/android/issues/detail?id=38817 + */ sslOutputStream = sslSocket.getOutputStream(); - sslInputStream = sslSocket.getInputStream(); + sslInputStream = new BufferedInputStream(sslSocket.getInputStream(), 128); byte[] selectedProtocol; if (tlsTolerant diff --git a/src/main/java/libcore/net/http/HttpEngine.java b/src/main/java/libcore/net/http/HttpEngine.java index 10acb3e93..de25581cc 100644 --- a/src/main/java/libcore/net/http/HttpEngine.java +++ b/src/main/java/libcore/net/http/HttpEngine.java @@ -101,6 +101,7 @@ public class HttpEngine { private Transport transport; + private InputStream responseTransferIn; private InputStream responseBodyIn; private final ResponseCache responseCache = ResponseCache.getDefault(); @@ -421,7 +422,7 @@ public class HttpEngine { if (!connectionReleased && connection != null) { connectionReleased = true; - if (!reusable || !transport.makeReusable(requestBodyOut, responseBodyIn)) { + if (!reusable || !transport.makeReusable(requestBodyOut, responseTransferIn)) { connection.closeSocketAndStreams(); connection = null; } else if (automaticallyReleaseConnectionToPool) { @@ -432,6 +433,7 @@ public class HttpEngine { } private void initContentStream(InputStream transferStream) throws IOException { + responseTransferIn = transferStream; if (transparentGzip && responseHeaders.isContentEncodingGzip()) { /* * If the response was transparently gzipped, remove the gzip header field diff --git a/src/main/java/libcore/net/http/HttpTransport.java b/src/main/java/libcore/net/http/HttpTransport.java index 35b96d334..3c5591837 100644 --- a/src/main/java/libcore/net/http/HttpTransport.java +++ b/src/main/java/libcore/net/http/HttpTransport.java @@ -501,12 +501,12 @@ final class HttpTransport implements Transport { cacheWrite(buffer, offset, read); /* - * If we're at the end of a chunk and the next chunk size is readable, - * read it! Reading the last chunk causes the underlying connection to - * be recycled and we want to do that as early as possible. Otherwise - * self-delimiting streams like gzip will never be recycled. - * http://code.google.com/p/android/issues/detail?id=7059 - */ + * If we're at the end of a chunk and the next chunk size is readable, + * read it! Reading the last chunk causes the underlying connection to + * be recycled and we want to do that as early as possible. Otherwise + * self-delimiting streams like gzip will never be recycled. + * http://code.google.com/p/android/issues/detail?id=7059 + */ if (bytesRemainingInChunk == 0 && in.available() >= MIN_LAST_CHUNK_LENGTH) { readChunkSize(); } diff --git a/src/test/java/libcore/net/http/URLConnectionTest.java b/src/test/java/libcore/net/http/URLConnectionTest.java index 5462252e3..6ea043747 100644 --- a/src/test/java/libcore/net/http/URLConnectionTest.java +++ b/src/test/java/libcore/net/http/URLConnectionTest.java @@ -1024,11 +1024,19 @@ public final class URLConnectionTest extends TestCase { } public void testGzipAndConnectionReuseWithFixedLength() throws Exception { - testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.FIXED_LENGTH); + testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.FIXED_LENGTH, false); } public void testGzipAndConnectionReuseWithChunkedEncoding() throws Exception { - testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.CHUNKED); + testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.CHUNKED, false); + } + + public void testGzipAndConnectionReuseWithFixedLengthAndTls() throws Exception { + testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.FIXED_LENGTH, true); + } + + public void testGzipAndConnectionReuseWithChunkedEncodingAndTls() throws Exception { + testClientConfiguredGzipContentEncodingAndConnectionReuse(TransferKind.CHUNKED, true); } public void testClientConfiguredCustomContentEncoding() throws Exception { @@ -1047,11 +1055,20 @@ public final class URLConnectionTest extends TestCase { /** * Test a bug where gzip input streams weren't exhausting the input stream, - * which corrupted the request that followed. + * which corrupted the request that followed or prevented connection reuse. * http://code.google.com/p/android/issues/detail?id=7059 + * http://code.google.com/p/android/issues/detail?id=38817 */ private void testClientConfiguredGzipContentEncodingAndConnectionReuse( - TransferKind transferKind) throws Exception { + TransferKind transferKind, boolean tls) throws Exception { + SSLSocketFactory socketFactory = null; + RecordingHostnameVerifier hostnameVerifier = null; + if (tls) { + socketFactory = sslContext.getSocketFactory(); + hostnameVerifier = new RecordingHostnameVerifier(); + server.useHttps(socketFactory, false); + } + MockResponse responseOne = new MockResponse(); responseOne.addHeader("Content-Encoding: gzip"); transferKind.setBody(responseOne, gzip("one (gzipped)".getBytes("UTF-8")), 5); @@ -1062,12 +1079,20 @@ public final class URLConnectionTest extends TestCase { server.play(); URLConnection connection = openConnection(server.getUrl("/")); + if (tls) { + ((OkHttpsConnection) connection).setSSLSocketFactory(socketFactory); + ((OkHttpsConnection) connection).setHostnameVerifier(hostnameVerifier); + } connection.addRequestProperty("Accept-Encoding", "gzip"); InputStream gunzippedIn = new GZIPInputStream(connection.getInputStream()); assertEquals("one (gzipped)", readAscii(gunzippedIn, Integer.MAX_VALUE)); assertEquals(0, server.takeRequest().getSequenceNumber()); connection = openConnection(server.getUrl("/")); + if (tls) { + ((OkHttpsConnection) connection).setSSLSocketFactory(socketFactory); + ((OkHttpsConnection) connection).setHostnameVerifier(hostnameVerifier); + } assertEquals("two (identity)", readAscii(connection.getInputStream(), Integer.MAX_VALUE)); assertEquals(1, server.takeRequest().getSequenceNumber()); }