diff --git a/okhttp-protocols/src/main/java/com/squareup/okhttp/Protocol.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/Protocol.java
index f57361ee0..dbd4499d8 100644
--- a/okhttp-protocols/src/main/java/com/squareup/okhttp/Protocol.java
+++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/Protocol.java
@@ -26,7 +26,7 @@ import java.util.List;
* NPN or
* ALPN selection.
*
- *
+ *
*
Protocol vs Scheme
* Despite its name, {@link java.net.URL#getProtocol()} returns the
* {@link java.net.URI#getScheme() scheme} (http, https, etc.) of the URL, not
diff --git a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/Platform.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/Platform.java
index f02a2ca60..7b54b29c6 100644
--- a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/Platform.java
+++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/Platform.java
@@ -44,7 +44,7 @@ import javax.net.ssl.SSLSocket;
* ALPN and NPN
* This class uses TLS extensions ALPN and NPN to negotiate the upgrade from
* HTTP/1.1 (the default protocol to use with TLS on port 443) to either SPDY
- * or HTTP/2.0.
+ * or HTTP/2.
*
* NPN (Next Protocol Negotiation) was developed for SPDY. It is widely
* available and we support it on both Android (4.1+) and OpenJDK 7 (via the
diff --git a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/FrameReader.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/FrameReader.java
index 36e2e152a..50568bb05 100644
--- a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/FrameReader.java
+++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/FrameReader.java
@@ -21,7 +21,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.List;
-/** Reads transport frames for SPDY/3 or HTTP/2.0. */
+/** Reads transport frames for SPDY/3 or HTTP/2. */
public interface FrameReader extends Closeable {
void readConnectionHeader() throws IOException;
boolean nextFrame(Handler handler) throws IOException;
@@ -32,7 +32,7 @@ public interface FrameReader extends Closeable {
/**
* Create or update incoming headers, creating the corresponding streams
* if necessary. Frames that trigger this are SPDY SYN_STREAM, HEADERS, and
- * SYN_REPLY, and HTTP/2.0 HEADERS and PUSH_PROMISE.
+ * SYN_REPLY, and HTTP/2 HEADERS and PUSH_PROMISE.
*
* @param outFinished true if the receiver should not send further frames.
* @param inFinished true if the sender will not send further frames.
@@ -40,23 +40,28 @@ public interface FrameReader extends Closeable {
* @param associatedStreamId the stream that triggered the sender to create
* this stream.
* @param priority or -1 for no priority. For SPDY, priorities range from 0
- * (highest) thru 7 (lowest). For HTTP/2.0, priorities range from 0
+ * (highest) thru 7 (lowest). For HTTP/2, priorities range from 0
* (highest) thru 2^31-1 (lowest), defaulting to 2^30.
*/
void headers(boolean outFinished, boolean inFinished, int streamId, int associatedStreamId,
int priority, List headerBlock, HeadersMode headersMode);
void rstStream(int streamId, ErrorCode errorCode);
void settings(boolean clearPrevious, Settings settings);
+
+ /** HTTP/2 only. */
+ void ackSettings();
+
+ /** SPDY/3 only. */
void noop();
/**
* Read a connection-level ping from the peer. {@code ack} indicates this
* is a reply. Payload parameters are different between SPDY/3 and HTTP/2.
- *
+ *
* In SPDY/3, only the first {@code payload1} parameter is set. If the
* reader is a client, it is an unsigned even number. Likewise, a server
* will receive an odd number.
- *
+ *
* In HTTP/2, both {@code payload1} and {@code payload2} parameters are
* set. The data is opaque binary, and there are no rules on the content.
*/
@@ -84,7 +89,7 @@ public interface FrameReader extends Closeable {
/**
* HTTP/2 only. Receive a push promise header block.
- *
+ *
* A push promise contains all the headers that pertain to a server-initiated
* request, and a {@code promisedStreamId} to which response frames will be
* delivered. Push promise frames are sent as a part of the response to
diff --git a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/FrameWriter.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/FrameWriter.java
index e508c96f1..b7182a660 100644
--- a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/FrameWriter.java
+++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/FrameWriter.java
@@ -20,15 +20,15 @@ import java.io.Closeable;
import java.io.IOException;
import java.util.List;
-/** Writes transport frames for SPDY/3 or HTTP/2.0. */
+/** Writes transport frames for SPDY/3 or HTTP/2. */
public interface FrameWriter extends Closeable {
- /** HTTP/2.0 only. */
+ /** HTTP/2 only. */
void connectionHeader() throws IOException;
void ackSettings() throws IOException;
/**
* HTTP/2 only. Send a push promise header block.
- *
+ *
* A push promise contains all the headers that pertain to a server-initiated
* request, and a {@code promisedStreamId} to which response frames will be
* delivered. Push promise frames are sent as a part of the response to
@@ -68,16 +68,18 @@ public interface FrameWriter extends Closeable {
/** Write okhttp's settings to the peer. */
void settings(Settings okHttpSettings) throws IOException;
+
+ /** SPDY/3 only. */
void noop() throws IOException;
/**
* Send a connection-level ping to the peer. {@code ack} indicates this is
* a reply. Payload parameters are different between SPDY/3 and HTTP/2.
- *
+ *
* In SPDY/3, only the first {@code payload1} parameter is sent. If the
* sender is a client, it is an unsigned odd number. Likewise, a server
* will send an even number.
- *
+ *
* In HTTP/2, both {@code payload1} and {@code payload2} parameters are
* sent. The data is opaque binary, and there are no rules on the content.
*/
diff --git a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/HpackDraft05.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/HpackDraft05.java
index 87ba091d7..c1349327e 100644
--- a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/HpackDraft05.java
+++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/HpackDraft05.java
@@ -132,7 +132,7 @@ final class HpackDraft05 {
/**
* Called by the reader when the peer sent a new header table size setting.
- *
+ *
* Evicts entries or clears the table as needed.
*/
void maxHeaderTableByteCount(int newMaxHeaderTableByteCount) {
diff --git a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/Http20Draft09.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/Http20Draft09.java
index ada0d6b2f..eb0f3f694 100644
--- a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/Http20Draft09.java
+++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/Http20Draft09.java
@@ -37,17 +37,6 @@ public final class Http20Draft09 implements Variant {
return Protocol.HTTP_2;
}
- // http://tools.ietf.org/html/draft-ietf-httpbis-http2-09#section-6.5
- static Settings defaultSettings(boolean client) {
- Settings settings = new Settings();
- settings.set(Settings.HEADER_TABLE_SIZE, 0, 4096);
- if (client) { // client specifies whether or not it accepts push.
- settings.set(Settings.ENABLE_PUSH, 0, 1);
- }
- settings.set(Settings.INITIAL_WINDOW_SIZE, 0, 65535);
- return settings;
- }
-
private static final byte[] CONNECTION_HEADER =
"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".getBytes(Util.UTF_8);
@@ -225,12 +214,14 @@ public final class Http20Draft09 implements Variant {
private void readSettings(Handler handler, short length, byte flags, int streamId)
throws IOException {
+ if (streamId != 0) throw ioException("TYPE_SETTINGS streamId != 0");
if ((flags & FLAG_ACK) != 0) {
if (length != 0) throw ioException("FRAME_SIZE_ERROR ack frame should be empty!");
+ handler.ackSettings();
+ return;
}
if (length % 8 != 0) throw ioException("TYPE_SETTINGS length %% 8 != 0: %s", length);
- if (streamId != 0) throw ioException("TYPE_SETTINGS streamId != 0");
Settings settings = new Settings();
for (int i = 0; i < length; i += 8) {
int w1 = in.readInt();
@@ -348,8 +339,7 @@ public final class Http20Draft09 implements Variant {
@Override
public synchronized void pushPromise(int streamId, int promisedStreamId,
- List requestHeaders)
- throws IOException {
+ List requestHeaders) throws IOException {
hpackBuffer.reset();
hpackWriter.writeHeaders(requestHeaders);
diff --git a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/Spdy3.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/Spdy3.java
index e20c9d710..99411b047 100644
--- a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/Spdy3.java
+++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/Spdy3.java
@@ -36,12 +36,6 @@ final class Spdy3 implements Variant {
return Protocol.SPDY_3;
}
- static Settings defaultSettings(boolean client) {
- Settings settings = new Settings();
- settings.set(Settings.INITIAL_WINDOW_SIZE, 0, 65535);
- return settings;
- }
-
static final int TYPE_DATA = 0x0;
static final int TYPE_SYN_STREAM = 0x1;
static final int TYPE_SYN_REPLY = 0x2;
diff --git a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/SpdyConnection.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/SpdyConnection.java
index 29342ad23..f9226e332 100644
--- a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/SpdyConnection.java
+++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/SpdyConnection.java
@@ -82,20 +82,30 @@ public final class SpdyConnection implements Closeable {
private Map pings;
private int nextPingId;
+ /**
+ * Initial window size to use for the connection and new streams. Until the
+ * peer sends an update, this will is initialized to {@code 65535}.
+ */
+ int initialWindowSize = 65535;
+
/**
* Count of bytes that can be written on the connection before receiving a
* window update.
*/
- private long bytesLeftInWriteWindow = 65535; // TODO: initialize this with settings.
+ // Visible for testing
+ long bytesLeftInWriteWindow = initialWindowSize;
// TODO: Do we want to dynamically adjust settings, or KISS and only set once?
// Settings we might send include toggling push, adjusting compression table size.
final Settings okHttpSettings;
// TODO: MWS will need to guard on this setting before attempting to push.
final Settings peerSettings;
+ private boolean receivedInitialPeerSettings = false;
final FrameReader frameReader;
final FrameWriter frameWriter;
+ // Visible for testing
+ final Reader readerRunnable;
final ByteArrayPool bufferPool;
private SpdyConnection(Builder builder) {
@@ -108,23 +118,22 @@ public final class SpdyConnection implements Closeable {
Variant variant;
if (protocol == Protocol.HTTP_2) {
- okHttpSettings = Http20Draft09.defaultSettings(client);
- variant = new Http20Draft09(); // connection-specific settings here!
+ variant = new Http20Draft09();
} else if (protocol == Protocol.SPDY_3) {
- okHttpSettings = Spdy3.defaultSettings(client);
- variant = new Spdy3(); // connection-specific settings here!
+ variant = new Spdy3();
} else {
throw new AssertionError(protocol);
}
-
+ okHttpSettings = new Settings();
+ peerSettings = new Settings();
// TODO: implement stream limit
// okHttpSettings.set(Settings.MAX_CONCURRENT_STREAMS, 0, max);
- peerSettings = okHttpSettings;
bufferPool = new ByteArrayPool(peerSettings.getInitialWindowSize() * 8);
frameReader = variant.newReader(builder.in, client);
frameWriter = variant.newWriter(builder.out, client);
- new Thread(new Reader()).start(); // Not a daemon thread.
+ readerRunnable = new Reader();
+ new Thread(readerRunnable).start(); // Not a daemon thread.
}
/** The protocol as selected using NPN or ALPN. */
@@ -195,7 +204,7 @@ public final class SpdyConnection implements Closeable {
streamId = nextStreamId;
nextStreamId += 2;
stream = new SpdyStream(
- streamId, this, outFinished, inFinished, priority, requestHeaders, peerSettings);
+ streamId, this, outFinished, inFinished, priority, requestHeaders, initialWindowSize);
if (stream.isOpen()) {
streams.put(streamId, stream);
setIdle(false);
@@ -234,6 +243,15 @@ public final class SpdyConnection implements Closeable {
}
}
+ /**
+ * {@code delta} will be negative if a settings frame initial window is
+ * smaller than the last.
+ */
+ void addBytesToWriteWindow(long delta) {
+ bytesLeftInWriteWindow += delta;
+ if (delta > 0) SpdyConnection.this.notifyAll();
+ }
+
void writeSynResetLater(final int streamId, final ErrorCode errorCode) {
executor.submit(new NamedRunnable("OkHttp %s stream %d", hostName, streamId) {
@Override public void execute() {
@@ -468,7 +486,7 @@ public final class SpdyConnection implements Closeable {
}
}
- private class Reader extends NamedRunnable implements FrameReader.Handler {
+ class Reader extends NamedRunnable implements FrameReader.Handler {
private Reader() {
super("OkHttp %s", hostName);
}
@@ -531,7 +549,7 @@ public final class SpdyConnection implements Closeable {
// Create a stream.
final SpdyStream newStream = new SpdyStream(streamId, SpdyConnection.this, outFinished,
- inFinished, priority, headerBlock, peerSettings);
+ inFinished, priority, headerBlock, initialWindowSize);
lastGoodStreamId = streamId;
streams.put(streamId, newStream);
executor.submit(new NamedRunnable("OkHttp %s stream %d", hostName, streamId) {
@@ -577,6 +595,15 @@ public final class SpdyConnection implements Closeable {
if (getProtocol() == Protocol.HTTP_2) {
ackSettingsLater();
}
+ int peerInitialWindowSize = peerSettings.getInitialWindowSize();
+ if (peerInitialWindowSize != -1 && peerInitialWindowSize != initialWindowSize) {
+ long delta = peerInitialWindowSize - initialWindowSize;
+ SpdyConnection.this.initialWindowSize = peerInitialWindowSize;
+ if (!receivedInitialPeerSettings) {
+ addBytesToWriteWindow(delta);
+ receivedInitialPeerSettings = true;
+ }
+ }
if (!streams.isEmpty()) {
streamsToNotify = streams.values().toArray(new SpdyStream[streams.size()]);
}
@@ -607,6 +634,10 @@ public final class SpdyConnection implements Closeable {
});
}
+ @Override public void ackSettings() {
+ // TODO: If we don't get this callback after sending settings to the peer, SETTINGS_TIMEOUT.
+ }
+
@Override public void noop() {
}
@@ -646,7 +677,7 @@ public final class SpdyConnection implements Closeable {
if (streamId == 0) {
synchronized (SpdyConnection.this) {
bytesLeftInWriteWindow += windowSizeIncrement;
- notifyAll();
+ SpdyConnection.this.notifyAll();
}
} else {
// TODO: honor endFlowControl
diff --git a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/SpdyStream.java b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/SpdyStream.java
index 68ab921d6..7336f1dbb 100644
--- a/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/SpdyStream.java
+++ b/okhttp-protocols/src/main/java/com/squareup/okhttp/internal/spdy/SpdyStream.java
@@ -55,7 +55,7 @@ public final class SpdyStream {
private List responseHeaders;
private final SpdyDataInputStream in;
- private final SpdyDataOutputStream out;
+ final SpdyDataOutputStream out;
/**
* The reason why this stream was abnormally closed. If there are multiple
@@ -65,18 +65,19 @@ public final class SpdyStream {
private ErrorCode errorCode = null;
SpdyStream(int id, SpdyConnection connection, boolean outFinished, boolean inFinished,
- int priority, List requestHeaders, Settings peerSettings) {
+ int priority, List