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

Merge pull request #476 from adriancole/http2-goaway

Add HTTP/2 GOAWAY frame
This commit is contained in:
Adrian Cole
2014-01-22 11:19:34 -08:00
11 changed files with 360 additions and 93 deletions

View File

@@ -61,8 +61,25 @@ public interface FrameReader extends Closeable {
* set. The data is opaque binary, and there are no rules on the content.
*/
void ping(boolean ack, int payload1, int payload2);
void goAway(int lastGoodStreamId, ErrorCode errorCode);
void windowUpdate(int streamId, int deltaWindowSize, boolean endFlowControl);
/**
* The peer tells us to stop creating streams. It is safe to replay
* streams with {@code ID > lastGoodStreamId} on a new connection. In-
* flight streams with {@code ID <= lastGoodStreamId} can only be replayed
* on a new connection if they are idempotent.
*
* @param lastGoodStreamId the last stream ID the peer processed before
* sending this message. If {@lastGoodStreamId} is zero, the peer processed no frames.
* @param errorCode reason for closing the connection.
* @param debugData only valid for http/2; opaque debug data to send.
*/
void goAway(int lastGoodStreamId, ErrorCode errorCode, byte[] debugData);
/**
* Notifies that an additional {@code windowSizeIncrement} bytes can be
* sent on {@code streamId}, or the connection if {@code streamId} is zero.
*/
void windowUpdate(int streamId, long windowSizeIncrement);
void priority(int streamId, int priority);
/**

View File

@@ -65,6 +65,7 @@ public interface FrameWriter extends Closeable {
*/
void data(boolean outFinished, int streamId, byte[] data, int offset, int byteCount)
throws IOException;
/** Write okhttp's settings to the peer. */
void settings(Settings okHttpSettings) throws IOException;
void noop() throws IOException;
@@ -81,6 +82,21 @@ public interface FrameWriter extends Closeable {
* sent. The data is opaque binary, and there are no rules on the content.
*/
void ping(boolean ack, int payload1, int payload2) throws IOException;
void goAway(int lastGoodStreamId, ErrorCode errorCode) throws IOException;
void windowUpdate(int streamId, int deltaWindowSize) throws IOException;
/**
* Tell the peer to stop creating streams and that we last processed
* {@code lastGoodStreamId}, or zero if no streams were processed.
*
* @param lastGoodStreamId the last stream ID processed, or zero if no
* streams were processed.
* @param errorCode reason for closing the connection.
* @param debugData only valid for http/2; opaque debug data to send.
*/
void goAway(int lastGoodStreamId, ErrorCode errorCode, byte[] debugData) throws IOException;
/**
* Inform peer that an additional {@code windowSizeIncrement} bytes can be
* sent on {@code streamId}, or the connection if {@code streamId} is zero.
*/
void windowUpdate(int streamId, long windowSizeIncrement) throws IOException;
}

View File

@@ -73,7 +73,6 @@ public final class Http20Draft09 implements Variant {
static final int FLAG_END_PUSH_PROMISE = 0x4;
static final int FLAG_PRIORITY = 0x8;
static final int FLAG_ACK = 0x1;
static final int FLAG_END_FLOW_CONTROL = 0x1;
@Override public FrameReader newReader(InputStream in, Settings peerSettings, boolean client) {
return new Reader(in, peerSettings.getHeaderTableSize(), client);
@@ -119,6 +118,9 @@ public final class Http20Draft09 implements Variant {
// boolean r = (w1 & 0xc0000000) != 0; // Reserved.
int length = (w1 & 0x3fff0000) >> 16; // 14-bit unsigned.
if (length > 16383) {
throw new IOException("FRAME_SIZE_ERROR max size is 16383: " + length);
}
int type = (w1 & 0xff00) >> 8;
int flags = w1 & 0xff;
// boolean r = (w2 & 0x80000000) != 0; // Reserved.
@@ -267,26 +269,28 @@ public final class Http20Draft09 implements Variant {
private void readGoAway(Handler handler, int flags, int length, int streamId)
throws IOException {
if (length < 8) throw ioException("TYPE_GOAWAY length < 8: %s", length);
if (streamId != 0) throw ioException("TYPE_GOAWAY streamId != 0");
int lastStreamId = in.readInt();
int errorCodeInt = in.readInt();
int opaqueDataLength = length - 8;
ErrorCode errorCode = ErrorCode.fromHttp2(errorCodeInt);
if (errorCode == null) {
throw ioException("TYPE_RST_STREAM unexpected error code: %d", errorCodeInt);
throw ioException("TYPE_GOAWAY unexpected error code: %d", errorCodeInt);
}
if (Util.skipByReading(in, opaqueDataLength) != opaqueDataLength) {
throw new IOException("TYPE_GOAWAY opaque data was truncated");
byte[] debugData = Util.EMPTY_BYTE_ARRAY;
if (opaqueDataLength > 0) { // Must read debug data in order to not corrupt the connection.
debugData = new byte[opaqueDataLength];
Util.readFully(in, debugData);
}
handler.goAway(lastStreamId, errorCode);
handler.goAway(lastStreamId, errorCode, debugData);
}
private void readWindowUpdate(Handler handler, int flags, int length, int streamId)
throws IOException {
int w1 = in.readInt();
// boolean r = (w1 & 0x80000000) != 0; // Reserved.
int windowSizeIncrement = (w1 & 0x7fffffff);
boolean endFlowControl = (flags & FLAG_END_FLOW_CONTROL) != 0;
handler.windowUpdate(streamId, windowSizeIncrement, endFlowControl);
if (length != 4) throw ioException("TYPE_WINDOW_UPDATE length !=4: %s", length);
long increment = (in.readInt() & 0x7fffffff);
if (increment == 0) throw ioException("windowSizeIncrement was 0", increment);
handler.windowUpdate(streamId, increment);
}
@Override public void close() throws IOException {
@@ -431,14 +435,33 @@ public final class Http20Draft09 implements Variant {
out.writeInt(payload2);
}
@Override public synchronized void goAway(int lastGoodStreamId, ErrorCode errorCode)
@Override
public synchronized void goAway(int lastGoodStreamId, ErrorCode errorCode, byte[] debugData)
throws IOException {
// TODO
if (errorCode.httpCode == -1) {
throw new IllegalArgumentException("errorCode.httpCode == -1");
}
int length = 8 + debugData.length;
checkFrameSize(length);
out.writeInt((length & 0x3fff) << 16 | (TYPE_GOAWAY & 0xff) << 8);
out.writeInt(0); // connection-level
out.writeInt(lastGoodStreamId);
out.writeInt(errorCode.httpCode);
if (debugData.length > 0) {
out.write(debugData);
}
out.flush();
}
@Override public synchronized void windowUpdate(int streamId, int deltaWindowSize)
@Override public synchronized void windowUpdate(int streamId, long increment)
throws IOException {
// TODO
if (increment == 0 || increment > 0x7fffffffL) {
throw new IllegalArgumentException(
"windowSizeIncrement must be between 1 and 0x7fffffff: " + increment);
}
out.writeInt(4 << 16 | (TYPE_WINDOW_UPDATE & 0xff) << 8); // No flags.
out.writeInt(streamId);
out.writeInt((int) increment);
}
@Override public void close() throws IOException {
@@ -447,7 +470,9 @@ public final class Http20Draft09 implements Variant {
}
private static void checkFrameSize(int bytes) throws IOException {
if (bytes > 16383) throw ioException("FRAME_SIZE_ERROR max size is 16383: %s", bytes);
if (bytes > 16383) {
throw new IllegalArgumentException("FRAME_SIZE_ERROR max size is 16383: " + bytes);
}
}
private static IOException ioException(String message, Object... args) throws IOException {

View File

@@ -252,8 +252,9 @@ final class Spdy3 implements Variant {
int w1 = in.readInt();
int w2 = in.readInt();
int streamId = w1 & 0x7fffffff;
int deltaWindowSize = w2 & 0x7fffffff;
handler.windowUpdate(streamId, deltaWindowSize, false);
long increment = w2 & 0x7fffffff;
if (increment == 0) throw ioException("windowSizeIncrement was 0", increment);
handler.windowUpdate(streamId, increment);
}
private void readPing(Handler handler, int flags, int length) throws IOException {
@@ -271,7 +272,7 @@ final class Spdy3 implements Variant {
if (errorCode == null) {
throw ioException("TYPE_GOAWAY unexpected error code: %d", errorCodeInt);
}
handler.goAway(lastGoodStreamId, errorCode);
handler.goAway(lastGoodStreamId, errorCode, Util.EMPTY_BYTE_ARRAY);
}
private void readSettings(Handler handler, int flags, int length) throws IOException {
@@ -410,8 +411,8 @@ final class Spdy3 implements Variant {
void sendDataFrame(int streamId, int flags, byte[] data, int offset, int byteCount)
throws IOException {
if (byteCount > 0xffffff) {
throw new IOException("FRAME_TOO_LARGE max size is 16Mib: " + byteCount);
if (byteCount > 0xffffffL) {
throw new IllegalArgumentException("FRAME_TOO_LARGE max size is 16Mib: " + byteCount);
}
out.writeInt(streamId & 0x7fffffff);
out.writeInt((flags & 0xff) << 24 | byteCount & 0xffffff);
@@ -471,9 +472,12 @@ final class Spdy3 implements Variant {
out.flush();
}
@Override public synchronized void goAway(int lastGoodStreamId, ErrorCode errorCode)
@Override
public synchronized void goAway(int lastGoodStreamId, ErrorCode errorCode, byte[] ignored)
throws IOException {
if (errorCode.spdyGoAwayCode == -1) throw new IllegalArgumentException();
if (errorCode.spdyGoAwayCode == -1) {
throw new IllegalArgumentException("errorCode.spdyGoAwayCode == -1");
}
int type = TYPE_GOAWAY;
int flags = 0;
int length = 8;
@@ -484,15 +488,19 @@ final class Spdy3 implements Variant {
out.flush();
}
@Override public synchronized void windowUpdate(int streamId, int deltaWindowSize)
@Override public synchronized void windowUpdate(int streamId, long increment)
throws IOException {
if (increment == 0 || increment > 0x7fffffffL) {
throw new IllegalArgumentException(
"windowSizeIncrement must be between 1 and 0x7fffffff: " + increment);
}
int type = TYPE_WINDOW_UPDATE;
int flags = 0;
int length = 8;
out.writeInt(0x80000000 | (VERSION & 0x7fff) << 16 | type & 0xffff);
out.writeInt((flags & 0xff) << 24 | length & 0xffffff);
out.writeInt(streamId);
out.writeInt(deltaWindowSize);
out.writeInt((int) increment);
out.flush();
}

View File

@@ -218,19 +218,19 @@ public final class SpdyConnection implements Closeable {
frameWriter.rstStream(streamId, statusCode);
}
void writeWindowUpdateLater(final int streamId, final int deltaWindowSize) {
void writeWindowUpdateLater(final int streamId, final int windowSizeIncrement) {
executor.submit(new NamedRunnable("OkHttp %s stream %d", hostName, streamId) {
@Override public void execute() {
try {
writeWindowUpdate(streamId, deltaWindowSize);
writeWindowUpdate(streamId, windowSizeIncrement);
} catch (IOException ignored) {
}
}
});
}
void writeWindowUpdate(int streamId, int deltaWindowSize) throws IOException {
frameWriter.windowUpdate(streamId, deltaWindowSize);
void writeWindowUpdate(int streamId, int windowSizeIncrement) throws IOException {
frameWriter.windowUpdate(streamId, windowSizeIncrement);
}
/**
@@ -303,7 +303,8 @@ public final class SpdyConnection implements Closeable {
shutdown = true;
lastGoodStreamId = this.lastGoodStreamId;
}
frameWriter.goAway(lastGoodStreamId, statusCode);
// TODO: propagate exception message into debugData
frameWriter.goAway(lastGoodStreamId, statusCode, Util.EMPTY_BYTE_ARRAY);
}
}
@@ -597,7 +598,10 @@ public final class SpdyConnection implements Closeable {
}
}
@Override public void goAway(int lastGoodStreamId, ErrorCode errorCode) {
@Override
public void goAway(int lastGoodStreamId, ErrorCode errorCode, byte[] debugData) {
if (debugData.length > 0) { // TODO: log the debugData
}
synchronized (SpdyConnection.this) {
shutdown = true;
@@ -614,16 +618,16 @@ public final class SpdyConnection implements Closeable {
}
}
@Override public void windowUpdate(int streamId, int deltaWindowSize, boolean endFlowControl) {
@Override public void windowUpdate(int streamId, long windowSizeIncrement) {
if (streamId == 0) {
// TODO: honor whole-stream flow control
// TODO: honor connection-level flow control
return;
}
// TODO: honor endFlowControl
SpdyStream stream = getStream(streamId);
if (stream != null) {
stream.receiveWindowUpdate(deltaWindowSize);
stream.receiveWindowUpdate(windowSizeIncrement);
}
}

View File

@@ -325,8 +325,8 @@ public final class SpdyStream {
notifyAll();
}
synchronized void receiveWindowUpdate(int deltaWindowSize) {
out.unacknowledgedBytes -= deltaWindowSize;
synchronized void receiveWindowUpdate(long windowSizeIncrement) {
out.unacknowledgedBytes -= windowSizeIncrement;
notifyAll();
}
@@ -594,7 +594,7 @@ public final class SpdyStream {
* acknowledged with an incoming {@code WINDOW_UPDATE} frame. Writes
* block if they cause this to exceed the {@code WINDOW_SIZE}.
*/
private int unacknowledgedBytes = 0;
private long unacknowledgedBytes = 0;
@Override public void write(int b) throws IOException {
Util.writeSingleByte(this, b);

View File

@@ -49,12 +49,13 @@ class BaseTestHandler implements FrameReader.Handler {
fail();
}
@Override public void goAway(int lastGoodStreamId, ErrorCode errorCode) {
@Override
public void goAway(int lastGoodStreamId, ErrorCode errorCode, byte[] debugData) {
fail();
}
@Override
public void windowUpdate(int streamId, int deltaWindowSize, boolean endFlowControl) {
public void windowUpdate(int streamId, long windowSizeIncrement) {
fail();
}

View File

@@ -348,11 +348,113 @@ public class Http20Draft09Test {
try {
sendDataFrame(new byte[0x1000000]);
fail();
} catch (IOException e) {
} catch (IllegalArgumentException e) {
assertEquals("FRAME_SIZE_ERROR max size is 16383: 16777216", e.getMessage());
}
}
@Test public void windowUpdateRoundTrip() throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
DataOutputStream dataOut = new DataOutputStream(out);
final long expectedWindowSizeIncrement = 0x7fffffff;
// Compose the expected window update frame.
dataOut.writeShort(4); // length
dataOut.write(Http20Draft09.TYPE_WINDOW_UPDATE);
dataOut.write(0); // No flags.
dataOut.writeInt(expectedStreamId);
dataOut.writeInt((int) expectedWindowSizeIncrement);
// Check writer sends the same bytes.
assertArrayEquals(out.toByteArray(), windowUpdate(expectedWindowSizeIncrement));
FrameReader fr = newReader(out);
fr.nextFrame(new BaseTestHandler() { // Consume the window update frame.
@Override public void windowUpdate(int streamId, long windowSizeIncrement) {
assertEquals(expectedStreamId, streamId);
assertEquals(expectedWindowSizeIncrement, windowSizeIncrement);
}
});
}
@Test public void badWindowSizeIncrement() throws IOException {
try {
windowUpdate(0);
fail();
} catch (IllegalArgumentException e) {
assertEquals("windowSizeIncrement must be between 1 and 0x7fffffff: 0", e.getMessage());
}
try {
windowUpdate(0x80000000L);
fail();
} catch (IllegalArgumentException e) {
assertEquals("windowSizeIncrement must be between 1 and 0x7fffffff: 2147483648",
e.getMessage());
}
}
@Test public void goAwayWithoutDebugDataRoundTrip() throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
DataOutputStream dataOut = new DataOutputStream(out);
final ErrorCode expectedError = ErrorCode.PROTOCOL_ERROR;
// Compose the expected GOAWAY frame without debug data.
dataOut.writeShort(8); // Without debug data there's only 2 32-bit fields.
dataOut.write(Http20Draft09.TYPE_GOAWAY);
dataOut.write(0); // no flags.
dataOut.writeInt(0); // connection-scope
dataOut.writeInt(expectedStreamId); // last good stream.
dataOut.writeInt(expectedError.httpCode);
// Check writer sends the same bytes.
assertArrayEquals(out.toByteArray(),
sendGoAway(expectedStreamId, expectedError, Util.EMPTY_BYTE_ARRAY));
FrameReader fr = newReader(out);
fr.nextFrame(new BaseTestHandler() { // Consume the go away frame.
@Override public void goAway(int lastGoodStreamId, ErrorCode errorCode, byte[] debugData) {
assertEquals(expectedStreamId, lastGoodStreamId);
assertEquals(expectedError, errorCode);
assertEquals(0, debugData.length);
}
});
}
@Test public void goAwayWithDebugDataRoundTrip() throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
DataOutputStream dataOut = new DataOutputStream(out);
final ErrorCode expectedError = ErrorCode.PROTOCOL_ERROR;
final byte[] expectedData = new byte[8];
Arrays.fill(expectedData, (byte) '*');
// Compose the expected GOAWAY frame without debug data.
dataOut.writeShort(8 + expectedData.length);
dataOut.write(Http20Draft09.TYPE_GOAWAY);
dataOut.write(0); // no flags.
dataOut.writeInt(0); // connection-scope
dataOut.writeInt(0); // never read any stream!
dataOut.writeInt(expectedError.httpCode);
dataOut.write(expectedData);
// Check writer sends the same bytes.
assertArrayEquals(out.toByteArray(), sendGoAway(0, expectedError, expectedData));
FrameReader fr = newReader(out);
fr.nextFrame(new BaseTestHandler() { // Consume the go away frame.
@Override public void goAway(int lastGoodStreamId, ErrorCode errorCode, byte[] debugData) {
assertEquals(0, lastGoodStreamId);
assertEquals(expectedError, errorCode);
assertArrayEquals(expectedData, debugData);
}
});
}
private Http20Draft09.Reader newReader(ByteArrayOutputStream out) {
return new Http20Draft09.Reader(new ByteArrayInputStream(out.toByteArray()),
Variant.HTTP_20_DRAFT_09.initialPeerSettings(false).getHeaderTableSize(), false);
@@ -370,6 +472,13 @@ public class Http20Draft09Test {
return out.toByteArray();
}
private byte[] sendGoAway(int lastGoodStreamId, ErrorCode errorCode, byte[] debugData)
throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
new Http20Draft09.Writer(out, true).goAway(lastGoodStreamId, errorCode, debugData);
return out.toByteArray();
}
private byte[] sendDataFrame(byte[] data) throws IOException {
return sendDataFrame(data, 0, data.length);
}
@@ -379,4 +488,10 @@ public class Http20Draft09Test {
new Http20Draft09.Writer(out, true).sendDataFrame(expectedStreamId, 0, data, offset, byteCount);
return out.toByteArray();
}
private byte[] windowUpdate(long windowSizeIncrement) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
new Http20Draft09.Writer(out, true).windowUpdate(expectedStreamId, windowSizeIncrement);
return out.toByteArray();
}
}

View File

@@ -185,7 +185,7 @@ public final class MockSpdyPeer implements Closeable {
public int associatedStreamId;
public int priority;
public ErrorCode errorCode;
public int deltaWindowSize;
public long windowSizeIncrement;
public List<Header> headerBlock;
public byte[] data;
public Settings settings;
@@ -250,18 +250,20 @@ public final class MockSpdyPeer implements Closeable {
this.type = Spdy3.TYPE_NOOP;
}
@Override public void goAway(int lastGoodStreamId, ErrorCode errorCode) {
@Override
public void goAway(int lastGoodStreamId, ErrorCode errorCode, byte[] debugData) {
if (this.type != -1) throw new IllegalStateException();
this.type = Spdy3.TYPE_GOAWAY;
this.streamId = lastGoodStreamId;
this.errorCode = errorCode;
this.data = debugData;
}
@Override public void windowUpdate(int streamId, int deltaWindowSize, boolean endFlowControl) {
@Override public void windowUpdate(int streamId, long windowSizeIncrement) {
if (this.type != -1) throw new IllegalStateException();
this.type = Spdy3.TYPE_WINDOW_UPDATE;
this.streamId = streamId;
this.deltaWindowSize = deltaWindowSize;
this.windowSizeIncrement = windowSizeIncrement;
}
@Override public void priority(int streamId, int priority) {

View File

@@ -15,10 +15,14 @@
*/
package com.squareup.okhttp.internal.spdy;
import com.squareup.okhttp.internal.Util;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import org.junit.Test;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
@@ -29,11 +33,63 @@ public class Spdy3Test {
try {
sendDataFrame(new byte[0x1000000]);
fail();
} catch (IOException e) {
assertEquals("FRAME_TOO_LARGE max size is 16Mib: 16777216", e.getMessage());
} catch (IllegalArgumentException e) {
assertEquals("FRAME_TOO_LARGE max size is 16Mib: " + 0x1000000L, e.getMessage());
}
}
@Test public void badWindowSizeIncrement() throws IOException {
try {
windowUpdate(0);
fail();
} catch (IllegalArgumentException e) {
assertEquals("windowSizeIncrement must be between 1 and 0x7fffffff: 0", e.getMessage());
}
try {
windowUpdate(0x80000000L);
fail();
} catch (IllegalArgumentException e) {
assertEquals("windowSizeIncrement must be between 1 and 0x7fffffff: 2147483648",
e.getMessage());
}
}
@Test public void goAwayRoundTrip() throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
DataOutputStream dataOut = new DataOutputStream(out);
final ErrorCode expectedError = ErrorCode.PROTOCOL_ERROR;
// Compose the expected GOAWAY frame without debug data
// |C| Version(15bits) | Type(16bits) |
dataOut.writeInt(0x80000000 | (Spdy3.VERSION & 0x7fff) << 16 | Spdy3.TYPE_GOAWAY & 0xffff);
// | Flags (8) | Length (24 bits) |
dataOut.writeInt(8); // no flags and length is 8.
dataOut.writeInt(expectedStreamId); // last good stream.
dataOut.writeInt(expectedError.spdyGoAwayCode);
// Check writer sends the same bytes.
assertArrayEquals(out.toByteArray(),
sendGoAway(expectedStreamId, expectedError, Util.EMPTY_BYTE_ARRAY));
// SPDY/3 does not send debug data, so bytes should be same!
assertArrayEquals(out.toByteArray(), sendGoAway(expectedStreamId, expectedError, new byte[8]));
FrameReader fr = newReader(out);
fr.nextFrame(new BaseTestHandler() { // Consume the goAway frame.
@Override public void goAway(int lastGoodStreamId, ErrorCode errorCode, byte[] debugData) {
assertEquals(expectedStreamId, lastGoodStreamId);
assertEquals(expectedError, errorCode);
assertEquals(0, debugData.length);
}
});
}
private Spdy3.Reader newReader(ByteArrayOutputStream out) {
return new Spdy3.Reader(new ByteArrayInputStream(out.toByteArray()), false);
}
private byte[] sendDataFrame(byte[] data) throws IOException {
return sendDataFrame(data, 0, data.length);
}
@@ -43,4 +99,17 @@ public class Spdy3Test {
new Spdy3.Writer(out, true).sendDataFrame(expectedStreamId, 0, data, offset, byteCount);
return out.toByteArray();
}
private byte[] windowUpdate(long increment) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
new Spdy3.Writer(out, true).windowUpdate(expectedStreamId, increment);
return out.toByteArray();
}
private byte[] sendGoAway(int lastGoodStreamId, ErrorCode errorCode, byte[] debugData)
throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
new Spdy3.Writer(out, true).goAway(lastGoodStreamId, errorCode, debugData);
return out.toByteArray();
}
}

View File

@@ -15,7 +15,6 @@
*/
package com.squareup.okhttp.internal.spdy;
import com.squareup.okhttp.Protocol;
import com.squareup.okhttp.internal.Base64;
import com.squareup.okhttp.internal.Util;
import java.io.ByteArrayOutputStream;
@@ -27,6 +26,7 @@ import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.After;
import org.junit.Ignore;
import org.junit.Test;
import static com.squareup.okhttp.internal.Util.UTF_8;
@@ -189,9 +189,7 @@ public final class SpdyConnectionTest {
peer.play();
// play it back
SpdyConnection connection =
new SpdyConnection.Builder(true, peer.openSocket()).handler(REJECT_INCOMING_STREAMS)
.build();
SpdyConnection connection = connection(peer, Variant.SPDY3);
connection.noop();
// verify the peer received what was expected
@@ -206,7 +204,7 @@ public final class SpdyConnectionTest {
peer.play();
// play it back
new SpdyConnection.Builder(true, peer.openSocket()).handler(REJECT_INCOMING_STREAMS).build();
connection(peer, Variant.SPDY3);
// verify the peer received what was expected
MockSpdyPeer.InFrame ping = peer.takeFrame();
@@ -225,7 +223,7 @@ public final class SpdyConnectionTest {
peer.play();
// play it back
http2Connection(peer);
connection(peer, Variant.HTTP_20_DRAFT_09);
// verify the peer received what was expected
MockSpdyPeer.InFrame ping = peer.takeFrame();
@@ -243,9 +241,7 @@ public final class SpdyConnectionTest {
peer.play();
// play it back
SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket())
.handler(REJECT_INCOMING_STREAMS)
.build();
SpdyConnection connection = connection(peer, Variant.SPDY3);
Ping ping = connection.ping();
assertTrue(ping.roundTripTime() > 0);
assertTrue(ping.roundTripTime() < TimeUnit.SECONDS.toNanos(1));
@@ -267,7 +263,7 @@ public final class SpdyConnectionTest {
peer.play();
// play it back
SpdyConnection connection = http2Connection(peer);
SpdyConnection connection = connection(peer, Variant.HTTP_20_DRAFT_09);
Ping ping = connection.ping();
assertTrue(ping.roundTripTime() > 0);
assertTrue(ping.roundTripTime() < TimeUnit.SECONDS.toNanos(1));
@@ -290,7 +286,7 @@ public final class SpdyConnectionTest {
peer.play();
// play it back
new SpdyConnection.Builder(true, peer.openSocket()).handler(REJECT_INCOMING_STREAMS).build();
connection(peer, Variant.SPDY3);
// verify the peer received what was expected
MockSpdyPeer.InFrame ping2 = peer.takeFrame();
@@ -338,9 +334,7 @@ public final class SpdyConnectionTest {
peer.play();
// play it back
SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket())
.handler(REJECT_INCOMING_STREAMS)
.build();
SpdyConnection connection = connection(peer, Variant.SPDY3);
peer.takeFrame(); // Guarantees that the peer Settings frame has been processed.
synchronized (connection) {
@@ -365,9 +359,7 @@ public final class SpdyConnectionTest {
peer.play();
// play it back
SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket())
.handler(REJECT_INCOMING_STREAMS)
.build();
SpdyConnection connection = connection(peer, Variant.SPDY3);
peer.takeFrame(); // Guarantees that the Settings frame has been processed.
synchronized (connection) {
@@ -391,7 +383,7 @@ public final class SpdyConnectionTest {
peer.play();
// play it back
new SpdyConnection.Builder(true, peer.openSocket()).handler(REJECT_INCOMING_STREAMS).build();
connection(peer, Variant.SPDY3);
// verify the peer received what was expected
MockSpdyPeer.InFrame rstStream = peer.takeFrame();
@@ -411,7 +403,7 @@ public final class SpdyConnectionTest {
peer.play();
// play it back
new SpdyConnection.Builder(true, peer.openSocket()).handler(REJECT_INCOMING_STREAMS).build();
connection(peer, Variant.SPDY3);
// verify the peer received what was expected
MockSpdyPeer.InFrame rstStream = peer.takeFrame();
@@ -433,9 +425,7 @@ public final class SpdyConnectionTest {
peer.play();
// play it back
SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket())
.handler(REJECT_INCOMING_STREAMS)
.build();
SpdyConnection connection = connection(peer, Variant.SPDY3);
SpdyStream stream = connection.newStream(headerEntries("a", "android"), true, false);
OutputStream out = stream.getOutputStream();
out.write("square".getBytes(UTF_8));
@@ -479,9 +469,7 @@ public final class SpdyConnectionTest {
peer.play();
// play it back
SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket())
.handler(REJECT_INCOMING_STREAMS)
.build();
SpdyConnection connection = connection(peer, Variant.SPDY3);
SpdyStream stream = connection.newStream(headerEntries("a", "android"), true, true);
OutputStream out = stream.getOutputStream();
connection.ping().roundTripTime(); // Ensure that the RST_CANCEL has been received.
@@ -521,9 +509,7 @@ public final class SpdyConnectionTest {
peer.play();
// play it back
SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket())
.handler(REJECT_INCOMING_STREAMS)
.build();
SpdyConnection connection = connection(peer, Variant.SPDY3);
SpdyStream stream = connection.newStream(headerEntries("a", "android"), false, true);
InputStream in = stream.getInputStream();
OutputStream out = stream.getOutputStream();
@@ -566,9 +552,7 @@ public final class SpdyConnectionTest {
peer.play();
// play it back
SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket())
.handler(REJECT_INCOMING_STREAMS)
.build();
SpdyConnection connection = connection(peer, Variant.SPDY3);
SpdyStream stream = connection.newStream(headerEntries("a", "android"), true, true);
InputStream in = stream.getInputStream();
OutputStream out = stream.getOutputStream();
@@ -610,9 +594,7 @@ public final class SpdyConnectionTest {
peer.play();
// play it back
SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket())
.handler(REJECT_INCOMING_STREAMS)
.build();
SpdyConnection connection = connection(peer, Variant.SPDY3);
SpdyStream stream = connection.newStream(headerEntries("a", "android"), false, true);
InputStream in = stream.getInputStream();
assertStreamData("square", in);
@@ -772,18 +754,29 @@ public final class SpdyConnectionTest {
assertEquals(2, ping.payload1);
}
@Test public void receiveGoAway() throws Exception {
receiveGoAway(Variant.SPDY3);
}
@Test public void receiveGoAwayHttp2() throws Exception {
receiveGoAway(Variant.HTTP_20_DRAFT_09);
}
private void receiveGoAway(Variant variant) throws Exception {
MockSpdyPeer peer = new MockSpdyPeer(variant, false);
// write the mocking script
peer.acceptFrame(); // SYN_STREAM 1
peer.acceptFrame(); // SYN_STREAM 3
peer.sendFrame().goAway(1, PROTOCOL_ERROR);
peer.sendFrame().goAway(1, PROTOCOL_ERROR, Util.EMPTY_BYTE_ARRAY);
peer.acceptFrame(); // PING
peer.sendFrame().ping(true, 1, 0);
peer.acceptFrame(); // DATA STREAM 1
peer.play();
// play it back
SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build();
SpdyConnection connection = connection(peer, variant);
SpdyStream stream1 = connection.newStream(headerEntries("a", "android"), true, true);
SpdyStream stream2 = connection.newStream(headerEntries("b", "banana"), true, true);
connection.ping().roundTripTime(); // Ensure that the GO_AWAY has been received.
@@ -1006,7 +999,24 @@ public final class SpdyConnectionTest {
}
@Test public void readSendsWindowUpdate() throws Exception {
int windowUpdateThreshold = Variant.SPDY3.initialPeerSettings(true).getInitialWindowSize() / 2;
readSendsWindowUpdate(Variant.SPDY3);
}
/**
* This test fails on http/2 as it tries to send too large data frame. In
* practice, {@link SpdyStream#OUTPUT_BUFFER_SIZE} prevents us from sending
* too large frames. The test should probably be rewritten to take into
* account max frame size per variant.
*/
@Test @Ignore public void readSendsWindowUpdateHttp2() throws Exception {
readSendsWindowUpdate(Variant.HTTP_20_DRAFT_09);
}
private void readSendsWindowUpdate(Variant variant)
throws IOException, InterruptedException {
MockSpdyPeer peer = new MockSpdyPeer(variant, false);
int windowUpdateThreshold = variant.initialPeerSettings(true).getInitialWindowSize() / 2;
// Write the mocking script.
peer.acceptFrame(); // SYN_STREAM
peer.sendFrame().synReply(false, 1, headerEntries("a", "android"));
@@ -1018,7 +1028,7 @@ public final class SpdyConnectionTest {
peer.play();
// Play it back.
SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build();
SpdyConnection connection = connection(peer, variant);
SpdyStream stream = connection.newStream(headerEntries("b", "banana"), true, true);
assertEquals(windowUpdateThreshold, stream.windowUpdateThreshold);
assertEquals(headerEntries("a", "android"), stream.getResponseHeaders());
@@ -1039,7 +1049,7 @@ public final class SpdyConnectionTest {
MockSpdyPeer.InFrame windowUpdate = peer.takeFrame();
assertEquals(TYPE_WINDOW_UPDATE, windowUpdate.type);
assertEquals(1, windowUpdate.streamId);
assertEquals(windowUpdateThreshold, windowUpdate.deltaWindowSize);
assertEquals(windowUpdateThreshold, windowUpdate.windowSizeIncrement);
}
}
@@ -1112,7 +1122,7 @@ public final class SpdyConnectionTest {
peer.play();
// play it back
SpdyConnection connection = http2Connection(peer);
SpdyConnection connection = connection(peer, Variant.HTTP_20_DRAFT_09);
SpdyStream stream = connection.newStream(headerEntries("b", "banana"), true, true);
OutputStream out = stream.getOutputStream();
out.write(buff);
@@ -1172,7 +1182,7 @@ public final class SpdyConnectionTest {
peer.play();
// play it back
http2Connection(peer);
connection(peer, Variant.HTTP_20_DRAFT_09);
// verify the peer received what was expected
MockSpdyPeer.InFrame rstStream = peer.takeFrame();
@@ -1189,7 +1199,7 @@ public final class SpdyConnectionTest {
peer.play();
// play it back
SpdyConnection connection = http2Connection(peer);
SpdyConnection connection = connection(peer, Variant.HTTP_20_DRAFT_09);
// verify the peer received the ACK
MockSpdyPeer.InFrame pingFrame = peer.takeFrame();
@@ -1201,9 +1211,9 @@ public final class SpdyConnectionTest {
return connection;
}
private SpdyConnection http2Connection(MockSpdyPeer peer) throws IOException {
private SpdyConnection connection(MockSpdyPeer peer, Variant variant) throws IOException {
return new SpdyConnection.Builder(true, peer.openSocket())
.protocol(Protocol.HTTP_2)
.protocol(variant.getProtocol())
.handler(REJECT_INCOMING_STREAMS).build();
}