diff --git a/src/main/java/com/squareup/okhttp/internal/net/spdy/SpdyConnection.java b/src/main/java/com/squareup/okhttp/internal/net/spdy/SpdyConnection.java index 27e7c30a3..1050f4a23 100644 --- a/src/main/java/com/squareup/okhttp/internal/net/spdy/SpdyConnection.java +++ b/src/main/java/com/squareup/okhttp/internal/net/spdy/SpdyConnection.java @@ -427,6 +427,18 @@ public final class SpdyConnection implements Closeable { } } + @Override public void headers(int flags, int streamId, List nameValueBlock) + throws IOException { + SpdyStream replyStream = getStream(streamId); + if (replyStream != null) { + try { + replyStream.receiveHeaders(nameValueBlock); + } catch (ProtocolException e) { + replyStream.closeLater(SpdyStream.RST_PROTOCOL_ERROR); + } + } + } + @Override public void rstStream(int flags, int streamId, int statusCode) { SpdyStream rstStream = removeStream(streamId); if (rstStream != null) { diff --git a/src/main/java/com/squareup/okhttp/internal/net/spdy/SpdyReader.java b/src/main/java/com/squareup/okhttp/internal/net/spdy/SpdyReader.java index d49d4848e..c8608f89d 100644 --- a/src/main/java/com/squareup/okhttp/internal/net/spdy/SpdyReader.java +++ b/src/main/java/com/squareup/okhttp/internal/net/spdy/SpdyReader.java @@ -118,8 +118,8 @@ final class SpdyReader implements Closeable { return true; case SpdyConnection.TYPE_HEADERS: - Streams.skipByReading(in, length); - throw new UnsupportedOperationException("TODO"); + readHeaders(handler, flags, length); + return true; default: throw new IOException("Unexpected frame"); @@ -158,6 +158,14 @@ final class SpdyReader implements Closeable { handler.rstStream(flags, streamId, statusCode); } + private void readHeaders(Handler handler, int flags, int length) throws IOException { + int w1 = in.readInt(); + in.readShort(); // unused + int streamId = w1 & 0x7fffffff; + List nameValueBlock = readNameValueBlock(length - 6); + handler.headers(flags, streamId, nameValueBlock); + } + private DataInputStream newNameValueBlockStream() { // Limit the inflater input stream to only those bytes in the Name/Value block. final InputStream throttleStream = new InputStream() { @@ -269,11 +277,11 @@ final class SpdyReader implements Closeable { void synStream(int flags, int streamId, int associatedStreamId, int priority, List nameValueBlock); void synReply(int flags, int streamId, List nameValueBlock) throws IOException; + void headers(int flags, int streamId, List nameValueBlock) throws IOException; void rstStream(int flags, int streamId, int statusCode); void settings(int flags, Settings settings); void noop(); void ping(int flags, int streamId); void goAway(int flags, int lastGoodStreamId); - // TODO: headers } } diff --git a/src/main/java/com/squareup/okhttp/internal/net/spdy/SpdyStream.java b/src/main/java/com/squareup/okhttp/internal/net/spdy/SpdyStream.java index ea780f119..f059c8932 100644 --- a/src/main/java/com/squareup/okhttp/internal/net/spdy/SpdyStream.java +++ b/src/main/java/com/squareup/okhttp/internal/net/spdy/SpdyStream.java @@ -26,6 +26,7 @@ import java.io.OutputStream; import java.net.ProtocolException; import java.net.SocketTimeoutException; import static java.nio.ByteOrder.BIG_ENDIAN; +import java.util.ArrayList; import java.util.List; /** @@ -271,6 +272,19 @@ public final class SpdyStream { } } + void receiveHeaders(List headers) throws IOException { + assert (!Thread.holdsLock(SpdyStream.this)); + synchronized (this) { + if (responseHeaders == null) { + throw new ProtocolException(); + } + List newHeaders = new ArrayList(); + newHeaders.addAll(responseHeaders); + newHeaders.addAll(headers); + this.responseHeaders = newHeaders; + } + } + void receiveData(InputStream in, int length) throws IOException { assert (!Thread.holdsLock(SpdyStream.this)); this.in.receive(in, length); diff --git a/src/main/java/com/squareup/okhttp/internal/net/spdy/SpdyWriter.java b/src/main/java/com/squareup/okhttp/internal/net/spdy/SpdyWriter.java index 28514a2dc..6428b9ee4 100644 --- a/src/main/java/com/squareup/okhttp/internal/net/spdy/SpdyWriter.java +++ b/src/main/java/com/squareup/okhttp/internal/net/spdy/SpdyWriter.java @@ -75,6 +75,21 @@ final class SpdyWriter implements Closeable { out.flush(); } + public synchronized void headers( + int flags, int streamId, List nameValueBlock) throws IOException { + writeNameValueBlockToBuffer(nameValueBlock); + int type = SpdyConnection.TYPE_HEADERS; + int length = nameValueBlockBuffer.size() + 6; + int unused = 0; + + out.writeInt(0x80000000 | (SpdyConnection.VERSION & 0x7fff) << 16 | type & 0xffff); + out.writeInt((flags & 0xff) << 24 | length & 0xffffff); + out.writeInt(streamId & 0x7fffffff); + out.writeShort(unused); + nameValueBlockBuffer.writeTo(out); + out.flush(); + } + public synchronized void synReset(int streamId, int statusCode) throws IOException { int flags = 0; int type = SpdyConnection.TYPE_RST_STREAM; diff --git a/src/test/java/com/squareup/okhttp/internal/net/spdy/MockSpdyPeer.java b/src/test/java/com/squareup/okhttp/internal/net/spdy/MockSpdyPeer.java index 233839de0..ba70085f8 100644 --- a/src/test/java/com/squareup/okhttp/internal/net/spdy/MockSpdyPeer.java +++ b/src/test/java/com/squareup/okhttp/internal/net/spdy/MockSpdyPeer.java @@ -172,6 +172,14 @@ public final class MockSpdyPeer { this.nameValueBlock = nameValueBlock; } + @Override public void headers(int flags, int streamId, List nameValueBlock) { + if (this.type != -1) throw new IllegalStateException(); + this.type = SpdyConnection.TYPE_HEADERS; + this.streamId = streamId; + this.flags = flags; + this.nameValueBlock = nameValueBlock; + } + @Override public void data(int flags, int streamId, InputStream in, int length) throws IOException { if (this.type != -1) throw new IllegalStateException(); diff --git a/src/test/java/com/squareup/okhttp/internal/net/spdy/SpdyConnectionTest.java b/src/test/java/com/squareup/okhttp/internal/net/spdy/SpdyConnectionTest.java index ce3359c1a..0df67b6ba 100644 --- a/src/test/java/com/squareup/okhttp/internal/net/spdy/SpdyConnectionTest.java +++ b/src/test/java/com/squareup/okhttp/internal/net/spdy/SpdyConnectionTest.java @@ -849,6 +849,58 @@ public final class SpdyConnectionTest { assertEquals(TYPE_SYN_STREAM, synStream.type); } + @Test public void headers() throws Exception { + // write the mocking script + peer.acceptFrame(); // SYN STREAM + peer.acceptFrame(); // PING + peer.sendFrame().synReply(0, 1, Arrays.asList("a", "android")); + peer.sendFrame().headers(0, 1, Arrays.asList("c", "c3po")); + peer.sendFrame().ping(0, 1); + peer.play(); + + // play it back + SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build(); + SpdyStream stream = connection.newStream(Arrays.asList("b", "banana"), true, true); + connection.ping().roundTripTime(); // Ensure that the HEADERS has been received. + assertEquals(Arrays.asList("a", "android", "c", "c3po"), stream.getResponseHeaders()); + + // verify the peer received what was expected + MockSpdyPeer.InFrame synStream = peer.takeFrame(); + assertEquals(TYPE_SYN_STREAM, synStream.type); + MockSpdyPeer.InFrame ping = peer.takeFrame(); + assertEquals(TYPE_PING, ping.type); + } + + @Test public void headersBeforeReply() throws Exception { + // write the mocking script + peer.acceptFrame(); // SYN STREAM + peer.acceptFrame(); // PING + peer.sendFrame().headers(0, 1, Arrays.asList("c", "c3po")); + peer.acceptFrame(); // RST STREAM + peer.sendFrame().ping(0, 1); + peer.play(); + + // play it back + SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build(); + SpdyStream stream = connection.newStream(Arrays.asList("b", "banana"), true, true); + connection.ping().roundTripTime(); // Ensure that the HEADERS has been received. + try { + stream.getResponseHeaders(); + fail(); + } catch (IOException e) { + assertEquals("stream was reset: PROTOCOL_ERROR", e.getMessage()); + } + + // verify the peer received what was expected + MockSpdyPeer.InFrame synStream = peer.takeFrame(); + assertEquals(TYPE_SYN_STREAM, synStream.type); + MockSpdyPeer.InFrame ping = peer.takeFrame(); + assertEquals(TYPE_PING, ping.type); + MockSpdyPeer.InFrame rstStream = peer.takeFrame(); + assertEquals(TYPE_RST_STREAM, rstStream.type); + assertEquals(RST_PROTOCOL_ERROR, rstStream.statusCode); + } + private void writeAndClose(SpdyStream stream, String data) throws IOException { OutputStream out = stream.getOutputStream(); out.write(data.getBytes("UTF-8"));