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

Be more careful around IOExceptions.

This adds a first test where a frame is truncated. That has some
unpleasant consequences for the other tests because it means the
MockSpdyPeer is more aggressive about closing the socket when all
frames have been sent.

It also reduces the use of exceptions for flow control when handling
bogus incoming frames. This is a bit worse and a bit better; my
goal is to make it easier to differentiate between protocol-level
problems (bogus frames) from transport-level problems (closed sockets
and EOF streams).
This commit is contained in:
jwilson
2013-01-19 11:37:24 -05:00
parent c37a793573
commit b9580d68e9
8 changed files with 193 additions and 132 deletions

View File

@@ -28,6 +28,7 @@ import java.net.URI;
import java.net.URL;
import java.nio.ByteOrder;
import java.nio.charset.Charset;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicReference;
/**
@@ -313,4 +314,14 @@ public final class Util {
}
return result.toString();
}
public static ThreadFactory newThreadFactory(final String name, final boolean daemon) {
return new ThreadFactory() {
@Override public Thread newThread(Runnable r) {
Thread result = new Thread(r, name);
result.setDaemon(daemon);
return result;
}
};
}
}

View File

@@ -117,11 +117,11 @@ public final class SpdyConnection implements Closeable {
String prefix = builder.client ? "Spdy Client " : "Spdy Server ";
readExecutor = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Threads.newThreadFactory(prefix + "Reader", false));
new SynchronousQueue<Runnable>(), Util.newThreadFactory(prefix + "Reader", false));
writeExecutor = new ThreadPoolExecutor(0, 1, 60, TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>(), Threads.newThreadFactory(prefix + "Writer", false));
new LinkedBlockingQueue<Runnable>(), Util.newThreadFactory(prefix + "Writer", false));
callbackExecutor = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>(), Threads.newThreadFactory(prefix + "Callbacks", false));
new SynchronousQueue<Runnable>(), Util.newThreadFactory(prefix + "Callbacks", false));
readExecutor.execute(new Reader());
}
@@ -317,7 +317,17 @@ public final class SpdyConnection implements Closeable {
* internal executor services.
*/
@Override public void close() throws IOException {
shutdown(GOAWAY_OK);
close(GOAWAY_OK, SpdyStream.RST_CANCEL);
}
private void close(int shutdownStatusCode, int rstStatusCode) throws IOException {
assert (!Thread.holdsLock(this));
IOException thrown = null;
try {
shutdown(shutdownStatusCode);
} catch (IOException e) {
thrown = e;
}
SpdyStream[] streamsToClose = null;
Ping[] pingsToCancel = null;
@@ -335,8 +345,9 @@ public final class SpdyConnection implements Closeable {
if (streamsToClose != null) {
for (SpdyStream stream : streamsToClose) {
try {
stream.close(SpdyStream.RST_CANCEL);
} catch (Throwable ignored) {
stream.close(rstStatusCode);
} catch (IOException e) {
if (thrown != null) thrown = e;
}
}
}
@@ -350,7 +361,17 @@ public final class SpdyConnection implements Closeable {
writeExecutor.shutdown();
callbackExecutor.shutdown();
readExecutor.shutdown();
Util.closeAll(spdyReader, spdyWriter);
try {
spdyReader.close();
} catch (IOException e) {
thrown = e;
}
try {
spdyWriter.close();
} catch (IOException e) {
if (thrown == null) thrown = e;
}
if (thrown != null) throw thrown;
}
public static class Builder {
@@ -389,15 +410,21 @@ public final class SpdyConnection implements Closeable {
private class Reader implements Runnable, SpdyReader.Handler {
@Override public void run() {
int shutdownStatusCode = GOAWAY_INTERNAL_ERROR;
int rstStatusCode = SpdyStream.RST_INTERNAL_ERROR;
try {
while (spdyReader.nextFrame(this)) {
}
} catch (ProtocolException e) {
shutdownLater(GOAWAY_PROTOCOL_ERROR);
shutdownStatusCode = GOAWAY_OK;
rstStatusCode = SpdyStream.RST_CANCEL;
} catch (IOException e) {
throw new RuntimeException(e);
shutdownStatusCode = GOAWAY_PROTOCOL_ERROR;
rstStatusCode = SpdyStream.RST_PROTOCOL_ERROR;
} finally {
Util.closeQuietly(SpdyConnection.this);
try {
close(shutdownStatusCode, rstStatusCode);
} catch (IOException ignored) {
}
}
}
@@ -409,14 +436,9 @@ public final class SpdyConnection implements Closeable {
Util.skipByReading(in, length);
return;
}
try {
dataStream.receiveData(in, length);
if ((flags & SpdyConnection.FLAG_FIN) != 0) {
dataStream.receiveFin();
}
} catch (ProtocolException e) {
Util.skipByReading(in, length);
dataStream.closeLater(SpdyStream.RST_FLOW_CONTROL_ERROR);
dataStream.receiveData(in, length);
if ((flags & SpdyConnection.FLAG_FIN) != 0) {
dataStream.receiveFin();
}
}
@@ -456,13 +478,9 @@ public final class SpdyConnection implements Closeable {
writeSynResetLater(streamId, SpdyStream.RST_INVALID_STREAM);
return;
}
try {
replyStream.receiveReply(nameValueBlock);
if ((flags & SpdyConnection.FLAG_FIN) != 0) {
replyStream.receiveFin();
}
} catch (ProtocolException e) {
replyStream.closeLater(SpdyStream.RST_STREAM_IN_USE);
replyStream.receiveReply(nameValueBlock);
if ((flags & SpdyConnection.FLAG_FIN) != 0) {
replyStream.receiveFin();
}
}
@@ -470,11 +488,7 @@ public final class SpdyConnection implements Closeable {
throws IOException {
SpdyStream replyStream = getStream(streamId);
if (replyStream != null) {
try {
replyStream.receiveHeaders(nameValueBlock);
} catch (ProtocolException e) {
replyStream.closeLater(SpdyStream.RST_PROTOCOL_ERROR);
}
replyStream.receiveHeaders(nameValueBlock);
}
}

View File

@@ -30,7 +30,7 @@ import java.util.zip.Inflater;
import java.util.zip.InflaterInputStream;
/**
* Read version 2 SPDY frames.
* Read spdy/3 frames.
*/
final class SpdyReader implements Closeable {
static final byte[] DICTIONARY = ("\u0000\u0000\u0000\u0007options\u0000\u0000\u0000\u0004hea"

View File

@@ -23,7 +23,6 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.net.ProtocolException;
import java.net.SocketTimeoutException;
import static java.nio.ByteOrder.BIG_ENDIAN;
import java.util.ArrayList;
@@ -101,6 +100,8 @@ public final class SpdyStream {
SpdyStream(int id, SpdyConnection connection, int flags, int priority, int slot,
List<String> requestHeaders, Settings settings) {
if (connection == null) throw new NullPointerException("connection == null");
if (requestHeaders == null) throw new NullPointerException("requestHeaders == null");
this.id = id;
this.connection = connection;
this.priority = priority;
@@ -124,7 +125,8 @@ public final class SpdyStream {
* Returns true if this stream is open. A stream is open until either:
* <ul>
* <li>A {@code SYN_RESET} frame abnormally terminates the stream.
* <li>Both input and output streams have transmitted all data.
* <li>Both input and output streams have transmitted all data and
* headers.
* </ul>
* Note that the input stream may continue to yield data even after a stream
* reports itself as not open. This is because input data is buffered.
@@ -133,7 +135,7 @@ public final class SpdyStream {
if (rstStatusCode != -1) {
return false;
}
if ((in.finished || in.closed) && (out.finished || out.closed)) {
if ((in.finished || in.closed) && (out.finished || out.closed) && responseHeaders != null) {
return false;
}
return true;
@@ -287,25 +289,39 @@ public final class SpdyStream {
void receiveReply(List<String> strings) throws IOException {
assert (!Thread.holdsLock(SpdyStream.this));
boolean streamInUseError = false;
boolean open = true;
synchronized (this) {
if (!isLocallyInitiated() || responseHeaders != null) {
throw new ProtocolException();
if (isLocallyInitiated() && responseHeaders == null) {
responseHeaders = strings;
open = isOpen();
notifyAll();
} else {
streamInUseError = true;
}
responseHeaders = strings;
notifyAll();
}
if (streamInUseError) {
closeLater(SpdyStream.RST_STREAM_IN_USE);
} else if (!open) {
connection.removeStream(id);
}
}
void receiveHeaders(List<String> headers) throws IOException {
assert (!Thread.holdsLock(SpdyStream.this));
boolean protocolError = false;
synchronized (this) {
if (responseHeaders == null) {
throw new ProtocolException();
if (responseHeaders != null) {
List<String> newHeaders = new ArrayList<String>();
newHeaders.addAll(responseHeaders);
newHeaders.addAll(headers);
this.responseHeaders = newHeaders;
} else {
protocolError = true;
}
List<String> newHeaders = new ArrayList<String>();
newHeaders.addAll(responseHeaders);
newHeaders.addAll(headers);
this.responseHeaders = newHeaders;
}
if (protocolError) {
closeLater(SpdyStream.RST_PROTOCOL_ERROR);
}
}
@@ -513,14 +529,20 @@ public final class SpdyStream {
int limit;
int firstNewByte;
boolean finished;
boolean flowControlError;
synchronized (SpdyStream.this) {
finished = this.finished;
pos = this.pos;
firstNewByte = this.limit;
limit = this.limit;
if (byteCount > buffer.length - available()) {
throw new ProtocolException();
}
flowControlError = byteCount > buffer.length - available();
}
// If the peer sends more data than we can handle, discard it and close the connection.
if (flowControlError) {
Util.skipByReading(in, byteCount);
closeLater(SpdyStream.RST_FLOW_CONTROL_ERROR);
return;
}
// Discard data received after the stream is finished. It's probably a benign race.

View File

@@ -27,7 +27,7 @@ import java.util.List;
import java.util.zip.Deflater;
/**
* Write version 2 SPDY frames.
* Write spdy/3 frames.
*/
final class SpdyWriter implements Closeable {
final DataOutputStream out;

View File

@@ -1,34 +0,0 @@
/*
* Copyright (C) 2011 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.squareup.okhttp.internal.spdy;
import java.util.concurrent.ThreadFactory;
final class Threads {
public static ThreadFactory newThreadFactory(final String name, final boolean daemon) {
return new ThreadFactory() {
@Override public Thread newThread(Runnable r) {
Thread result = new Thread(r, name);
result.setDaemon(daemon);
return result;
}
};
}
private Threads() {
}
}

View File

@@ -43,7 +43,7 @@ public final class MockSpdyPeer implements Closeable {
private final BlockingQueue<InFrame> inFrames = new LinkedBlockingQueue<InFrame>();
private int port;
private final Executor executor = Executors.newCachedThreadPool(
Threads.newThreadFactory("MockSpdyPeer", true));
Util.newThreadFactory("MockSpdyPeer", true));
private ServerSocket serverSocket;
private Socket socket;
@@ -52,8 +52,17 @@ public final class MockSpdyPeer implements Closeable {
}
public SpdyWriter sendFrame() {
OutFrame frame = new OutFrame(frameCount++, bytesOut.size());
outFrames.add(frame);
outFrames.add(new OutFrame(frameCount++, bytesOut.size(), Integer.MAX_VALUE));
return spdyWriter;
}
/**
* Sends a frame, truncated to {@code truncateToLength} bytes. This is only
* useful for testing error handling as the truncated frame will be
* malformed.
*/
public SpdyWriter sendTruncatedFrame(int truncateToLength) {
outFrames.add(new OutFrame(frameCount++, bytesOut.size(), truncateToLength));
return spdyWriter;
}
@@ -99,6 +108,7 @@ public final class MockSpdyPeer implements Closeable {
if (nextOutFrame != null && nextOutFrame.sequence == i) {
int start = nextOutFrame.start;
int truncateToLength = nextOutFrame.truncateToLength;
int end;
if (outFramesIterator.hasNext()) {
nextOutFrame = outFramesIterator.next();
@@ -108,7 +118,8 @@ public final class MockSpdyPeer implements Closeable {
}
// write a frame
out.write(outBytes, start, end - start);
int length = Math.min(end - start, truncateToLength);
out.write(outBytes, start, length);
} else {
// read a frame
@@ -117,6 +128,7 @@ public final class MockSpdyPeer implements Closeable {
inFrames.add(inFrame);
}
}
Util.closeQuietly(socket);
}
public Socket openSocket() throws IOException {
@@ -139,10 +151,12 @@ public final class MockSpdyPeer implements Closeable {
private static class OutFrame {
private final int sequence;
private final int start;
private final int truncateToLength;
private OutFrame(int sequence, int start) {
private OutFrame(int sequence, int start, int truncateToLength) {
this.sequence = sequence;
this.start = start;
this.truncateToLength = truncateToLength;
}
}

View File

@@ -16,6 +16,7 @@
package com.squareup.okhttp.internal.spdy;
import com.squareup.okhttp.internal.Util;
import static com.squareup.okhttp.internal.Util.UTF_8;
import static com.squareup.okhttp.internal.spdy.Settings.PERSIST_VALUE;
import static com.squareup.okhttp.internal.spdy.SpdyConnection.FLAG_FIN;
@@ -65,10 +66,10 @@ public final class SpdyConnectionTest {
@Test public void clientCreatesStreamAndServerReplies() throws Exception {
// write the mocking script
peer.acceptFrame();
peer.acceptFrame(); // SYN_STREAM
peer.sendFrame().synReply(0, 1, Arrays.asList("a", "android"));
peer.sendFrame().data(SpdyConnection.FLAG_FIN, 1, "robot".getBytes("UTF-8"));
peer.acceptFrame();
peer.acceptFrame(); // DATA
peer.play();
// play it back
@@ -90,19 +91,21 @@ public final class SpdyConnectionTest {
assertTrue(Arrays.equals("c3po".getBytes("UTF-8"), requestData.data));
}
@Test public void headersOnlyStreamIsClosedImmediately() throws Exception {
peer.acceptFrame(); // SYN STREAM
@Test public void headersOnlyStreamIsClosedAfterReplyHeaders() throws Exception {
peer.acceptFrame(); // SYN_STREAM
peer.sendFrame().synReply(0, 1, Arrays.asList("b", "banana"));
peer.play();
SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build();
connection.newStream(Arrays.asList("a", "android"), false, false);
SpdyStream stream = connection.newStream(Arrays.asList("a", "android"), false, false);
assertEquals(1, connection.openStreamCount());
assertEquals(Arrays.asList("b", "banana"), stream.getResponseHeaders());
assertEquals(0, connection.openStreamCount());
}
@Test public void clientCreatesStreamAndServerRepliesWithFin() throws Exception {
// write the mocking script
peer.acceptFrame(); // SYN STREAM
peer.acceptFrame(); // SYN_STREAM
peer.acceptFrame(); // PING
peer.sendFrame().synReply(FLAG_FIN, 1, Arrays.asList("a", "android"));
peer.sendFrame().ping(0, 1);
@@ -125,7 +128,7 @@ public final class SpdyConnectionTest {
@Test public void serverCreatesStreamAndClientReplies() throws Exception {
// write the mocking script
peer.sendFrame().synStream(0, 2, 0, 5, 129, Arrays.asList("a", "android"));
peer.acceptFrame();
peer.acceptFrame(); // SYN_REPLY
peer.play();
// play it back
@@ -157,7 +160,7 @@ public final class SpdyConnectionTest {
@Test public void replyWithNoData() throws Exception {
// write the mocking script
peer.sendFrame().synStream(0, 2, 0, 0, 0, Arrays.asList("a", "android"));
peer.acceptFrame();
peer.acceptFrame(); // SYN_REPLY
peer.play();
// play it back
@@ -182,7 +185,7 @@ public final class SpdyConnectionTest {
@Test public void noop() throws Exception {
// write the mocking script
peer.acceptFrame();
peer.acceptFrame(); // NOOP
peer.play();
// play it back
@@ -200,7 +203,7 @@ public final class SpdyConnectionTest {
@Test public void serverPingsClient() throws Exception {
// write the mocking script
peer.sendFrame().ping(0, 2);
peer.acceptFrame();
peer.acceptFrame(); // PING
peer.play();
// play it back
@@ -217,7 +220,7 @@ public final class SpdyConnectionTest {
@Test public void clientPingsServer() throws Exception {
// write the mocking script
peer.acceptFrame();
peer.acceptFrame(); // PING
peer.sendFrame().ping(0, 1);
peer.play();
@@ -239,10 +242,10 @@ public final class SpdyConnectionTest {
@Test public void unexpectedPingIsNotReturned() throws Exception {
// write the mocking script
peer.sendFrame().ping(0, 2);
peer.acceptFrame();
peer.acceptFrame(); // PING
peer.sendFrame().ping(0, 3); // This ping will not be returned.
peer.sendFrame().ping(0, 4);
peer.acceptFrame();
peer.acceptFrame(); // PING
peer.play();
// play it back
@@ -263,7 +266,7 @@ public final class SpdyConnectionTest {
settings.set(Settings.MAX_CONCURRENT_STREAMS, PERSIST_VALUE, 10);
peer.sendFrame().settings(Settings.FLAG_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS, settings);
peer.sendFrame().ping(0, 2);
peer.acceptFrame();
peer.acceptFrame(); // PING
peer.play();
// play it back
@@ -360,10 +363,11 @@ public final class SpdyConnectionTest {
@Test public void clientClosesClientOutputStream() throws Exception {
// write the mocking script
peer.acceptFrame(); // SYN_STREAM
peer.sendFrame().synReply(0, 1, Arrays.asList("b", "banana"));
peer.acceptFrame(); // TYPE_DATA
peer.acceptFrame(); // TYPE_DATA with FLAG_FIN
peer.sendFrame().ping(0, 2);
peer.acceptFrame(); // PING response
peer.acceptFrame(); // PING
peer.sendFrame().ping(0, 1);
peer.play();
// play it back
@@ -382,6 +386,7 @@ public final class SpdyConnectionTest {
} catch (Exception expected) {
assertEquals("stream closed", expected.getMessage());
}
connection.ping().roundTripTime(); // Ensure that the SYN_REPLY has been received.
assertEquals(0, connection.openStreamCount());
// verify the peer received what was expected
@@ -397,7 +402,7 @@ public final class SpdyConnectionTest {
assertEquals(FLAG_FIN, fin.flags);
MockSpdyPeer.InFrame ping = peer.takeFrame();
assertEquals(TYPE_PING, ping.type);
assertEquals(2, ping.streamId);
assertEquals(1, ping.streamId);
}
@Test public void serverClosesClientOutputStream() throws Exception {
@@ -406,6 +411,7 @@ public final class SpdyConnectionTest {
peer.sendFrame().rstStream(1, SpdyStream.RST_CANCEL);
peer.acceptFrame(); // PING
peer.sendFrame().ping(0, 1);
peer.acceptFrame(); // DATA
peer.play();
// play it back
@@ -431,6 +437,10 @@ public final class SpdyConnectionTest {
MockSpdyPeer.InFrame ping = peer.takeFrame();
assertEquals(TYPE_PING, ping.type);
assertEquals(1, ping.streamId);
MockSpdyPeer.InFrame data = peer.takeFrame();
assertEquals(TYPE_DATA, data.type);
assertEquals(1, data.streamId);
assertEquals(FLAG_FIN, data.flags);
}
/**
@@ -523,6 +533,7 @@ public final class SpdyConnectionTest {
@Test public void serverClosesClientInputStream() throws Exception {
// write the mocking script
peer.acceptFrame(); // SYN_STREAM
peer.sendFrame().synReply(0, 1, Arrays.asList("b", "banana"));
peer.sendFrame().data(FLAG_FIN, 1, "square".getBytes(UTF_8));
peer.play();
@@ -543,12 +554,12 @@ public final class SpdyConnectionTest {
@Test public void remoteDoubleSynReply() throws Exception {
// write the mocking script
peer.acceptFrame();
peer.acceptFrame(); // SYN_STREAM
peer.sendFrame().synReply(0, 1, Arrays.asList("a", "android"));
peer.acceptFrame(); // PING
peer.sendFrame().synReply(0, 1, Arrays.asList("b", "banana"));
peer.sendFrame().ping(0, 1);
peer.acceptFrame(); // RST STREAM
peer.acceptFrame(); // RST_STREAM
peer.play();
// play it back
@@ -559,8 +570,8 @@ public final class SpdyConnectionTest {
try {
stream.getInputStream().read();
fail();
} catch (IOException e) {
assertEquals("stream was reset: STREAM_IN_USE", e.getMessage());
} catch (IOException expected) {
assertEquals("stream was reset: STREAM_IN_USE", expected.getMessage());
}
// verify the peer received what was expected
@@ -578,9 +589,9 @@ public final class SpdyConnectionTest {
@Test public void remoteDoubleSynStream() throws Exception {
// write the mocking script
peer.sendFrame().synStream(0, 2, 0, 0, 0, Arrays.asList("a", "android"));
peer.acceptFrame();
peer.acceptFrame(); // SYN_REPLY
peer.sendFrame().synStream(0, 2, 0, 0, 0, Arrays.asList("b", "banana"));
peer.acceptFrame();
peer.acceptFrame(); // RST_STREAM
peer.play();
// play it back
@@ -610,12 +621,12 @@ public final class SpdyConnectionTest {
@Test public void remoteSendsDataAfterInFinished() throws Exception {
// write the mocking script
peer.acceptFrame();
peer.acceptFrame(); // SYN_STREAM
peer.sendFrame().synReply(0, 1, Arrays.asList("a", "android"));
peer.sendFrame().data(SpdyConnection.FLAG_FIN, 1, "robot".getBytes("UTF-8"));
peer.sendFrame().data(SpdyConnection.FLAG_FIN, 1, "c3po".getBytes("UTF-8")); // Ignored.
peer.sendFrame().ping(0, 2); // Ping just to make sure the stream was fastforwarded.
peer.acceptFrame();
peer.acceptFrame(); // PING
peer.play();
// play it back
@@ -635,12 +646,12 @@ public final class SpdyConnectionTest {
@Test public void remoteSendsTooMuchData() throws Exception {
// write the mocking script
peer.acceptFrame();
peer.acceptFrame(); // SYN_STREAM
peer.sendFrame().synReply(0, 1, Arrays.asList("b", "banana"));
peer.sendFrame().data(0, 1, new byte[64 * 1024 + 1]);
peer.acceptFrame();
peer.acceptFrame(); // RST_STREAM
peer.sendFrame().ping(0, 2); // Ping just to make sure the stream was fastforwarded.
peer.acceptFrame();
peer.acceptFrame(); // PING
peer.play();
// play it back
@@ -663,10 +674,10 @@ public final class SpdyConnectionTest {
@Test public void remoteSendsRefusedStreamBeforeReplyHeaders() throws Exception {
// write the mocking script
peer.acceptFrame();
peer.acceptFrame(); // SYN_STREAM
peer.sendFrame().rstStream(1, RST_REFUSED_STREAM);
peer.sendFrame().ping(0, 2);
peer.acceptFrame();
peer.acceptFrame(); // PING
peer.play();
// play it back
@@ -691,8 +702,8 @@ public final class SpdyConnectionTest {
@Test public void receiveGoAway() throws Exception {
// write the mocking script
peer.acceptFrame(); // SYN STREAM 1
peer.acceptFrame(); // SYN STREAM 3
peer.acceptFrame(); // SYN_STREAM 1
peer.acceptFrame(); // SYN_STREAM 3
peer.sendFrame().goAway(0, 1, GOAWAY_PROTOCOL_ERROR);
peer.acceptFrame(); // PING
peer.sendFrame().ping(0, 1);
@@ -736,7 +747,7 @@ public final class SpdyConnectionTest {
@Test public void sendGoAway() throws Exception {
// write the mocking script
peer.acceptFrame(); // SYN STREAM 1
peer.acceptFrame(); // SYN_STREAM 1
peer.acceptFrame(); // GOAWAY
peer.acceptFrame(); // PING
peer.sendFrame().synStream(0, 2, 0, 0, 0, Arrays.asList("b", "b")); // Should be ignored!
@@ -748,8 +759,8 @@ public final class SpdyConnectionTest {
connection.newStream(Arrays.asList("a", "android"), true, true);
Ping ping = connection.ping();
connection.shutdown(GOAWAY_PROTOCOL_ERROR);
ping.roundTripTime(); // Ensure that the SYN STREAM has been received.
assertEquals(1, connection.openStreamCount());
ping.roundTripTime(); // Prevent the peer from exiting prematurely.
// verify the peer received what was expected
MockSpdyPeer.InFrame synStream1 = peer.takeFrame();
@@ -785,9 +796,9 @@ public final class SpdyConnectionTest {
@Test public void close() throws Exception {
// write the mocking script
peer.acceptFrame(); // SYN STREAM
peer.acceptFrame(); // SYN_STREAM
peer.acceptFrame(); // GOAWAY
peer.acceptFrame(); // RST STREAM
peer.acceptFrame(); // RST_STREAM
peer.play();
// play it back
@@ -840,8 +851,10 @@ public final class SpdyConnectionTest {
@Test public void readTimeoutExpires() throws Exception {
// write the mocking script
peer.acceptFrame(); // SYN STREAM
peer.acceptFrame(); // SYN_STREAM
peer.sendFrame().synReply(0, 1, Arrays.asList("a", "android"));
peer.acceptFrame(); // PING
peer.sendFrame().ping(0, 1);
peer.play();
// play it back
@@ -858,6 +871,7 @@ public final class SpdyConnectionTest {
long elapsedNanos = System.nanoTime() - startNanos;
assertEquals(1000d, TimeUnit.NANOSECONDS.toMillis(elapsedNanos), 200d /* 200ms delta */);
assertEquals(1, connection.openStreamCount());
connection.ping().roundTripTime(); // Prevent the peer from exiting prematurely.
// verify the peer received what was expected
MockSpdyPeer.InFrame synStream = peer.takeFrame();
@@ -866,7 +880,7 @@ public final class SpdyConnectionTest {
@Test public void headers() throws Exception {
// write the mocking script
peer.acceptFrame(); // SYN STREAM
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"));
@@ -888,10 +902,10 @@ public final class SpdyConnectionTest {
@Test public void headersBeforeReply() throws Exception {
// write the mocking script
peer.acceptFrame(); // SYN STREAM
peer.acceptFrame(); // SYN_STREAM
peer.acceptFrame(); // PING
peer.sendFrame().headers(0, 1, Arrays.asList("c", "c3po"));
peer.acceptFrame(); // RST STREAM
peer.acceptFrame(); // RST_STREAM
peer.sendFrame().ping(0, 1);
peer.play();
@@ -902,8 +916,8 @@ public final class SpdyConnectionTest {
try {
stream.getResponseHeaders();
fail();
} catch (IOException e) {
assertEquals("stream was reset: PROTOCOL_ERROR", e.getMessage());
} catch (IOException expected) {
assertEquals("stream was reset: PROTOCOL_ERROR", expected.getMessage());
}
// verify the peer received what was expected
@@ -918,7 +932,7 @@ public final class SpdyConnectionTest {
@Test public void readSendsWindowUpdate() throws Exception {
// Write the mocking script.
peer.acceptFrame(); // SYN STREAM
peer.acceptFrame(); // SYN_STREAM
peer.sendFrame().synReply(0, 1, Arrays.asList("a", "android"));
for (int i = 0; i < 3; i++) {
peer.sendFrame().data(0, 1, new byte[WINDOW_UPDATE_THRESHOLD]);
@@ -954,7 +968,7 @@ public final class SpdyConnectionTest {
@Test public void writeAwaitsWindowUpdate() throws Exception {
// Write the mocking script. This accepts more data frames than necessary!
peer.acceptFrame(); // SYN STREAM
peer.acceptFrame(); // SYN_STREAM
for (int i = 0; i < Settings.DEFAULT_INITIAL_WINDOW_SIZE / 1024; i++) {
peer.acceptFrame(); // DATA
}
@@ -980,6 +994,26 @@ public final class SpdyConnectionTest {
assertEquals(TYPE_DATA, data.type);
}
@Test public void testTruncatedDataFrame() throws Exception {
// write the mocking script
peer.acceptFrame(); // SYN_STREAM
peer.sendFrame().synReply(0, 1, Arrays.asList("a", "android"));
peer.sendTruncatedFrame(8 + 100).data(0, 1, new byte[1024]);
peer.play();
// play it back
SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build();
SpdyStream stream = connection.newStream(Arrays.asList("b", "banana"), true, true);
assertEquals(Arrays.asList("a", "android"), stream.getResponseHeaders());
InputStream in = stream.getInputStream();
try {
Util.readFully(in, new byte[101]);
fail();
} catch (IOException expected) {
assertEquals("stream was reset: PROTOCOL_ERROR", expected.getMessage());
}
}
private void writeAndClose(SpdyStream stream, String data) throws IOException {
OutputStream out = stream.getOutputStream();
out.write(data.getBytes("UTF-8"));