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

Merge pull request #33 from square/jwilson/spdysettings

Handle incoming SETTINGS frames in SPDY.
This commit is contained in:
Jake Wharton
2012-09-22 11:31:49 -07:00
6 changed files with 135 additions and 7 deletions

View File

@@ -43,6 +43,27 @@ public final class SpdyConnection implements Closeable {
static final int FLAG_FIN = 0x01;
static final int FLAG_UNIDIRECTIONAL = 0x02;
/** Peer request to clear durable settings. */
static final int FLAG_SETTINGS_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS = 0x01;
/** Sent by servers only. The peer requests this setting persisted for future connections. */
static final int FLAG_SETTINGS_PERSIST_VALUE = 0x1;
/** Sent by clients only. The client is reminding the server of a persisted value. */
static final int FLAG_SETTINGS_PERSISTED = 0x2;
/** Sender's estimate of max incoming kbps. */
static final int SETTINGS_UPLOAD_BANDWIDTH = 0x01;
/** Sender's estimate of max outgoing kbps. */
static final int SETTINGS_DOWNLOAD_BANDWIDTH = 0x02;
/** Sender's estimate of milliseconds between sending a request and receiving a response. */
static final int SETTINGS_ROUND_TRIP_TIME = 0x03;
/** Sender's maximum number of concurrent streams. */
static final int SETTINGS_MAX_CONCURRENT_STREAMS = 0x04;
/** Current CWND in Packets. */
static final int SETTINGS_CURRENT_CWND = 0x05;
/** Retransmission rate. Percentage */
static final int SETTINGS_DOWNLOAD_RETRANS_RATE = 0x06;
/** Window size in bytes. */
static final int SETTINGS_INITIAL_WINDOW_SIZE = 0x07;
static final int TYPE_EOF = -1;
static final int TYPE_DATA = 0x00;
static final int TYPE_SYN_STREAM = 0x01;
@@ -61,6 +82,9 @@ public final class SpdyConnection implements Closeable {
private final SpdyWriter spdyWriter;
private final Executor executor;
/** The maximum number of concurrent streams permitted by the peer, or -1 for no limit. */
int peerMaxConcurrentStreams;
/**
* User code to run in response to an incoming stream. This must not be run
* on the read thread, otherwise a deadlock is possible.
@@ -75,11 +99,12 @@ public final class SpdyConnection implements Closeable {
spdyReader = new SpdyReader(builder.in);
spdyWriter = new SpdyWriter(builder.out);
handler = builder.handler;
clearSettings();
String name = isClient() ? "ClientReader" : "ServerReader";
executor = builder.executor != null
? builder.executor
: Executors.newCachedThreadPool(Threads.newThreadFactory(name));
: Executors.newCachedThreadPool(Threads.newThreadFactory(name, true));
executor.execute(new Reader());
}
@@ -90,6 +115,26 @@ public final class SpdyConnection implements Closeable {
return nextStreamId % 2 == 1;
}
/**
* Resets this connection's settings to their default values.
*/
private synchronized void clearSettings() {
peerMaxConcurrentStreams = -1;
}
/**
* Receive an incoming setting from a peer. This SPDY client doesn't care
* about most settings, and so it doesn't save them.
* https://github.com/square/okhttp/issues/32
*/
private synchronized void receiveSetting(int id, int idFlags, int value) {
switch (id) {
case SETTINGS_MAX_CONCURRENT_STREAMS:
peerMaxConcurrentStreams = value;
break;
}
}
private SpdyStream getStream(int id) {
SpdyStream stream = streams.get(id);
if (stream == null) {
@@ -264,8 +309,23 @@ public final class SpdyConnection implements Closeable {
return true;
case SpdyConnection.TYPE_SETTINGS:
// TODO: implement
System.out.println("Unimplemented TYPE_SETTINGS frame discarded");
int numberOfEntries = spdyReader.in.readInt();
if (spdyReader.length != 4 + numberOfEntries * 8) {
// TODO: DIE
}
if ((spdyReader.flags & FLAG_SETTINGS_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS) != 0) {
clearSettings();
}
for (int i = 0; i < numberOfEntries; i++) {
int w1 = spdyReader.in.readInt();
int value = spdyReader.in.readInt();
// The ID is a 24 bit little-endian value, so 0xabcdefxx becomes 0x00efcdab.
int id = ((w1 & 0xff000000) >>> 24)
| ((w1 & 0xff0000) >>> 8)
| ((w1 & 0xff00) << 8);
int idFlags = (w1 & 0xff);
receiveSetting(id, idFlags, value);
}
return true;
case SpdyConnection.TYPE_NOOP:

View File

@@ -112,6 +112,9 @@ final class SpdyReader {
readSynReset();
return SpdyConnection.TYPE_RST_STREAM;
case SpdyConnection.TYPE_SETTINGS:
return SpdyConnection.TYPE_SETTINGS;
default:
readControlFrame();
return type;
@@ -128,7 +131,7 @@ final class SpdyReader {
int s3 = in.readShort();
streamId = w1 & 0x7fffffff;
associatedStreamId = w2 & 0x7fffffff;
priority = s3 & 0xc000 >> 14;
priority = s3 & 0xc000 >>> 14;
// int unused = s3 & 0x3fff;
nameValueBlock = readNameValueBlock(length - 10);
}

View File

@@ -38,6 +38,7 @@ final class SpdyWriter {
public List<String> nameValueBlock;
private final ByteArrayOutputStream nameValueBlockBuffer;
private final DataOutputStream nameValueBlockOut;
private int settingsRemaining = 0;
SpdyWriter(OutputStream out) {
this.out = new DataOutputStream(out);
@@ -85,6 +86,7 @@ final class SpdyWriter {
out.writeInt((flags & 0xff) << 24 | length & 0xffffff);
out.writeInt(streamId & 0x7fffffff);
out.writeInt(statusCode);
out.flush();
}
public void data(byte[] data) throws IOException {
@@ -105,4 +107,36 @@ final class SpdyWriter {
}
nameValueBlockOut.flush();
}
/**
* Begins a settings frame with {@code numberOfEntries} settings. Calls to
* this method <strong>must</strong> be followed by {@code numberOfEntries}
* calls to {@link #setting}.
*/
public void settings(int numberOfEntries) throws IOException {
if (settingsRemaining != 0) throw new IllegalStateException();
settingsRemaining = numberOfEntries;
int type = SpdyConnection.TYPE_SETTINGS;
int length = 4 + numberOfEntries * 8;
out.writeInt(0x80000000 | (SpdyConnection.VERSION & 0x7fff) << 16 | type & 0xffff);
out.writeInt((flags & 0xff) << 24 | length & 0xffffff);
out.writeInt(numberOfEntries);
}
/**
* Writes a single setting. Must be preceded by a call to {@link #settings}.
*/
public void setting(int settingId, int settingFlag, int value) throws IOException {
if (settingsRemaining < 1) throw new IllegalStateException();
settingsRemaining--;
// settingId 0x00efcdab and settingFlag 0x12 combine to 0xabcdef12.
out.writeInt(((settingId & 0xff0000) >>> 8)
| ((settingId & 0xff00) << 8)
| ((settingId & 0xff) << 24)
| (settingFlag & 0xff));
out.writeInt(value);
if (settingsRemaining == 0) {
out.flush();
}
}
}

View File

@@ -19,10 +19,12 @@ package libcore.net.spdy;
import java.util.concurrent.ThreadFactory;
final class Threads {
public static ThreadFactory newThreadFactory(final String name) {
public static ThreadFactory newThreadFactory(final String name, final boolean daemon) {
return new ThreadFactory() {
@Override public Thread newThread(Runnable r) {
return new Thread(r, name);
Thread result = new Thread(r, name);
result.setDaemon(daemon);
return result;
}
};
}

View File

@@ -40,7 +40,7 @@ public final class MockSpdyPeer {
private final BlockingQueue<InFrame> inFrames = new LinkedBlockingQueue<InFrame>();
private int port;
private final Executor executor = Executors.newCachedThreadPool(
Threads.newThreadFactory("MockSpdyPeer"));
Threads.newThreadFactory("MockSpdyPeer", true));
public void acceptFrame() {
frameCount++;

View File

@@ -93,4 +93,33 @@ public final class SpdyConnectionTest extends TestCase {
assertEquals(0, synStream.reader.associatedStreamId);
assertEquals(Arrays.asList("b", "banana"), synStream.reader.nameValueBlock);
}
public void testServerSendsSettingsToClient() throws Exception {
// write the mocking script
SpdyWriter newStream = peer.sendFrame();
newStream.flags = SpdyConnection.FLAG_SETTINGS_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS;
newStream.settings(1);
newStream.setting(SpdyConnection.SETTINGS_MAX_CONCURRENT_STREAMS,
SpdyConnection.FLAG_SETTINGS_PERSIST_VALUE, 10);
// TODO: send a 'ping' frame.
peer.play();
// play it back
IncomingStreamHandler handler = new IncomingStreamHandler() {
@Override public void receive(SpdyStream stream) throws IOException {
assertEquals(Arrays.asList("a", "android"), stream.getRequestHeaders());
assertEquals(-1, stream.getRstStatusCode());
stream.reply(Arrays.asList("b", "banana"));
}
};
SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket())
.handler(handler)
.build();
// TODO: wait for the ping response, which better and faster than this fragile sleep.
Thread.sleep(1000);
synchronized (connection) {
assertEquals(10, connection.peerMaxConcurrentStreams);
}
}
}