/** * When writing a set of headers fails due to an {@code IOException}, make sure the writer is left * in a consistent state so the next writer also gets an {@code IOException} also instead of * something worse (like an {@link IllegalStateException}. * * <p>See https://github.com/square/okhttp/issues/1651 */ @Test public void socketExceptionWhileWritingHeaders() throws Exception { peer.setVariantAndClient(HTTP_2, false); peer.acceptFrame(); // SYN_STREAM. peer.play(); String longString = repeat('a', Http2.INITIAL_MAX_FRAME_SIZE + 1); Socket socket = peer.openSocket(); FramedConnection connection = new FramedConnection.Builder(true) .socket(socket) .pushObserver(IGNORE) .protocol(HTTP_2.getProtocol()) .build(); socket.shutdownOutput(); try { connection.newStream(headerEntries("a", longString), false, true); fail(); } catch (IOException expected) { } try { connection.newStream(headerEntries("b", longString), false, true); fail(); } catch (IOException expected) { } }
@Test public void maxFrameSizeHonored() throws Exception { peer.setVariantAndClient(HTTP_2, false); byte[] buff = new byte[peer.maxOutboundDataLength() + 1]; Arrays.fill(buff, (byte) '*'); // write the mocking script peer.acceptFrame(); // SYN_STREAM peer.sendFrame().synReply(false, 3, headerEntries("a", "android")); peer.acceptFrame(); // DATA peer.acceptFrame(); // DATA peer.play(); // play it back FramedConnection connection = connection(peer, HTTP_2); FramedStream stream = connection.newStream(headerEntries("b", "banana"), true, true); BufferedSink out = Okio.buffer(stream.getSink()); out.write(buff); out.flush(); out.close(); MockSpdyPeer.InFrame synStream = peer.takeFrame(); assertEquals(TYPE_HEADERS, synStream.type); MockSpdyPeer.InFrame data = peer.takeFrame(); assertEquals(peer.maxOutboundDataLength(), data.data.length); data = peer.takeFrame(); assertEquals(1, data.data.length); }
private FramedConnection.Builder connectionBuilder(MockSpdyPeer peer, Variant variant) throws IOException { return new FramedConnection.Builder(true) .socket(peer.openSocket()) .pushObserver(IGNORE) .protocol(variant.getProtocol()); }
@Test public void readSendsWindowUpdateHttp2() throws Exception { peer.setVariantAndClient(HTTP_2, false); int windowSize = 100; int windowUpdateThreshold = 50; // Write the mocking script. peer.acceptFrame(); // SYN_STREAM peer.sendFrame().synReply(false, 3, headerEntries("a", "android")); for (int i = 0; i < 3; i++) { // Send frames of summing to size 50, which is windowUpdateThreshold. peer.sendFrame().data(false, 3, data(24), 24); peer.sendFrame().data(false, 3, data(25), 25); peer.sendFrame().data(false, 3, data(1), 1); peer.acceptFrame(); // connection WINDOW UPDATE peer.acceptFrame(); // stream WINDOW UPDATE } peer.sendFrame().data(true, 3, data(0), 0); peer.play(); // Play it back. FramedConnection connection = connection(peer, HTTP_2); connection.okHttpSettings.set(INITIAL_WINDOW_SIZE, 0, windowSize); FramedStream stream = connection.newStream(headerEntries("b", "banana"), false, true); assertEquals(0, stream.unacknowledgedBytesRead); assertEquals(headerEntries("a", "android"), stream.getResponseHeaders()); Source in = stream.getSource(); Buffer buffer = new Buffer(); buffer.writeAll(in); assertEquals(-1, in.read(buffer, 1)); assertEquals(150, buffer.size()); MockSpdyPeer.InFrame synStream = peer.takeFrame(); assertEquals(TYPE_HEADERS, synStream.type); for (int i = 0; i < 3; i++) { List<Integer> windowUpdateStreamIds = new ArrayList<>(2); for (int j = 0; j < 2; j++) { MockSpdyPeer.InFrame windowUpdate = peer.takeFrame(); assertEquals(TYPE_WINDOW_UPDATE, windowUpdate.type); windowUpdateStreamIds.add(windowUpdate.streamId); assertEquals(windowUpdateThreshold, windowUpdate.windowSizeIncrement); } assertTrue(windowUpdateStreamIds.contains(0)); // connection assertTrue(windowUpdateStreamIds.contains(3)); // stream } }
@Test public void pushPromiseStream() throws Exception { peer.setVariantAndClient(HTTP_2, false); // write the mocking script peer.acceptFrame(); // SYN_STREAM peer.sendFrame().synReply(false, 3, headerEntries("a", "android")); final List<Header> expectedRequestHeaders = Arrays.asList( new Header(Header.TARGET_METHOD, "GET"), new Header(Header.TARGET_SCHEME, "https"), new Header(Header.TARGET_AUTHORITY, "squareup.com"), new Header(Header.TARGET_PATH, "/cached")); peer.sendFrame().pushPromise(3, 2, expectedRequestHeaders); final List<Header> expectedResponseHeaders = Arrays.asList(new Header(Header.RESPONSE_STATUS, "200")); peer.sendFrame().synReply(true, 2, expectedResponseHeaders); peer.sendFrame().data(true, 3, data(0), 0); peer.play(); RecordingPushObserver observer = new RecordingPushObserver(); // play it back FramedConnection connection = connectionBuilder(peer, HTTP_2).pushObserver(observer).build(); FramedStream client = connection.newStream(headerEntries("b", "banana"), false, true); assertEquals(-1, client.getSource().read(new Buffer(), 1)); // verify the peer received what was expected assertEquals(TYPE_HEADERS, peer.takeFrame().type); assertEquals(expectedRequestHeaders, observer.takeEvent()); assertEquals(expectedResponseHeaders, observer.takeEvent()); }
@Test public void serverPingsClientHttp2() throws Exception { peer.setVariantAndClient(HTTP_2, false); // write the mocking script peer.sendFrame().ping(false, 2, 3); peer.acceptFrame(); // PING peer.play(); // play it back connection(peer, HTTP_2); // verify the peer received what was expected MockSpdyPeer.InFrame ping = peer.takeFrame(); assertEquals(TYPE_PING, ping.type); assertEquals(0, ping.streamId); assertEquals(2, ping.payload1); assertEquals(3, ping.payload2); assertTrue(ping.ack); }
@Test public void pushPromiseStreamsAutomaticallyCancel() throws Exception { peer.setVariantAndClient(HTTP_2, false); // write the mocking script peer.sendFrame() .pushPromise( 3, 2, Arrays.asList( new Header(Header.TARGET_METHOD, "GET"), new Header(Header.TARGET_SCHEME, "https"), new Header(Header.TARGET_AUTHORITY, "squareup.com"), new Header(Header.TARGET_PATH, "/cached"))); peer.sendFrame().synReply(true, 2, Arrays.asList(new Header(Header.RESPONSE_STATUS, "200"))); peer.acceptFrame(); // RST_STREAM peer.play(); // play it back connectionBuilder(peer, HTTP_2).pushObserver(PushObserver.CANCEL).build(); // verify the peer received what was expected MockSpdyPeer.InFrame rstStream = peer.takeFrame(); assertEquals(TYPE_RST_STREAM, rstStream.type); assertEquals(2, rstStream.streamId); assertEquals(CANCEL, rstStream.errorCode); }
@Test public void clientPingsServerHttp2() throws Exception { peer.setVariantAndClient(HTTP_2, false); // write the mocking script peer.acceptFrame(); // PING peer.sendFrame().ping(true, 1, 5); peer.play(); // play it back FramedConnection connection = connection(peer, HTTP_2); Ping ping = connection.ping(); assertTrue(ping.roundTripTime() > 0); assertTrue(ping.roundTripTime() < TimeUnit.SECONDS.toNanos(1)); // verify the peer received what was expected MockSpdyPeer.InFrame pingFrame = peer.takeFrame(); assertEquals(0, pingFrame.streamId); assertEquals(1, pingFrame.payload1); assertEquals(0x4f4b6f6b, pingFrame.payload2); // connection.ping() sets this. assertFalse(pingFrame.ack); }
@Test public void doublePushPromise() throws Exception { peer.setVariantAndClient(HTTP_2, false); // write the mocking script peer.sendFrame().pushPromise(3, 2, headerEntries("a", "android")); peer.acceptFrame(); // SYN_REPLY peer.sendFrame().pushPromise(3, 2, headerEntries("b", "banana")); peer.acceptFrame(); // RST_STREAM peer.play(); // play it back FramedConnection connection = connectionBuilder(peer, HTTP_2).build(); connection.newStream(headerEntries("b", "banana"), false, true); // verify the peer received what was expected assertEquals(TYPE_HEADERS, peer.takeFrame().type); assertEquals(PROTOCOL_ERROR, peer.takeFrame().errorCode); }
@Test public void peerHttp2ServerLowersInitialWindowSize() throws Exception { peer.setVariantAndClient(HTTP_2, false); Settings initial = new Settings(); initial.set(INITIAL_WINDOW_SIZE, PERSIST_VALUE, 1684); Settings shouldntImpactConnection = new Settings(); shouldntImpactConnection.set(INITIAL_WINDOW_SIZE, PERSIST_VALUE, 3368); peer.sendFrame().settings(initial); peer.acceptFrame(); // ACK peer.sendFrame().settings(shouldntImpactConnection); peer.acceptFrame(); // ACK 2 peer.acceptFrame(); // HEADERS peer.play(); FramedConnection connection = connection(peer, HTTP_2); // Default is 64KiB - 1. assertEquals(65535, connection.peerSettings.getInitialWindowSize(-1)); // Verify the peer received the ACK. MockSpdyPeer.InFrame ackFrame = peer.takeFrame(); assertEquals(TYPE_SETTINGS, ackFrame.type); assertEquals(0, ackFrame.streamId); assertTrue(ackFrame.ack); ackFrame = peer.takeFrame(); assertEquals(TYPE_SETTINGS, ackFrame.type); assertEquals(0, ackFrame.streamId); assertTrue(ackFrame.ack); // This stream was created *after* the connection settings were adjusted. FramedStream stream = connection.newStream(headerEntries("a", "android"), false, true); assertEquals(3368, connection.peerSettings.getInitialWindowSize(DEFAULT_INITIAL_WINDOW_SIZE)); assertEquals(1684, connection.bytesLeftInWriteWindow); // initial wasn't affected. // New Stream is has the most recent initial window size. assertEquals(3368, stream.bytesLeftInWriteWindow); }
@Test public void clientSendsEmptyDataServerDoesntSendWindowUpdateHttp2() throws Exception { peer.setVariantAndClient(HTTP_2, false); // Write the mocking script. peer.acceptFrame(); // SYN_STREAM peer.acceptFrame(); // DATA peer.sendFrame().synReply(false, 3, headerEntries("a", "android")); peer.play(); // Play it back. FramedConnection connection = connection(peer, HTTP_2); FramedStream client = connection.newStream(headerEntries("b", "banana"), true, true); BufferedSink out = Okio.buffer(client.getSink()); out.write(EMPTY_BYTE_ARRAY); out.flush(); out.close(); // Verify the peer received what was expected. assertEquals(TYPE_HEADERS, peer.takeFrame().type); assertEquals(TYPE_DATA, peer.takeFrame().type); assertEquals(3, peer.frameCount()); }
@Test public void serverSendsEmptyDataClientDoesntSendWindowUpdateHttp2() throws Exception { peer.setVariantAndClient(HTTP_2, false); // Write the mocking script. peer.acceptFrame(); // SYN_STREAM peer.sendFrame().synReply(false, 3, headerEntries("a", "android")); peer.sendFrame().data(true, 3, data(0), 0); peer.play(); // Play it back. FramedConnection connection = connection(peer, HTTP_2); FramedStream client = connection.newStream(headerEntries("b", "banana"), false, true); assertEquals(-1, client.getSource().read(new Buffer(), 1)); // Verify the peer received what was expected. MockSpdyPeer.InFrame synStream = peer.takeFrame(); assertEquals(TYPE_HEADERS, synStream.type); assertEquals(3, peer.frameCount()); }
private FramedConnection sendHttp2SettingsAndCheckForAck(boolean client, Settings settings) throws IOException, InterruptedException { peer.setVariantAndClient(HTTP_2, client); peer.sendFrame().settings(settings); peer.acceptFrame(); // ACK peer.acceptFrame(); // PING peer.sendFrame().ping(true, 1, 0); peer.play(); // play it back FramedConnection connection = connection(peer, HTTP_2); // verify the peer received the ACK MockSpdyPeer.InFrame ackFrame = peer.takeFrame(); assertEquals(TYPE_SETTINGS, ackFrame.type); assertEquals(0, ackFrame.streamId); assertTrue(ackFrame.ack); connection.ping().roundTripTime(); // Ensure that settings have been applied before returning. return connection; }
@After public void tearDown() throws Exception { peer.close(); }
@Test public void receiveGoAwayHttp2() throws Exception { peer.setVariantAndClient(HTTP_2, false); // write the mocking script peer.acceptFrame(); // SYN_STREAM 3 peer.acceptFrame(); // SYN_STREAM 5 peer.sendFrame().goAway(3, PROTOCOL_ERROR, EMPTY_BYTE_ARRAY); peer.acceptFrame(); // PING peer.sendFrame().ping(true, 1, 0); peer.acceptFrame(); // DATA STREAM 3 peer.play(); // play it back FramedConnection connection = connection(peer, HTTP_2); FramedStream stream1 = connection.newStream(headerEntries("a", "android"), true, true); FramedStream stream2 = connection.newStream(headerEntries("b", "banana"), true, true); connection.ping().roundTripTime(); // Ensure the GO_AWAY that resets stream2 has been received. BufferedSink sink1 = Okio.buffer(stream1.getSink()); BufferedSink sink2 = Okio.buffer(stream2.getSink()); sink1.writeUtf8("abc"); try { sink2.writeUtf8("abc"); sink2.flush(); fail(); } catch (IOException expected) { assertEquals("stream was reset: REFUSED_STREAM", expected.getMessage()); } sink1.writeUtf8("def"); sink1.close(); try { connection.newStream(headerEntries("c", "cola"), true, true); fail(); } catch (IOException expected) { assertEquals("shutdown", expected.getMessage()); } assertTrue(stream1.isOpen()); assertFalse(stream2.isOpen()); assertEquals(1, connection.openStreamCount()); // verify the peer received what was expected MockSpdyPeer.InFrame synStream1 = peer.takeFrame(); assertEquals(TYPE_HEADERS, synStream1.type); MockSpdyPeer.InFrame synStream2 = peer.takeFrame(); assertEquals(TYPE_HEADERS, synStream2.type); MockSpdyPeer.InFrame ping = peer.takeFrame(); assertEquals(TYPE_PING, ping.type); MockSpdyPeer.InFrame data1 = peer.takeFrame(); assertEquals(TYPE_DATA, data1.type); assertEquals(3, data1.streamId); assertTrue(Arrays.equals("abcdef".getBytes("UTF-8"), data1.data)); }