From 241814257b774f39896d7384c3452ef22612307f Mon Sep 17 00:00:00 2001 From: Adrian Cole Date: Fri, 24 Jan 2014 10:47:03 +0100 Subject: [PATCH] Backfill spdy and http/2 related tests wrt zero-length messages. --- .../okhttp/internal/spdy/MockSpdyPeer.java | 4 + .../internal/spdy/SpdyConnectionTest.java | 84 +++++++++++++++++-- .../internal/http/URLConnectionTest.java | 78 +++++++++++++++++ 3 files changed, 161 insertions(+), 5 deletions(-) diff --git a/okhttp-protocols/src/test/java/com/squareup/okhttp/internal/spdy/MockSpdyPeer.java b/okhttp-protocols/src/test/java/com/squareup/okhttp/internal/spdy/MockSpdyPeer.java index e5335fa48..3675bb086 100644 --- a/okhttp-protocols/src/test/java/com/squareup/okhttp/internal/spdy/MockSpdyPeer.java +++ b/okhttp-protocols/src/test/java/com/squareup/okhttp/internal/spdy/MockSpdyPeer.java @@ -57,6 +57,10 @@ public final class MockSpdyPeer implements Closeable { frameCount++; } + public int frameCount() { + return frameCount; + } + public FrameWriter sendFrame() { outFrames.add(new OutFrame(frameCount++, bytesOut.size(), Integer.MAX_VALUE)); return frameWriter; diff --git a/okhttp-protocols/src/test/java/com/squareup/okhttp/internal/spdy/SpdyConnectionTest.java b/okhttp-protocols/src/test/java/com/squareup/okhttp/internal/spdy/SpdyConnectionTest.java index fbf656048..ceb68ddcc 100644 --- a/okhttp-protocols/src/test/java/com/squareup/okhttp/internal/spdy/SpdyConnectionTest.java +++ b/okhttp-protocols/src/test/java/com/squareup/okhttp/internal/spdy/SpdyConnectionTest.java @@ -162,6 +162,17 @@ public final class SpdyConnectionTest { } @Test public void replyWithNoData() throws Exception { + MockSpdyPeer.InFrame reply = replyWithNoData(SPDY3); + assertEquals(HeadersMode.SPDY_REPLY, reply.headersMode); + } + + @Test public void replyWithNoDataHttp2() throws Exception { + MockSpdyPeer.InFrame reply = replyWithNoData(HTTP_20_DRAFT_09); + assertEquals(HeadersMode.HTTP_20_HEADERS, reply.headersMode); + } + + private MockSpdyPeer.InFrame replyWithNoData(Variant variant) throws Exception { + MockSpdyPeer peer = new MockSpdyPeer(variant, false); // write the mocking script peer.sendFrame().synStream(false, false, 2, 0, 0, 0, headerEntries("a", "android")); peer.acceptFrame(); // SYN_REPLY @@ -175,15 +186,15 @@ public final class SpdyConnectionTest { receiveCount.incrementAndGet(); } }; - new SpdyConnection.Builder(true, peer.openSocket()).handler(handler).build(); + connectionBuilder(peer, variant).handler(handler).build(); // verify the peer received what was expected MockSpdyPeer.InFrame reply = peer.takeFrame(); assertEquals(TYPE_HEADERS, reply.type); - assertEquals(HeadersMode.SPDY_REPLY, reply.headersMode); assertTrue(reply.inFinished); assertEquals(headerEntries("b", "banana"), reply.headerBlock); assertEquals(1, receiveCount.get()); + return reply; } @Test public void noop() throws Exception { @@ -1056,6 +1067,66 @@ public final class SpdyConnectionTest { } } + @Test public void serverSendsEmptyDataClientDoesntSendWindowUpdate() throws Exception { + serverSendsEmptyDataClientDoesntSendWindowUpdate(SPDY3); + } + + @Test public void serverSendsEmptyDataClientDoesntSendWindowUpdateHttp2() throws Exception { + serverSendsEmptyDataClientDoesntSendWindowUpdate(HTTP_20_DRAFT_09); + } + + private void serverSendsEmptyDataClientDoesntSendWindowUpdate(Variant variant) + throws IOException, InterruptedException { + MockSpdyPeer peer = new MockSpdyPeer(variant, false); + + // Write the mocking script. + peer.acceptFrame(); // SYN_STREAM + peer.sendFrame().synReply(false, 1, headerEntries("a", "android")); + peer.sendFrame().data(true, 1, new byte[0]); + peer.play(); + + // Play it back. + SpdyConnection connection = connection(peer, variant); + SpdyStream client = connection.newStream(headerEntries("b", "banana"), false, true); + assertEquals(-1, client.getInputStream().read()); + + // Verify the peer received what was expected. + MockSpdyPeer.InFrame synStream = peer.takeFrame(); + assertEquals(TYPE_HEADERS, synStream.type); + assertEquals(3, peer.frameCount()); + } + + @Test public void clientSendsEmptyDataServerDoesntSendWindowUpdate() throws Exception { + clientSendsEmptyDataServerDoesntSendWindowUpdate(SPDY3); + } + + @Test public void clientSendsEmptyDataServerDoesntSendWindowUpdateHttp2() throws Exception { + clientSendsEmptyDataServerDoesntSendWindowUpdate(HTTP_20_DRAFT_09); + } + + private void clientSendsEmptyDataServerDoesntSendWindowUpdate(Variant variant) + throws IOException, InterruptedException { + MockSpdyPeer peer = new MockSpdyPeer(variant, false); + + // Write the mocking script. + peer.acceptFrame(); // SYN_STREAM + peer.sendFrame().synReply(false, 1, headerEntries("a", "android")); + peer.play(); + + // Play it back. + SpdyConnection connection = connection(peer, variant); + SpdyStream client = connection.newStream(headerEntries("b", "banana"), true, true); + assertEquals(0, client.getInputStream().available()); + client.getOutputStream().write(Util.EMPTY_BYTE_ARRAY); + client.getOutputStream().flush(); + client.getOutputStream().close(); + + // Verify the peer received what was expected. + MockSpdyPeer.InFrame synStream = peer.takeFrame(); + assertEquals(TYPE_HEADERS, synStream.type); + assertEquals(2, peer.frameCount()); + } + @Test public void writeAwaitsWindowUpdate() throws Exception { int windowSize = 65535; @@ -1215,9 +1286,12 @@ public final class SpdyConnectionTest { } private SpdyConnection connection(MockSpdyPeer peer, Variant variant) throws IOException { - return new SpdyConnection.Builder(true, peer.openSocket()) - .protocol(variant.getProtocol()) - .handler(REJECT_INCOMING_STREAMS).build(); + return connectionBuilder(peer, variant).handler(REJECT_INCOMING_STREAMS).build(); + } + + private SpdyConnection.Builder connectionBuilder(MockSpdyPeer peer, Variant variant) + throws IOException { + return new SpdyConnection.Builder(true, peer.openSocket()).protocol(variant.getProtocol()); } private void writeAndClose(SpdyStream stream, String data) throws IOException { diff --git a/okhttp/src/test/java/com/squareup/okhttp/internal/http/URLConnectionTest.java b/okhttp/src/test/java/com/squareup/okhttp/internal/http/URLConnectionTest.java index 7b9eb8d09..117970210 100644 --- a/okhttp/src/test/java/com/squareup/okhttp/internal/http/URLConnectionTest.java +++ b/okhttp/src/test/java/com/squareup/okhttp/internal/http/URLConnectionTest.java @@ -76,6 +76,7 @@ import org.junit.Before; import org.junit.Ignore; import org.junit.Test; +import static com.squareup.okhttp.internal.http.OkHeaders.SELECTED_PROTOCOL; import static com.squareup.okhttp.internal.http.StatusLine.HTTP_TEMP_REDIRECT; import static com.squareup.okhttp.mockwebserver.SocketPolicy.DISCONNECT_AT_END; import static com.squareup.okhttp.mockwebserver.SocketPolicy.DISCONNECT_AT_START; @@ -2527,6 +2528,71 @@ public final class URLConnectionTest { assertTrue(call, call.contains("challenges=[Basic realm=\"protected area\"]")); } + @Test public void npnSetsProtocolHeader_SPDY_3() throws Exception { + npnSetsProtocolHeader(Protocol.SPDY_3); + } + + @Test public void npnSetsProtocolHeader_HTTP_2() throws Exception { + npnSetsProtocolHeader(Protocol.HTTP_2); + } + + private void npnSetsProtocolHeader(Protocol protocol) throws IOException { + enableNpn(protocol); + server.enqueue(new MockResponse().setBody("A")); + server.play(); + client.setProtocols(Arrays.asList(Protocol.HTTP_11, protocol)); + HttpURLConnection connection = client.open(server.getUrl("/")); + List protocolValues = connection.getHeaderFields().get(SELECTED_PROTOCOL); + assertEquals(Arrays.asList(protocol.name.utf8()), protocolValues); + assertContent("A", connection); + } + + /** For example, empty Protobuf RPC messages end up as a zero-length POST. */ + @Test public void zeroLengthPost() throws IOException, InterruptedException { + zeroLengthPayload("POST"); + } + + @Test public void zeroLengthPost_SPDY_3() throws Exception { + enableNpn(Protocol.SPDY_3); + zeroLengthPost(); + } + + @Test public void zeroLengthPost_HTTP_2() throws Exception { + enableNpn(Protocol.HTTP_2); + zeroLengthPost(); + } + + /** For example, creating an Amazon S3 bucket ends up as a zero-length POST. */ + @Test public void zeroLengthPut() throws IOException, InterruptedException { + zeroLengthPayload("PUT"); + } + + @Test public void zeroLengthPut_SPDY_3() throws Exception { + enableNpn(Protocol.SPDY_3); + zeroLengthPut(); + } + + @Test public void zeroLengthPut_HTTP_2() throws Exception { + enableNpn(Protocol.HTTP_2); + zeroLengthPut(); + } + + private void zeroLengthPayload(String method) + throws IOException, InterruptedException { + server.enqueue(new MockResponse()); + server.play(); + HttpURLConnection connection = client.open(server.getUrl("/")); + connection.setRequestProperty("Content-Length", "0"); + connection.setRequestMethod(method); + connection.setFixedLengthStreamingMode(0); + connection.setDoOutput(true); + assertContent("", connection); + RecordedRequest zeroLengthPayload = server.takeRequest(); + assertEquals(method, zeroLengthPayload.getMethod()); + assertEquals("0", zeroLengthPayload.getHeader("content-length")); + assertEquals(0L, zeroLengthPayload.getBodySize()); + } + @Test public void setProtocols() throws Exception { server.enqueue(new MockResponse().setBody("A")); server.play(); @@ -2772,4 +2838,16 @@ public final class URLConnectionTest { @Override public void connectFailed(URI uri, SocketAddress sa, IOException ioe) { } } + + /** + * Tests that use this will fail unless boot classpath is set. Ex. {@code + * -Xbootclasspath/p:/tmp/npn-boot-8.1.2.v20120308.jar} + */ + private void enableNpn(Protocol protocol) { + server.useHttps(sslContext.getSocketFactory(), false); + server.setNpnEnabled(true); + client.setSslSocketFactory(sslContext.getSocketFactory()); + client.setHostnameVerifier(new RecordingHostnameVerifier()); + client.setProtocols(Arrays.asList(protocol, Protocol.HTTP_11)); + } }