mirror of
https://github.com/square/okhttp.git
synced 2026-01-27 04:22:07 +03:00
Merge pull request #80 from square/jwilson/spdy3
Upgrade from spdy/2 to spdy/3.
This commit is contained in:
@@ -65,11 +65,11 @@ import javax.net.ssl.SSLSocket;
|
||||
*/
|
||||
public final class Connection implements Closeable {
|
||||
private static final byte[] NPN_PROTOCOLS = new byte[] {
|
||||
6, 's', 'p', 'd', 'y', '/', '2',
|
||||
6, 's', 'p', 'd', 'y', '/', '3',
|
||||
8, 'h', 't', 't', 'p', '/', '1', '.', '1',
|
||||
};
|
||||
private static final byte[] SPDY2 = new byte[] {
|
||||
's', 'p', 'd', 'y', '/', '2',
|
||||
private static final byte[] SPDY3 = new byte[] {
|
||||
's', 'p', 'd', 'y', '/', '3',
|
||||
};
|
||||
private static final byte[] HTTP_11 = new byte[] {
|
||||
'h', 't', 't', 'p', '/', '1', '.', '1',
|
||||
@@ -159,7 +159,7 @@ public final class Connection implements Closeable {
|
||||
byte[] selectedProtocol;
|
||||
if (modernTls
|
||||
&& (selectedProtocol = platform.getNpnSelectedProtocol(sslSocket)) != null) {
|
||||
if (Arrays.equals(selectedProtocol, SPDY2)) {
|
||||
if (Arrays.equals(selectedProtocol, SPDY3)) {
|
||||
sslSocket.setSoTimeout(0); // SPDY timeouts are set per-stream.
|
||||
spdyConnection = new SpdyConnection.Builder(true, in, out).build();
|
||||
} else if (!Arrays.equals(selectedProtocol, HTTP_11)) {
|
||||
|
||||
@@ -80,26 +80,30 @@ public final class ConnectionPool {
|
||||
List<Connection> connections = connectionPool.get(address);
|
||||
while (connections != null) {
|
||||
Connection connection = connections.get(connections.size() - 1);
|
||||
if (!connection.isSpdy()) {
|
||||
boolean usable = connection.isEligibleForRecycling();
|
||||
if (usable && !connection.isSpdy()) {
|
||||
try {
|
||||
Platform.get().tagSocket(connection.getSocket());
|
||||
} catch (SocketException e) {
|
||||
// When unable to tag, skip recycling and close
|
||||
Platform.get().logW("Unable to tagSocket(): " + e);
|
||||
usable = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!connection.isSpdy() || !usable) {
|
||||
connections.remove(connections.size() - 1);
|
||||
if (connections.isEmpty()) {
|
||||
connectionPool.remove(address);
|
||||
connections = null;
|
||||
}
|
||||
}
|
||||
if (connections.isEmpty()) {
|
||||
connectionPool.remove(address);
|
||||
connections = null;
|
||||
}
|
||||
if (!connection.isEligibleForRecycling()) {
|
||||
|
||||
if (usable) {
|
||||
return connection;
|
||||
} else {
|
||||
Util.closeQuietly(connection);
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
Platform.get().tagSocket(connection.getSocket());
|
||||
} catch (SocketException e) {
|
||||
// When unable to tag, skip recycling and close
|
||||
Platform.get().logW("Unable to tagSocket(): " + e);
|
||||
Util.closeQuietly(connection);
|
||||
continue;
|
||||
}
|
||||
return connection;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -125,30 +125,33 @@ public final class RawHeaders {
|
||||
String version = null;
|
||||
for (int i = 0; i < namesAndValues.size(); i += 2) {
|
||||
String name = namesAndValues.get(i);
|
||||
if ("status".equals(name)) {
|
||||
if (":status".equals(name)) {
|
||||
status = namesAndValues.get(i + 1);
|
||||
} else if ("version".equals(name)) {
|
||||
} else if (":version".equals(name)) {
|
||||
version = namesAndValues.get(i + 1);
|
||||
}
|
||||
}
|
||||
if (status == null || version == null) {
|
||||
throw new ProtocolException("Expected 'status' and 'version' headers not present");
|
||||
throw new ProtocolException("Expected ':status' and ':version' headers not present");
|
||||
}
|
||||
setStatusLine(version + " " + status);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param method like "GET", "POST", "HEAD", etc.
|
||||
* @param scheme like "https"
|
||||
* @param url like "/foo/bar.html"
|
||||
* @param path like "/foo/bar.html"
|
||||
* @param version like "HTTP/1.1"
|
||||
* @param host like "www.android.com:1234"
|
||||
* @param scheme like "https"
|
||||
*/
|
||||
public void addSpdyRequestHeaders(String method, String scheme, String url, String version) {
|
||||
public void addSpdyRequestHeaders(
|
||||
String method, String path, String version, String host, String scheme) {
|
||||
// TODO: populate the statusLine for the client's benefit?
|
||||
add("method", method);
|
||||
add("scheme", scheme);
|
||||
add("url", url);
|
||||
add("version", version);
|
||||
add(":method", method);
|
||||
add(":scheme", scheme);
|
||||
add(":path", path);
|
||||
add(":version", version);
|
||||
add(":host", host);
|
||||
}
|
||||
|
||||
public String getStatusLine() {
|
||||
@@ -393,8 +396,9 @@ public final class RawHeaders {
|
||||
throw new IllegalArgumentException("Unexpected header: " + name + ": " + value);
|
||||
}
|
||||
|
||||
// Drop headers that are ignored when layering HTTP over SPDY.
|
||||
if (name.equals("connection") || name.equals("accept-encoding")) {
|
||||
// Drop headers that are forbidden when layering HTTP over SPDY.
|
||||
if (name.equals("connection") || name.equals("host") || name.equals("keep-alive")
|
||||
|| name.equals("proxy-connection") || name.equals("transfer-encoding")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.CacheRequest;
|
||||
import java.net.URL;
|
||||
import java.util.List;
|
||||
|
||||
public final class SpdyTransport implements Transport {
|
||||
@@ -49,8 +50,9 @@ public final class SpdyTransport implements Transport {
|
||||
}
|
||||
RawHeaders requestHeaders = httpEngine.requestHeaders.getHeaders();
|
||||
String version = httpEngine.connection.getHttpMinorVersion() == 1 ? "HTTP/1.1" : "HTTP/1.0";
|
||||
requestHeaders.addSpdyRequestHeaders(httpEngine.method, httpEngine.uri.getScheme(),
|
||||
HttpEngine.requestPath(httpEngine.policy.getURL()), version);
|
||||
URL url = httpEngine.policy.getURL();
|
||||
requestHeaders.addSpdyRequestHeaders(httpEngine.method, HttpEngine.requestPath(url),
|
||||
version, HttpEngine.getOriginAddress(url), httpEngine.uri.getScheme());
|
||||
boolean hasRequestBody = httpEngine.hasRequestBody();
|
||||
boolean hasResponseBody = true;
|
||||
stream = spdyConnection.newStream(requestHeaders.toNameValueBlock(),
|
||||
@@ -67,7 +69,6 @@ public final class SpdyTransport implements Transport {
|
||||
}
|
||||
|
||||
@Override public ResponseHeaders readResponseHeaders() throws IOException {
|
||||
// TODO: fix the SPDY implementation so this throws a (buffered) IOException
|
||||
List<String> nameValueBlock = stream.getResponseHeaders();
|
||||
RawHeaders rawHeaders = RawHeaders.fromNameValueBlock(nameValueBlock);
|
||||
rawHeaders.computeResponseStatusLineFromSpdyHeaders();
|
||||
|
||||
@@ -38,8 +38,10 @@ final class Settings {
|
||||
static final int DOWNLOAD_RETRANS_RATE = 0x6;
|
||||
/** Window size in bytes. */
|
||||
static final int INITIAL_WINDOW_SIZE = 0x7;
|
||||
/** Window size in bytes. */
|
||||
static final int CLIENT_CERTIFICATE_VECTOR_SIZE = 0x8;
|
||||
/** Total number of settings. */
|
||||
static final int COUNT = 0x8;
|
||||
static final int COUNT = 0x9;
|
||||
|
||||
/** Bitfield of which flags that values. */
|
||||
private int set;
|
||||
@@ -141,6 +143,11 @@ final class Settings {
|
||||
return (bit & set) != 0 ? values[INITIAL_WINDOW_SIZE] : defaultValue;
|
||||
}
|
||||
|
||||
int getClientCertificateVectorSize(int defaultValue) {
|
||||
int bit = 1 << CLIENT_CERTIFICATE_VECTOR_SIZE;
|
||||
return (bit & set) != 0 ? values[CLIENT_CERTIFICATE_VECTOR_SIZE] : defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this user agent should use this setting in future SPDY
|
||||
* connections to the same host.
|
||||
|
||||
@@ -70,7 +70,13 @@ public final class SpdyConnection implements Closeable {
|
||||
static final int TYPE_PING = 0x6;
|
||||
static final int TYPE_GOAWAY = 0x7;
|
||||
static final int TYPE_HEADERS = 0x8;
|
||||
static final int VERSION = 2;
|
||||
static final int TYPE_WINDOW_UPDATE = 0x9;
|
||||
static final int TYPE_CREDENTIAL = 0x10;
|
||||
static final int VERSION = 3;
|
||||
|
||||
static final int GOAWAY_OK = 0;
|
||||
static final int GOAWAY_PROTOCOL_ERROR = 1;
|
||||
static final int GOAWAY_INTERNAL_ERROR = 2;
|
||||
|
||||
/**
|
||||
* True if this peer initiated the connection.
|
||||
@@ -147,8 +153,9 @@ public final class SpdyConnection implements Closeable {
|
||||
public SpdyStream newStream(List<String> requestHeaders, boolean out, boolean in)
|
||||
throws IOException {
|
||||
int flags = (out ? 0 : FLAG_FIN) | (in ? 0 : FLAG_UNIDIRECTIONAL);
|
||||
int associatedStreamId = 0; // TODO: permit the caller to specify an associated stream.
|
||||
int priority = 0; // TODO: permit the caller to specify a priority.
|
||||
int associatedStreamId = 0; // TODO: permit the caller to specify an associated stream?
|
||||
int priority = 0; // TODO: permit the caller to specify a priority?
|
||||
int slot = 0; // TODO: permit the caller to specify a slot?
|
||||
SpdyStream stream;
|
||||
int streamId;
|
||||
|
||||
@@ -159,13 +166,14 @@ public final class SpdyConnection implements Closeable {
|
||||
}
|
||||
streamId = nextStreamId;
|
||||
nextStreamId += 2;
|
||||
stream = new SpdyStream(streamId, this, requestHeaders, flags);
|
||||
stream = new SpdyStream(streamId, this, flags, priority, slot, requestHeaders);
|
||||
if (stream.isOpen()) {
|
||||
streams.put(streamId, stream);
|
||||
}
|
||||
}
|
||||
|
||||
spdyWriter.synStream(flags, streamId, associatedStreamId, priority, requestHeaders);
|
||||
spdyWriter.synStream(flags, streamId, associatedStreamId, priority, slot,
|
||||
requestHeaders);
|
||||
}
|
||||
|
||||
return stream;
|
||||
@@ -194,7 +202,7 @@ public final class SpdyConnection implements Closeable {
|
||||
}
|
||||
|
||||
void writeSynReset(int streamId, int statusCode) throws IOException {
|
||||
spdyWriter.synReset(streamId, statusCode);
|
||||
spdyWriter.rstStream(streamId, statusCode);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -253,13 +261,27 @@ public final class SpdyConnection implements Closeable {
|
||||
}
|
||||
}
|
||||
|
||||
private void shutdownLater(final int statusCode) {
|
||||
writeExecutor.execute(new Runnable() {
|
||||
@Override public void run() {
|
||||
try {
|
||||
shutdown(statusCode);
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Degrades this connection such that new streams can neither be created
|
||||
* locally, nor accepted from the remote peer. Existing streams are not
|
||||
* impacted. This is intended to permit an endpoint to gracefully stop
|
||||
* accepting new requests without harming previously established streams.
|
||||
*
|
||||
* @param statusCode one of {@link #GOAWAY_OK}, {@link
|
||||
* #GOAWAY_INTERNAL_ERROR} or {@link #GOAWAY_PROTOCOL_ERROR}.
|
||||
*/
|
||||
public void shutdown() throws IOException {
|
||||
public void shutdown(int statusCode) throws IOException {
|
||||
synchronized (spdyWriter) {
|
||||
int lastGoodStreamId;
|
||||
synchronized (this) {
|
||||
@@ -269,7 +291,7 @@ public final class SpdyConnection implements Closeable {
|
||||
shutdown = true;
|
||||
lastGoodStreamId = this.lastGoodStreamId;
|
||||
}
|
||||
spdyWriter.goAway(0, lastGoodStreamId);
|
||||
spdyWriter.goAway(0, lastGoodStreamId, statusCode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,7 +301,7 @@ public final class SpdyConnection implements Closeable {
|
||||
* internal executor services.
|
||||
*/
|
||||
@Override public void close() throws IOException {
|
||||
shutdown();
|
||||
shutdown(GOAWAY_OK);
|
||||
|
||||
SpdyStream[] streamsToClose = null;
|
||||
Ping[] pingsToCancel = null;
|
||||
@@ -354,6 +376,8 @@ public final class SpdyConnection implements Closeable {
|
||||
try {
|
||||
while (spdyReader.nextFrame(this)) {
|
||||
}
|
||||
} catch (ProtocolException e) {
|
||||
shutdownLater(GOAWAY_PROTOCOL_ERROR);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
@@ -381,9 +405,9 @@ public final class SpdyConnection implements Closeable {
|
||||
}
|
||||
|
||||
@Override public void synStream(int flags, int streamId, int associatedStreamId,
|
||||
int priority, List<String> nameValueBlock) {
|
||||
int priority, int slot, List<String> nameValueBlock) {
|
||||
final SpdyStream synStream = new SpdyStream(streamId, SpdyConnection.this,
|
||||
nameValueBlock, flags);
|
||||
flags, priority, slot, nameValueBlock);
|
||||
final SpdyStream previous;
|
||||
synchronized (SpdyConnection.this) {
|
||||
if (shutdown) {
|
||||
@@ -421,7 +445,7 @@ public final class SpdyConnection implements Closeable {
|
||||
replyStream.receiveFin();
|
||||
}
|
||||
} catch (ProtocolException e) {
|
||||
replyStream.closeLater(SpdyStream.RST_PROTOCOL_ERROR);
|
||||
replyStream.closeLater(SpdyStream.RST_STREAM_IN_USE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -470,7 +494,7 @@ public final class SpdyConnection implements Closeable {
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void goAway(int flags, int lastGoodStreamId) {
|
||||
@Override public void goAway(int flags, int lastGoodStreamId, int statusCode) {
|
||||
synchronized (SpdyConnection.this) {
|
||||
shutdown = true;
|
||||
|
||||
@@ -486,5 +510,9 @@ public final class SpdyConnection implements Closeable {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void windowUpdate(int flags, int streamId, int deltaWindowSize) {
|
||||
// TODO
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import java.io.Closeable;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.ProtocolException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
@@ -33,28 +33,39 @@ import java.util.zip.InflaterInputStream;
|
||||
* Read version 2 SPDY frames.
|
||||
*/
|
||||
final class SpdyReader implements Closeable {
|
||||
private static final String DICTIONARY_STRING = ""
|
||||
+ "optionsgetheadpostputdeletetraceacceptaccept-charsetaccept-encodingaccept-"
|
||||
+ "languageauthorizationexpectfromhostif-modified-sinceif-matchif-none-matchi"
|
||||
+ "f-rangeif-unmodifiedsincemax-forwardsproxy-authorizationrangerefererteuser"
|
||||
+ "-agent10010120020120220320420520630030130230330430530630740040140240340440"
|
||||
+ "5406407408409410411412413414415416417500501502503504505accept-rangesageeta"
|
||||
+ "glocationproxy-authenticatepublicretry-afterservervarywarningwww-authentic"
|
||||
+ "ateallowcontent-basecontent-encodingcache-controlconnectiondatetrailertran"
|
||||
+ "sfer-encodingupgradeviawarningcontent-languagecontent-lengthcontent-locati"
|
||||
+ "oncontent-md5content-rangecontent-typeetagexpireslast-modifiedset-cookieMo"
|
||||
+ "ndayTuesdayWednesdayThursdayFridaySaturdaySundayJanFebMarAprMayJunJulAugSe"
|
||||
+ "pOctNovDecchunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplic"
|
||||
+ "ation/xhtmltext/plainpublicmax-agecharset=iso-8859-1utf-8gzipdeflateHTTP/1"
|
||||
+ ".1statusversionurl\0";
|
||||
public static final byte[] DICTIONARY;
|
||||
static {
|
||||
try {
|
||||
DICTIONARY = DICTIONARY_STRING.getBytes("UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
}
|
||||
static final byte[] DICTIONARY = ("\u0000\u0000\u0000\u0007options\u0000\u0000\u0000\u0004hea"
|
||||
+ "d\u0000\u0000\u0000\u0004post\u0000\u0000\u0000\u0003put\u0000\u0000\u0000\u0006dele"
|
||||
+ "te\u0000\u0000\u0000\u0005trace\u0000\u0000\u0000\u0006accept\u0000\u0000\u0000"
|
||||
+ "\u000Eaccept-charset\u0000\u0000\u0000\u000Faccept-encoding\u0000\u0000\u0000\u000Fa"
|
||||
+ "ccept-language\u0000\u0000\u0000\raccept-ranges\u0000\u0000\u0000\u0003age\u0000"
|
||||
+ "\u0000\u0000\u0005allow\u0000\u0000\u0000\rauthorization\u0000\u0000\u0000\rcache-co"
|
||||
+ "ntrol\u0000\u0000\u0000\nconnection\u0000\u0000\u0000\fcontent-base\u0000\u0000"
|
||||
+ "\u0000\u0010content-encoding\u0000\u0000\u0000\u0010content-language\u0000\u0000"
|
||||
+ "\u0000\u000Econtent-length\u0000\u0000\u0000\u0010content-location\u0000\u0000\u0000"
|
||||
+ "\u000Bcontent-md5\u0000\u0000\u0000\rcontent-range\u0000\u0000\u0000\fcontent-type"
|
||||
+ "\u0000\u0000\u0000\u0004date\u0000\u0000\u0000\u0004etag\u0000\u0000\u0000\u0006expe"
|
||||
+ "ct\u0000\u0000\u0000\u0007expires\u0000\u0000\u0000\u0004from\u0000\u0000\u0000"
|
||||
+ "\u0004host\u0000\u0000\u0000\bif-match\u0000\u0000\u0000\u0011if-modified-since"
|
||||
+ "\u0000\u0000\u0000\rif-none-match\u0000\u0000\u0000\bif-range\u0000\u0000\u0000"
|
||||
+ "\u0013if-unmodified-since\u0000\u0000\u0000\rlast-modified\u0000\u0000\u0000\blocati"
|
||||
+ "on\u0000\u0000\u0000\fmax-forwards\u0000\u0000\u0000\u0006pragma\u0000\u0000\u0000"
|
||||
+ "\u0012proxy-authenticate\u0000\u0000\u0000\u0013proxy-authorization\u0000\u0000"
|
||||
+ "\u0000\u0005range\u0000\u0000\u0000\u0007referer\u0000\u0000\u0000\u000Bretry-after"
|
||||
+ "\u0000\u0000\u0000\u0006server\u0000\u0000\u0000\u0002te\u0000\u0000\u0000\u0007trai"
|
||||
+ "ler\u0000\u0000\u0000\u0011transfer-encoding\u0000\u0000\u0000\u0007upgrade\u0000"
|
||||
+ "\u0000\u0000\nuser-agent\u0000\u0000\u0000\u0004vary\u0000\u0000\u0000\u0003via"
|
||||
+ "\u0000\u0000\u0000\u0007warning\u0000\u0000\u0000\u0010www-authenticate\u0000\u0000"
|
||||
+ "\u0000\u0006method\u0000\u0000\u0000\u0003get\u0000\u0000\u0000\u0006status\u0000"
|
||||
+ "\u0000\u0000\u0006200 OK\u0000\u0000\u0000\u0007version\u0000\u0000\u0000\bHTTP/1.1"
|
||||
+ "\u0000\u0000\u0000\u0003url\u0000\u0000\u0000\u0006public\u0000\u0000\u0000\nset-coo"
|
||||
+ "kie\u0000\u0000\u0000\nkeep-alive\u0000\u0000\u0000\u0006origin100101201202205206300"
|
||||
+ "302303304305306307402405406407408409410411412413414415416417502504505203 Non-Authori"
|
||||
+ "tative Information204 No Content301 Moved Permanently400 Bad Request401 Unauthorized"
|
||||
+ "403 Forbidden404 Not Found500 Internal Server Error501 Not Implemented503 Service Un"
|
||||
+ "availableJan Feb Mar Apr May Jun Jul Aug Sept Oct Nov Dec 00:00:00 Mon, Tue, Wed, Th"
|
||||
+ "u, Fri, Sat, Sun, GMTchunked,text/html,image/png,image/jpg,image/gif,application/xml"
|
||||
+ ",application/xhtml+xml,text/plain,text/javascript,publicprivatemax-age=gzip,deflate,"
|
||||
+ "sdchcharset=utf-8charset=iso-8859-1,utf-,*,enq=0.").getBytes(Util.UTF_8);
|
||||
|
||||
private final DataInputStream in;
|
||||
private final DataInputStream nameValueBlockIn;
|
||||
@@ -86,6 +97,10 @@ final class SpdyReader implements Closeable {
|
||||
int version = (w1 & 0x7fff0000) >>> 16;
|
||||
int type = (w1 & 0xffff);
|
||||
|
||||
if (version != 3) {
|
||||
throw new ProtocolException("version != 3: " + version);
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case SpdyConnection.TYPE_SYN_STREAM:
|
||||
readSynStream(handler, flags, length);
|
||||
@@ -120,6 +135,14 @@ final class SpdyReader implements Closeable {
|
||||
readHeaders(handler, flags, length);
|
||||
return true;
|
||||
|
||||
case SpdyConnection.TYPE_WINDOW_UPDATE:
|
||||
readWindowUpdate(handler, flags, length);
|
||||
return true;
|
||||
|
||||
case SpdyConnection.TYPE_CREDENTIAL:
|
||||
Util.skipByReading(in, length);
|
||||
throw new UnsupportedOperationException("TODO"); // TODO: implement
|
||||
|
||||
default:
|
||||
throw new IOException("Unexpected frame");
|
||||
}
|
||||
@@ -136,17 +159,16 @@ final class SpdyReader implements Closeable {
|
||||
int s3 = in.readShort();
|
||||
int streamId = w1 & 0x7fffffff;
|
||||
int associatedStreamId = w2 & 0x7fffffff;
|
||||
int priority = s3 & 0xc000 >>> 14;
|
||||
// int unused = s3 & 0x3fff;
|
||||
int priority = (s3 & 0xe000) >>> 13;
|
||||
int slot = s3 & 0xff;
|
||||
List<String> nameValueBlock = readNameValueBlock(length - 10);
|
||||
handler.synStream(flags, streamId, associatedStreamId, priority, nameValueBlock);
|
||||
handler.synStream(flags, streamId, associatedStreamId, priority, slot, nameValueBlock);
|
||||
}
|
||||
|
||||
private void readSynReply(Handler handler, int flags, int length) throws IOException {
|
||||
int w1 = in.readInt();
|
||||
in.readShort(); // unused
|
||||
int streamId = w1 & 0x7fffffff;
|
||||
List<String> nameValueBlock = readNameValueBlock(length - 6);
|
||||
List<String> nameValueBlock = readNameValueBlock(length - 4);
|
||||
handler.synReply(flags, streamId, nameValueBlock);
|
||||
}
|
||||
|
||||
@@ -159,12 +181,19 @@ final class SpdyReader implements Closeable {
|
||||
|
||||
private void readHeaders(Handler handler, int flags, int length) throws IOException {
|
||||
int w1 = in.readInt();
|
||||
in.readShort(); // unused
|
||||
int streamId = w1 & 0x7fffffff;
|
||||
List<String> nameValueBlock = readNameValueBlock(length - 6);
|
||||
List<String> nameValueBlock = readNameValueBlock(length - 4);
|
||||
handler.headers(flags, streamId, nameValueBlock);
|
||||
}
|
||||
|
||||
private void readWindowUpdate(Handler handler, int flags, int length) throws IOException {
|
||||
int w1 = in.readInt();
|
||||
int w2 = in.readInt();
|
||||
int streamId = w1 & 0x7fffffff;
|
||||
int deltaWindowSize = w2 & 0x7fffffff;
|
||||
handler.windowUpdate(flags, streamId, deltaWindowSize);
|
||||
}
|
||||
|
||||
private DataInputStream newNameValueBlockStream() {
|
||||
// Limit the inflater input stream to only those bytes in the Name/Value block.
|
||||
final InputStream throttleStream = new InputStream() {
|
||||
@@ -203,7 +232,7 @@ final class SpdyReader implements Closeable {
|
||||
private List<String> readNameValueBlock(int length) throws IOException {
|
||||
this.compressedLimit += length;
|
||||
try {
|
||||
int numberOfPairs = nameValueBlockIn.readShort();
|
||||
int numberOfPairs = nameValueBlockIn.readInt();
|
||||
List<String> entries = new ArrayList<String>(numberOfPairs * 2);
|
||||
for (int i = 0; i < numberOfPairs; i++) {
|
||||
String name = readString();
|
||||
@@ -226,7 +255,7 @@ final class SpdyReader implements Closeable {
|
||||
}
|
||||
|
||||
private String readString() throws DataFormatException, IOException {
|
||||
int length = nameValueBlockIn.readShort();
|
||||
int length = nameValueBlockIn.readInt();
|
||||
byte[] bytes = new byte[length];
|
||||
Util.readFully(nameValueBlockIn, bytes);
|
||||
return new String(bytes, 0, length, "UTF-8");
|
||||
@@ -239,9 +268,10 @@ final class SpdyReader implements Closeable {
|
||||
}
|
||||
|
||||
private void readGoAway(Handler handler, int flags, int length) throws IOException {
|
||||
if (length != 4) throw ioException("TYPE_GOAWAY length: %d != 4", length);
|
||||
if (length != 8) throw ioException("TYPE_GOAWAY length: %d != 8", length);
|
||||
int lastGoodStreamId = in.readInt() & 0x7fffffff;
|
||||
handler.goAway(flags, lastGoodStreamId);
|
||||
int statusCode = in.readInt();
|
||||
handler.goAway(flags, lastGoodStreamId, statusCode);
|
||||
}
|
||||
|
||||
private void readSettings(Handler handler, int flags, int length) throws IOException {
|
||||
@@ -274,13 +304,14 @@ final class SpdyReader implements Closeable {
|
||||
public interface Handler {
|
||||
void data(int flags, int streamId, InputStream in, int length) throws IOException;
|
||||
void synStream(int flags, int streamId, int associatedStreamId, int priority,
|
||||
List<String> nameValueBlock);
|
||||
int slot, List<String> nameValueBlock);
|
||||
void synReply(int flags, int streamId, List<String> nameValueBlock) throws IOException;
|
||||
void headers(int flags, int streamId, List<String> 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);
|
||||
void goAway(int flags, int lastGoodStreamId, int statusCode);
|
||||
void windowUpdate(int flags, int streamId, int deltaWindowSize);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,10 @@ public final class SpdyStream {
|
||||
"CANCEL",
|
||||
"INTERNAL_ERROR",
|
||||
"FLOW_CONTROL_ERROR",
|
||||
"STREAM_IN_USE",
|
||||
"STREAM_ALREADY_CLOSED",
|
||||
"INVALID_CREDENTIALS",
|
||||
"FRAME_TOO_LARGE",
|
||||
};
|
||||
|
||||
public static final int RST_PROTOCOL_ERROR = 1;
|
||||
@@ -59,9 +63,15 @@ public final class SpdyStream {
|
||||
public static final int RST_CANCEL = 5;
|
||||
public static final int RST_INTERNAL_ERROR = 6;
|
||||
public static final int RST_FLOW_CONTROL_ERROR = 7;
|
||||
public static final int RST_STREAM_IN_USE = 8;
|
||||
public static final int RST_STREAM_ALREADY_CLOSED = 9;
|
||||
public static final int RST_INVALID_CREDENTIALS = 10;
|
||||
public static final int RST_FRAME_TOO_LARGE = 11;
|
||||
|
||||
private final int id;
|
||||
private final SpdyConnection connection;
|
||||
private final int priority;
|
||||
private final int slot;
|
||||
private long readTimeoutMillis = 0;
|
||||
|
||||
/** Headers sent by the stream initiator. Immutable and non null. */
|
||||
@@ -80,9 +90,12 @@ public final class SpdyStream {
|
||||
*/
|
||||
private int rstStatusCode = -1;
|
||||
|
||||
SpdyStream(int id, SpdyConnection connection, List<String> requestHeaders, int flags) {
|
||||
SpdyStream(int id, SpdyConnection connection, int flags, int priority, int slot,
|
||||
List<String> requestHeaders) {
|
||||
this.id = id;
|
||||
this.connection = connection;
|
||||
this.priority = priority;
|
||||
this.slot = slot;
|
||||
this.requestHeaders = requestHeaders;
|
||||
|
||||
if (isLocallyInitiated()) {
|
||||
@@ -316,6 +329,14 @@ public final class SpdyStream {
|
||||
: Integer.toString(rstStatusCode);
|
||||
}
|
||||
|
||||
int getPriority() {
|
||||
return priority;
|
||||
}
|
||||
|
||||
int getSlot() {
|
||||
return slot;
|
||||
}
|
||||
|
||||
/**
|
||||
* An input stream that reads the incoming data frames of a stream. Although
|
||||
* this class uses synchronization to safely receive incoming data frames,
|
||||
|
||||
@@ -45,7 +45,7 @@ final class SpdyWriter implements Closeable {
|
||||
}
|
||||
|
||||
public synchronized void synStream(int flags, int streamId, int associatedStreamId,
|
||||
int priority, List<String> nameValueBlock) throws IOException {
|
||||
int priority, int slot, List<String> nameValueBlock) throws IOException {
|
||||
writeNameValueBlockToBuffer(nameValueBlock);
|
||||
int length = 10 + nameValueBlockBuffer.size();
|
||||
int type = SpdyConnection.TYPE_SYN_STREAM;
|
||||
@@ -55,7 +55,7 @@ final class SpdyWriter implements Closeable {
|
||||
out.writeInt((flags & 0xff) << 24 | length & 0xffffff);
|
||||
out.writeInt(streamId & 0x7fffffff);
|
||||
out.writeInt(associatedStreamId & 0x7fffffff);
|
||||
out.writeShort((priority & 0x3) << 30 | (unused & 0x3FFF) << 16);
|
||||
out.writeShort((priority & 0x7) << 13 | (unused & 0x1f) << 8 | (slot & 0xff));
|
||||
nameValueBlockBuffer.writeTo(out);
|
||||
out.flush();
|
||||
}
|
||||
@@ -64,13 +64,11 @@ final class SpdyWriter implements Closeable {
|
||||
int flags, int streamId, List<String> nameValueBlock) throws IOException {
|
||||
writeNameValueBlockToBuffer(nameValueBlock);
|
||||
int type = SpdyConnection.TYPE_SYN_REPLY;
|
||||
int length = nameValueBlockBuffer.size() + 6;
|
||||
int unused = 0;
|
||||
int length = nameValueBlockBuffer.size() + 4;
|
||||
|
||||
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();
|
||||
}
|
||||
@@ -79,18 +77,16 @@ final class SpdyWriter implements Closeable {
|
||||
int flags, int streamId, List<String> nameValueBlock) throws IOException {
|
||||
writeNameValueBlockToBuffer(nameValueBlock);
|
||||
int type = SpdyConnection.TYPE_HEADERS;
|
||||
int length = nameValueBlockBuffer.size() + 6;
|
||||
int unused = 0;
|
||||
int length = nameValueBlockBuffer.size() + 4;
|
||||
|
||||
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 {
|
||||
public synchronized void rstStream(int streamId, int statusCode) throws IOException {
|
||||
int flags = 0;
|
||||
int type = SpdyConnection.TYPE_RST_STREAM;
|
||||
int length = 8;
|
||||
@@ -112,9 +108,9 @@ final class SpdyWriter implements Closeable {
|
||||
private void writeNameValueBlockToBuffer(List<String> nameValueBlock) throws IOException {
|
||||
nameValueBlockBuffer.reset();
|
||||
int numberOfPairs = nameValueBlock.size() / 2;
|
||||
nameValueBlockOut.writeShort(numberOfPairs);
|
||||
nameValueBlockOut.writeInt(numberOfPairs);
|
||||
for (String s : nameValueBlock) {
|
||||
nameValueBlockOut.writeShort(s.length());
|
||||
nameValueBlockOut.writeInt(s.length());
|
||||
nameValueBlockOut.write(s.getBytes("UTF-8"));
|
||||
}
|
||||
nameValueBlockOut.flush();
|
||||
@@ -158,15 +154,22 @@ final class SpdyWriter implements Closeable {
|
||||
out.flush();
|
||||
}
|
||||
|
||||
public synchronized void goAway(int flags, int lastGoodStreamId) throws IOException {
|
||||
public synchronized void goAway(int flags, int lastGoodStreamId, int statusCode)
|
||||
throws IOException {
|
||||
int type = SpdyConnection.TYPE_GOAWAY;
|
||||
int length = 4;
|
||||
int length = 8;
|
||||
out.writeInt(0x80000000 | (SpdyConnection.VERSION & 0x7fff) << 16 | type & 0xffff);
|
||||
out.writeInt((flags & 0xff) << 24 | length & 0xffffff);
|
||||
out.writeInt(lastGoodStreamId);
|
||||
out.writeInt(statusCode);
|
||||
out.flush();
|
||||
}
|
||||
|
||||
public synchronized void windowUpdate(int flags, int streamId, int deltaWindowSize)
|
||||
throws IOException {
|
||||
throw new UnsupportedOperationException("TODO"); // TODO
|
||||
}
|
||||
|
||||
@Override public void close() throws IOException {
|
||||
Util.closeAll(out, nameValueBlockOut);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
package com.squareup.okhttp.internal.http;
|
||||
|
||||
import com.squareup.okhttp.internal.http.RawHeaders;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
@@ -28,19 +27,20 @@ public final class RawHeadersTest {
|
||||
"no-cache, no-store",
|
||||
"set-cookie",
|
||||
"Cookie1\u0000Cookie2",
|
||||
"status", "200 OK"
|
||||
":status", "200 OK"
|
||||
);
|
||||
// TODO: fromNameValueBlock should synthesize a request status line
|
||||
RawHeaders rawHeaders = RawHeaders.fromNameValueBlock(nameValueBlock);
|
||||
assertEquals("no-cache, no-store", rawHeaders.get("cache-control"));
|
||||
assertEquals("Cookie2", rawHeaders.get("set-cookie"));
|
||||
assertEquals("200 OK", rawHeaders.get("status"));
|
||||
assertEquals("200 OK", rawHeaders.get(":status"));
|
||||
assertEquals("cache-control", rawHeaders.getFieldName(0));
|
||||
assertEquals("no-cache, no-store", rawHeaders.getValue(0));
|
||||
assertEquals("set-cookie", rawHeaders.getFieldName(1));
|
||||
assertEquals("Cookie1", rawHeaders.getValue(1));
|
||||
assertEquals("set-cookie", rawHeaders.getFieldName(2));
|
||||
assertEquals("Cookie2", rawHeaders.getValue(2));
|
||||
assertEquals("status", rawHeaders.getFieldName(3));
|
||||
assertEquals(":status", rawHeaders.getFieldName(3));
|
||||
assertEquals("200 OK", rawHeaders.getValue(3));
|
||||
}
|
||||
|
||||
@@ -49,15 +49,23 @@ public final class RawHeadersTest {
|
||||
rawHeaders.add("cache-control", "no-cache, no-store");
|
||||
rawHeaders.add("set-cookie", "Cookie1");
|
||||
rawHeaders.add("set-cookie", "Cookie2");
|
||||
rawHeaders.add("status", "200 OK");
|
||||
rawHeaders.add(":status", "200 OK");
|
||||
// TODO: fromNameValueBlock should take the status line headers
|
||||
List<String> nameValueBlock = rawHeaders.toNameValueBlock();
|
||||
List<String> expected = Arrays.asList(
|
||||
"cache-control",
|
||||
"no-cache, no-store",
|
||||
"set-cookie",
|
||||
"Cookie1\u0000Cookie2",
|
||||
"status", "200 OK"
|
||||
":status", "200 OK"
|
||||
);
|
||||
assertEquals(expected, nameValueBlock);
|
||||
}
|
||||
|
||||
@Test public void toNameValueBlockDropsForbiddenHeaders() {
|
||||
RawHeaders rawHeaders = new RawHeaders();
|
||||
rawHeaders.add("Connection", "close");
|
||||
rawHeaders.add("Transfer-Encoding", "chunked");
|
||||
assertEquals(Arrays.<String>asList(), rawHeaders.toNameValueBlock());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.squareup.okhttp.internal.spdy;
|
||||
|
||||
import com.squareup.okhttp.internal.Util;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
@@ -34,7 +35,7 @@ import java.util.concurrent.LinkedBlockingQueue;
|
||||
/**
|
||||
* Replays prerecorded outgoing frames and records incoming frames.
|
||||
*/
|
||||
public final class MockSpdyPeer {
|
||||
public final class MockSpdyPeer implements Closeable {
|
||||
private int frameCount = 0;
|
||||
private final ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
|
||||
private final SpdyWriter spdyWriter = new SpdyWriter(bytesOut);
|
||||
@@ -43,6 +44,8 @@ public final class MockSpdyPeer {
|
||||
private int port;
|
||||
private final Executor executor = Executors.newCachedThreadPool(
|
||||
Threads.newThreadFactory("MockSpdyPeer", true));
|
||||
private ServerSocket serverSocket;
|
||||
private Socket socket;
|
||||
|
||||
public void acceptFrame() {
|
||||
frameCount++;
|
||||
@@ -63,13 +66,14 @@ public final class MockSpdyPeer {
|
||||
}
|
||||
|
||||
public void play() throws IOException {
|
||||
final ServerSocket serverSocket = new ServerSocket(0);
|
||||
if (serverSocket != null) throw new IllegalStateException();
|
||||
serverSocket = new ServerSocket(0);
|
||||
serverSocket.setReuseAddress(true);
|
||||
this.port = serverSocket.getLocalPort();
|
||||
executor.execute(new Runnable() {
|
||||
@Override public void run() {
|
||||
try {
|
||||
readAndWriteFrames(serverSocket);
|
||||
readAndWriteFrames();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@@ -77,8 +81,9 @@ public final class MockSpdyPeer {
|
||||
});
|
||||
}
|
||||
|
||||
private void readAndWriteFrames(ServerSocket serverSocket) throws IOException {
|
||||
Socket socket = serverSocket.accept();
|
||||
private void readAndWriteFrames() throws IOException {
|
||||
if (socket != null) throw new IllegalStateException();
|
||||
socket = serverSocket.accept();
|
||||
OutputStream out = socket.getOutputStream();
|
||||
InputStream in = socket.getInputStream();
|
||||
SpdyReader reader = new SpdyReader(in);
|
||||
@@ -118,6 +123,19 @@ public final class MockSpdyPeer {
|
||||
return new Socket("localhost", port);
|
||||
}
|
||||
|
||||
@Override public void close() throws IOException {
|
||||
Socket socket = this.socket;
|
||||
if (socket != null) {
|
||||
socket.close();
|
||||
this.socket = null;
|
||||
}
|
||||
ServerSocket serverSocket = this.serverSocket;
|
||||
if (serverSocket != null) {
|
||||
serverSocket.close();
|
||||
this.serverSocket = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static class OutFrame {
|
||||
private final int sequence;
|
||||
private final int start;
|
||||
@@ -136,7 +154,9 @@ public final class MockSpdyPeer {
|
||||
public int streamId;
|
||||
public int associatedStreamId;
|
||||
public int priority;
|
||||
public int slot;
|
||||
public int statusCode;
|
||||
public int deltaWindowSize;
|
||||
public List<String> nameValueBlock;
|
||||
public byte[] data;
|
||||
public Settings settings;
|
||||
@@ -154,13 +174,14 @@ public final class MockSpdyPeer {
|
||||
}
|
||||
|
||||
@Override public void synStream(int flags, int streamId, int associatedStreamId,
|
||||
int priority, List<String> nameValueBlock) {
|
||||
int priority, int slot, List<String> nameValueBlock) {
|
||||
if (this.type != -1) throw new IllegalStateException();
|
||||
this.type = SpdyConnection.TYPE_SYN_STREAM;
|
||||
this.flags = flags;
|
||||
this.streamId = streamId;
|
||||
this.associatedStreamId = associatedStreamId;
|
||||
this.priority = priority;
|
||||
this.slot = slot;
|
||||
this.nameValueBlock = nameValueBlock;
|
||||
}
|
||||
|
||||
@@ -210,11 +231,20 @@ public final class MockSpdyPeer {
|
||||
this.type = SpdyConnection.TYPE_NOOP;
|
||||
}
|
||||
|
||||
@Override public void goAway(int flags, int lastGoodStreamId) {
|
||||
@Override public void goAway(int flags, int lastGoodStreamId, int statusCode) {
|
||||
if (this.type != -1) throw new IllegalStateException();
|
||||
this.type = SpdyConnection.TYPE_GOAWAY;
|
||||
this.flags = flags;
|
||||
this.streamId = lastGoodStreamId;
|
||||
this.statusCode = statusCode;
|
||||
}
|
||||
|
||||
@Override public void windowUpdate(int flags, int streamId, int deltaWindowSize) {
|
||||
if (this.type != -1) throw new IllegalStateException();
|
||||
this.type = SpdyConnection.TYPE_WINDOW_UPDATE;
|
||||
this.flags = flags;
|
||||
this.streamId = streamId;
|
||||
this.deltaWindowSize = deltaWindowSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
package com.squareup.okhttp.internal.spdy;
|
||||
|
||||
import com.squareup.okhttp.internal.spdy.Settings;
|
||||
import static com.squareup.okhttp.internal.spdy.Settings.DOWNLOAD_BANDWIDTH;
|
||||
import static com.squareup.okhttp.internal.spdy.Settings.DOWNLOAD_RETRANS_RATE;
|
||||
import static com.squareup.okhttp.internal.spdy.Settings.MAX_CONCURRENT_STREAMS;
|
||||
@@ -63,6 +62,10 @@ public final class SettingsTest {
|
||||
assertEquals(-3, settings.getInitialWindowSize(-3));
|
||||
settings.set(Settings.INITIAL_WINDOW_SIZE, 0, 108);
|
||||
assertEquals(108, settings.getInitialWindowSize(-3));
|
||||
|
||||
assertEquals(-3, settings.getClientCertificateVectorSize(-3));
|
||||
settings.set(Settings.CLIENT_CERTIFICATE_VECTOR_SIZE, 0, 117);
|
||||
assertEquals(117, settings.getClientCertificateVectorSize(-3));
|
||||
}
|
||||
|
||||
@Test public void isPersisted() {
|
||||
|
||||
@@ -20,6 +20,8 @@ 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;
|
||||
import static com.squareup.okhttp.internal.spdy.SpdyConnection.FLAG_UNIDIRECTIONAL;
|
||||
import static com.squareup.okhttp.internal.spdy.SpdyConnection.GOAWAY_INTERNAL_ERROR;
|
||||
import static com.squareup.okhttp.internal.spdy.SpdyConnection.GOAWAY_PROTOCOL_ERROR;
|
||||
import static com.squareup.okhttp.internal.spdy.SpdyConnection.TYPE_DATA;
|
||||
import static com.squareup.okhttp.internal.spdy.SpdyConnection.TYPE_GOAWAY;
|
||||
import static com.squareup.okhttp.internal.spdy.SpdyConnection.TYPE_NOOP;
|
||||
@@ -31,6 +33,7 @@ import static com.squareup.okhttp.internal.spdy.SpdyStream.RST_FLOW_CONTROL_ERRO
|
||||
import static com.squareup.okhttp.internal.spdy.SpdyStream.RST_INVALID_STREAM;
|
||||
import static com.squareup.okhttp.internal.spdy.SpdyStream.RST_PROTOCOL_ERROR;
|
||||
import static com.squareup.okhttp.internal.spdy.SpdyStream.RST_REFUSED_STREAM;
|
||||
import static com.squareup.okhttp.internal.spdy.SpdyStream.RST_STREAM_IN_USE;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -38,6 +41,7 @@ import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import org.junit.After;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
@@ -52,6 +56,10 @@ public final class SpdyConnectionTest {
|
||||
};
|
||||
private final MockSpdyPeer peer = new MockSpdyPeer();
|
||||
|
||||
@After public void tearDown() throws Exception {
|
||||
peer.close();
|
||||
}
|
||||
|
||||
@Test public void clientCreatesStreamAndServerReplies() throws Exception {
|
||||
// write the mocking script
|
||||
peer.acceptFrame();
|
||||
@@ -113,7 +121,7 @@ public final class SpdyConnectionTest {
|
||||
|
||||
@Test public void serverCreatesStreamAndClientReplies() throws Exception {
|
||||
// write the mocking script
|
||||
peer.sendFrame().synStream(0, 2, 0, 0, Arrays.asList("a", "android"));
|
||||
peer.sendFrame().synStream(0, 2, 0, 5, 129, Arrays.asList("a", "android"));
|
||||
peer.acceptFrame();
|
||||
peer.play();
|
||||
|
||||
@@ -124,6 +132,8 @@ public final class SpdyConnectionTest {
|
||||
receiveCount.incrementAndGet();
|
||||
assertEquals(Arrays.asList("a", "android"), stream.getRequestHeaders());
|
||||
assertEquals(-1, stream.getRstStatusCode());
|
||||
assertEquals(5, stream.getPriority());
|
||||
assertEquals(129, stream.getSlot());
|
||||
stream.reply(Arrays.asList("b", "banana"), true);
|
||||
|
||||
}
|
||||
@@ -143,7 +153,7 @@ public final class SpdyConnectionTest {
|
||||
|
||||
@Test public void replyWithNoData() throws Exception {
|
||||
// write the mocking script
|
||||
peer.sendFrame().synStream(0, 2, 0, 0, Arrays.asList("a", "android"));
|
||||
peer.sendFrame().synStream(0, 2, 0, 0, 0, Arrays.asList("a", "android"));
|
||||
peer.acceptFrame();
|
||||
peer.play();
|
||||
|
||||
@@ -390,7 +400,7 @@ public final class SpdyConnectionTest {
|
||||
@Test public void serverClosesClientOutputStream() throws Exception {
|
||||
// write the mocking script
|
||||
peer.acceptFrame(); // SYN_STREAM
|
||||
peer.sendFrame().synReset(1, SpdyStream.RST_CANCEL);
|
||||
peer.sendFrame().rstStream(1, SpdyStream.RST_CANCEL);
|
||||
peer.acceptFrame(); // PING
|
||||
peer.sendFrame().ping(0, 1);
|
||||
peer.play();
|
||||
@@ -547,7 +557,7 @@ public final class SpdyConnectionTest {
|
||||
stream.getInputStream().read();
|
||||
fail();
|
||||
} catch (IOException e) {
|
||||
assertEquals("stream was reset: PROTOCOL_ERROR", e.getMessage());
|
||||
assertEquals("stream was reset: STREAM_IN_USE", e.getMessage());
|
||||
}
|
||||
|
||||
// verify the peer received what was expected
|
||||
@@ -559,14 +569,14 @@ public final class SpdyConnectionTest {
|
||||
assertEquals(TYPE_RST_STREAM, rstStream.type);
|
||||
assertEquals(1, rstStream.streamId);
|
||||
assertEquals(0, rstStream.flags);
|
||||
assertEquals(RST_PROTOCOL_ERROR, rstStream.statusCode);
|
||||
assertEquals(RST_STREAM_IN_USE, rstStream.statusCode);
|
||||
}
|
||||
|
||||
@Test public void remoteDoubleSynStream() throws Exception {
|
||||
// write the mocking script
|
||||
peer.sendFrame().synStream(0, 2, 0, 0, Arrays.asList("a", "android"));
|
||||
peer.sendFrame().synStream(0, 2, 0, 0, 0, Arrays.asList("a", "android"));
|
||||
peer.acceptFrame();
|
||||
peer.sendFrame().synStream(0, 2, 0, 0, Arrays.asList("b", "banana"));
|
||||
peer.sendFrame().synStream(0, 2, 0, 0, 0, Arrays.asList("b", "banana"));
|
||||
peer.acceptFrame();
|
||||
peer.play();
|
||||
|
||||
@@ -651,7 +661,7 @@ public final class SpdyConnectionTest {
|
||||
@Test public void remoteSendsRefusedStreamBeforeReplyHeaders() throws Exception {
|
||||
// write the mocking script
|
||||
peer.acceptFrame();
|
||||
peer.sendFrame().synReset(1, RST_REFUSED_STREAM);
|
||||
peer.sendFrame().rstStream(1, RST_REFUSED_STREAM);
|
||||
peer.sendFrame().ping(0, 2);
|
||||
peer.acceptFrame();
|
||||
peer.play();
|
||||
@@ -680,7 +690,7 @@ public final class SpdyConnectionTest {
|
||||
// write the mocking script
|
||||
peer.acceptFrame(); // SYN STREAM 1
|
||||
peer.acceptFrame(); // SYN STREAM 3
|
||||
peer.sendFrame().goAway(0, 1);
|
||||
peer.sendFrame().goAway(0, 1, GOAWAY_PROTOCOL_ERROR);
|
||||
peer.acceptFrame(); // PING
|
||||
peer.sendFrame().ping(0, 1);
|
||||
peer.acceptFrame(); // DATA STREAM 1
|
||||
@@ -726,7 +736,7 @@ public final class SpdyConnectionTest {
|
||||
peer.acceptFrame(); // SYN STREAM 1
|
||||
peer.acceptFrame(); // GOAWAY
|
||||
peer.acceptFrame(); // PING
|
||||
peer.sendFrame().synStream(0, 2, 0, 0, Arrays.asList("b", "banana")); // Should be ignored!
|
||||
peer.sendFrame().synStream(0, 2, 0, 0, 0, Arrays.asList("b", "b")); // Should be ignored!
|
||||
peer.sendFrame().ping(0, 1);
|
||||
peer.play();
|
||||
|
||||
@@ -734,7 +744,7 @@ public final class SpdyConnectionTest {
|
||||
SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build();
|
||||
connection.newStream(Arrays.asList("a", "android"), true, true);
|
||||
Ping ping = connection.ping();
|
||||
connection.shutdown();
|
||||
connection.shutdown(GOAWAY_PROTOCOL_ERROR);
|
||||
ping.roundTripTime(); // Ensure that the SYN STREAM has been received.
|
||||
assertEquals(1, connection.openStreamCount());
|
||||
|
||||
@@ -746,6 +756,7 @@ public final class SpdyConnectionTest {
|
||||
MockSpdyPeer.InFrame goaway = peer.takeFrame();
|
||||
assertEquals(TYPE_GOAWAY, goaway.type);
|
||||
assertEquals(0, goaway.streamId);
|
||||
assertEquals(GOAWAY_PROTOCOL_ERROR, goaway.statusCode);
|
||||
}
|
||||
|
||||
@Test public void noPingsAfterShutdown() throws Exception {
|
||||
@@ -755,7 +766,7 @@ public final class SpdyConnectionTest {
|
||||
|
||||
// play it back
|
||||
SpdyConnection connection = new SpdyConnection.Builder(true, peer.openSocket()).build();
|
||||
connection.shutdown();
|
||||
connection.shutdown(GOAWAY_INTERNAL_ERROR);
|
||||
try {
|
||||
connection.ping();
|
||||
fail();
|
||||
@@ -766,6 +777,7 @@ public final class SpdyConnectionTest {
|
||||
// verify the peer received what was expected
|
||||
MockSpdyPeer.InFrame goaway = peer.takeFrame();
|
||||
assertEquals(TYPE_GOAWAY, goaway.type);
|
||||
assertEquals(GOAWAY_INTERNAL_ERROR, goaway.statusCode);
|
||||
}
|
||||
|
||||
@Test public void close() throws Exception {
|
||||
|
||||
@@ -71,7 +71,7 @@ public final class SpdyServer implements IncomingStreamHandler {
|
||||
System.out.println("UNSUPPORTED");
|
||||
}
|
||||
@Override public List<String> protocols() {
|
||||
return Arrays.asList("spdy/2");
|
||||
return Arrays.asList("spdy/3");
|
||||
}
|
||||
@Override public void protocolSelected(String protocol) {
|
||||
System.out.println("PROTOCOL SELECTED: " + protocol);
|
||||
@@ -85,7 +85,7 @@ public final class SpdyServer implements IncomingStreamHandler {
|
||||
String path = null;
|
||||
for (int i = 0; i < requestHeaders.size(); i += 2) {
|
||||
String s = requestHeaders.get(i);
|
||||
if ("url".equals(s)) {
|
||||
if (":path".equals(s)) {
|
||||
path = requestHeaders.get(i + 1);
|
||||
break;
|
||||
}
|
||||
@@ -109,8 +109,8 @@ public final class SpdyServer implements IncomingStreamHandler {
|
||||
|
||||
private void send404(SpdyStream stream, String path) throws IOException {
|
||||
List<String> responseHeaders = Arrays.asList(
|
||||
"status", "404",
|
||||
"version", "HTTP/1.1",
|
||||
":status", "404",
|
||||
":version", "HTTP/1.1",
|
||||
"content-type", "text/plain"
|
||||
);
|
||||
stream.reply(responseHeaders, true);
|
||||
@@ -122,8 +122,8 @@ public final class SpdyServer implements IncomingStreamHandler {
|
||||
|
||||
private void serveDirectory(SpdyStream stream, String[] files) throws IOException {
|
||||
List<String> responseHeaders = Arrays.asList(
|
||||
"status", "200",
|
||||
"version", "HTTP/1.1",
|
||||
":status", "200",
|
||||
":version", "HTTP/1.1",
|
||||
"content-type", "text/html; charset=UTF-8"
|
||||
);
|
||||
stream.reply(responseHeaders, true);
|
||||
@@ -139,8 +139,8 @@ public final class SpdyServer implements IncomingStreamHandler {
|
||||
InputStream in = new FileInputStream(file);
|
||||
byte[] buffer = new byte[8192];
|
||||
stream.reply(Arrays.asList(
|
||||
"status", "200",
|
||||
"version", "HTTP/1.1",
|
||||
":status", "200",
|
||||
":version", "HTTP/1.1",
|
||||
"content-type", contentType(file)
|
||||
), true);
|
||||
OutputStream out = stream.getOutputStream();
|
||||
|
||||
Reference in New Issue
Block a user