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

update http2 to draft 9 and hpack to draft 5

This commit is contained in:
Adrian Cole
2014-01-04 19:33:14 -08:00
parent d02c685a28
commit 68de030b35
16 changed files with 1002 additions and 548 deletions

View File

@@ -74,12 +74,12 @@ public final class MockWebServer {
6, 's', 'p', 'd', 'y', '/', '3',
8, 'h', 't', 't', 'p', '/', '1', '.', '1'
};
private static final byte[] HTTP_20_DRAFT_09 = new byte[] {
'H', 'T', 'T', 'P', '-', 'd', 'r', 'a', 'f', 't', '-', '0', '9', '/', '2', '.', '0'
};
private static final byte[] SPDY3 = new byte[] {
's', 'p', 'd', 'y', '/', '3'
};
private static final byte[] HTTP_20_DRAFT_06 = new byte[] {
'H', 'T', 'T', 'P', '-', 'd', 'r', 'a', 'f', 't', '-', '0', '6', '/', '2', '.', '0'
};
private static final byte[] HTTP_11 = new byte[] {
'h', 't', 't', 'p', '/', '1', '.', '1'
};
@@ -326,8 +326,8 @@ public final class MockWebServer {
byte[] selectedProtocol = Platform.get().getNpnSelectedProtocol(sslSocket);
if (selectedProtocol == null || Arrays.equals(selectedProtocol, HTTP_11)) {
transport = Transport.HTTP_11;
} else if (Arrays.equals(selectedProtocol, HTTP_20_DRAFT_06)) {
transport = Transport.HTTP_20_DRAFT_06;
} else if (Arrays.equals(selectedProtocol, HTTP_20_DRAFT_09)) {
transport = Transport.HTTP_20_DRAFT_09;
} else if (Arrays.equals(selectedProtocol, SPDY3)) {
transport = Transport.SPDY_3;
} else {
@@ -340,14 +340,14 @@ public final class MockWebServer {
socket = raw;
}
if (transport == Transport.SPDY_3 || transport == Transport.HTTP_20_DRAFT_06) {
if (transport == Transport.HTTP_20_DRAFT_09 || transport == Transport.SPDY_3) {
SpdySocketHandler spdySocketHandler = new SpdySocketHandler(socket, transport);
SpdyConnection.Builder builder = new SpdyConnection.Builder(false, socket)
.handler(spdySocketHandler);
if (transport == Transport.SPDY_3) {
builder.spdy3();
if (transport == Transport.HTTP_20_DRAFT_09) {
builder.http20Draft09();
} else {
builder.http20Draft06();
builder.spdy3();
}
SpdyConnection spdyConnection = builder.build();
openSpdyConnections.put(spdyConnection, Boolean.TRUE);
@@ -721,6 +721,6 @@ public final class MockWebServer {
}
enum Transport {
HTTP_11, SPDY_3, HTTP_20_DRAFT_06
HTTP_11, SPDY_3, HTTP_20_DRAFT_09
}
}

View File

@@ -7,25 +7,41 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.List;
import java.util.ListIterator;
/**
* Read and write HPACK v03.
* http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-03
* Read and write HPACK v05.
* http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-05
*/
final class Hpack {
final class HpackDraft05 {
static class HeaderEntry {
private final String name;
private final String value;
// Visible for testing.
static class HeaderEntry implements Cloneable {
final String name;
final String value;
final int size;
boolean referenced = true;
HeaderEntry(String name, String value) {
this.name = name;
this.value = value;
// TODO: This needs to be the size in bytes, not the length in chars.
this.size = 32 + name.length() + value.length();
}
// TODO: This needs to be the length in UTF-8 bytes, not the length in chars.
int length() {
return 32 + name.length() + value.length();
/** Adds name and value, if this entry is referenced. */
void addTo(List<String> out) {
if (!referenced) return;
out.add(name);
out.add(value);
}
@Override public HeaderEntry clone() {
try {
return (HeaderEntry) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
@@ -34,98 +50,97 @@ final class Hpack {
static final int PREFIX_7_BITS = 0x7f;
static final int PREFIX_8_BITS = 0xff;
static final List<HeaderEntry> INITIAL_CLIENT_TO_SERVER_HEADER_TABLE = Arrays.asList(
static final List<HeaderEntry> STATIC_HEADER_TABLE = Arrays.asList(
new HeaderEntry(":authority", ""),
new HeaderEntry(":method", "GET"),
new HeaderEntry(":method", "POST"),
new HeaderEntry(":path", "/"),
new HeaderEntry(":path", "/index.html"),
new HeaderEntry(":scheme", "http"),
new HeaderEntry(":scheme", "https"),
new HeaderEntry(":host", ""),
new HeaderEntry(":path", "/"),
new HeaderEntry(":method", "GET"),
new HeaderEntry("accept", ""),
new HeaderEntry(":status", "200"),
new HeaderEntry(":status", "500"),
new HeaderEntry(":status", "404"),
new HeaderEntry(":status", "403"),
new HeaderEntry(":status", "400"),
new HeaderEntry(":status", "401"),
new HeaderEntry("accept-charset", ""),
new HeaderEntry("accept-encoding", ""),
new HeaderEntry("accept-language", ""),
new HeaderEntry("cookie", ""),
new HeaderEntry("if-modified-since", ""),
new HeaderEntry("user-agent", ""),
new HeaderEntry("referer", ""),
new HeaderEntry("authorization", ""),
new HeaderEntry("allow", ""),
new HeaderEntry("cache-control", ""),
new HeaderEntry("connection", ""),
new HeaderEntry("content-length", ""),
new HeaderEntry("content-type", ""),
new HeaderEntry("date", ""),
new HeaderEntry("expect", ""),
new HeaderEntry("from", ""),
new HeaderEntry("if-match", ""),
new HeaderEntry("if-none-match", ""),
new HeaderEntry("if-range", ""),
new HeaderEntry("if-unmodified-since", ""),
new HeaderEntry("max-forwards", ""),
new HeaderEntry("proxy-authorization", ""),
new HeaderEntry("range", ""),
new HeaderEntry("via", "")
);
static final List<HeaderEntry> INITIAL_SERVER_TO_CLIENT_HEADER_TABLE = Arrays.asList(
new HeaderEntry(":status", "200"),
new HeaderEntry("age", ""),
new HeaderEntry("cache-control", ""),
new HeaderEntry("content-length", ""),
new HeaderEntry("content-type", ""),
new HeaderEntry("date", ""),
new HeaderEntry("etag", ""),
new HeaderEntry("expires", ""),
new HeaderEntry("last-modified", ""),
new HeaderEntry("server", ""),
new HeaderEntry("set-cookie", ""),
new HeaderEntry("vary", ""),
new HeaderEntry("via", ""),
new HeaderEntry("access-control-allow-origin", ""),
new HeaderEntry("accept-ranges", ""),
new HeaderEntry("accept", ""),
new HeaderEntry("access-control-allow-origin", ""),
new HeaderEntry("age", ""),
new HeaderEntry("allow", ""),
new HeaderEntry("connection", ""),
new HeaderEntry("authorization", ""),
new HeaderEntry("cache-control", ""),
new HeaderEntry("content-disposition", ""),
new HeaderEntry("content-encoding", ""),
new HeaderEntry("content-language", ""),
new HeaderEntry("content-length", ""),
new HeaderEntry("content-location", ""),
new HeaderEntry("content-range", ""),
new HeaderEntry("content-type", ""),
new HeaderEntry("cookie", ""),
new HeaderEntry("date", ""),
new HeaderEntry("etag", ""),
new HeaderEntry("expect", ""),
new HeaderEntry("expires", ""),
new HeaderEntry("from", ""),
new HeaderEntry("host", ""),
new HeaderEntry("if-match", ""),
new HeaderEntry("if-modified-since", ""),
new HeaderEntry("if-none-match", ""),
new HeaderEntry("if-range", ""),
new HeaderEntry("if-unmodified-since", ""),
new HeaderEntry("last-modified", ""),
new HeaderEntry("link", ""),
new HeaderEntry("location", ""),
new HeaderEntry("max-forwards", ""),
new HeaderEntry("proxy-authenticate", ""),
new HeaderEntry("proxy-authorization", ""),
new HeaderEntry("range", ""),
new HeaderEntry("referer", ""),
new HeaderEntry("refresh", ""),
new HeaderEntry("retry-after", ""),
new HeaderEntry("server", ""),
new HeaderEntry("set-cookie", ""),
new HeaderEntry("strict-transport-security", ""),
new HeaderEntry("transfer-encoding", ""),
new HeaderEntry("user-agent", ""),
new HeaderEntry("vary", ""),
new HeaderEntry("via", ""),
new HeaderEntry("www-authenticate", "")
);
// Update these when initial tables change to sum of each entry length.
static final int INITIAL_CLIENT_TO_SERVER_HEADER_TABLE_LENGTH = 1262;
static final int INITIAL_SERVER_TO_CLIENT_HEADER_TABLE_LENGTH = 1304;
private Hpack() {
private HpackDraft05() {
}
// TODO: huffman encoding!
// http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-05#section-4.1.2
static class Reader {
private final long maxBufferSize = 4096; // TODO: needs to come from settings.
private final DataInputStream in;
private final BitSet referenceSet = new BitSet();
private final List<HeaderEntry> headerTable;
private final List<String> emittedHeaders = new ArrayList<String>();
private long bufferSize = 0;
private long bytesLeft = 0;
Reader(DataInputStream in, boolean client) {
// Visible for testing.
final List<HeaderEntry> headerTable = new ArrayList<HeaderEntry>(); // TODO: default capacity?
final BitSet staticReferenceSet = new BitSet();
long headerTableSize = 0;
long maxHeaderTableSize = 4096; // TODO: needs to come from SETTINGS_HEADER_TABLE_SIZE.
Reader(DataInputStream in) {
this.in = in;
if (client) { // we are reading from the server
this.headerTable = new ArrayList<HeaderEntry>(INITIAL_SERVER_TO_CLIENT_HEADER_TABLE);
this.bufferSize = INITIAL_SERVER_TO_CLIENT_HEADER_TABLE_LENGTH;
} else {
this.headerTable = new ArrayList<HeaderEntry>(INITIAL_CLIENT_TO_SERVER_HEADER_TABLE);
this.bufferSize = INITIAL_CLIENT_TO_SERVER_HEADER_TABLE_LENGTH;
}
}
// Visible for testing.
void reset() {
bytesLeft = 0;
headerTableSize = 0;
maxHeaderTableSize = 4096;
staticReferenceSet.clear();
headerTable.clear();
emittedHeaders.clear();
}
/**
@@ -141,32 +156,42 @@ final class Hpack {
if ((b & 0x80) != 0) {
int index = readInt(b, PREFIX_7_BITS);
readIndexedHeader(index);
} else if (b == 0x60) {
readLiteralHeaderWithoutIndexingNewName();
} else if ((b & 0xe0) == 0x60) {
int index = readInt(b, PREFIX_5_BITS);
readLiteralHeaderWithoutIndexingIndexedName(index - 1);
if (index == 0) {
emptyReferenceSet();
} else {
readIndexedHeader(index - 1);
}
} else if (b == 0x40) {
readLiteralHeaderWithIncrementalIndexingNewName();
readLiteralHeaderWithoutIndexingNewName();
} else if ((b & 0xe0) == 0x40) {
int index = readInt(b, PREFIX_5_BITS);
readLiteralHeaderWithIncrementalIndexingIndexedName(index - 1);
int index = readInt(b, PREFIX_6_BITS);
readLiteralHeaderWithoutIndexingIndexedName(index - 1);
} else if (b == 0) {
readLiteralHeaderWithSubstitutionIndexingNewName();
readLiteralHeaderWithIncrementalIndexingNewName();
} else if ((b & 0xc0) == 0) {
int index = readInt(b, PREFIX_6_BITS);
readLiteralHeaderWithSubstitutionIndexingIndexedName(index - 1);
readLiteralHeaderWithIncrementalIndexingIndexedName(index - 1);
} else {
throw new AssertionError();
}
}
}
private void emptyReferenceSet() {
staticReferenceSet.clear();
for (HeaderEntry entry : headerTable) {
entry.referenced = false;
}
}
public void emitReferenceSet() {
for (int i = referenceSet.nextSetBit(0); i != -1; i = referenceSet.nextSetBit(i + 1)) {
emittedHeaders.add(getName(i));
emittedHeaders.add(getValue(i));
for (int i = staticReferenceSet.nextSetBit(0); i != -1;
i = staticReferenceSet.nextSetBit(i + 1)) {
STATIC_HEADER_TABLE.get(i).addTo(emittedHeaders);
}
for (ListIterator<HeaderEntry> li = headerTable.listIterator(headerTable.size());
li.hasPrevious(); ) {
li.previous().addTo(emittedHeaders);
}
}
@@ -181,10 +206,17 @@ final class Hpack {
}
private void readIndexedHeader(int index) {
if (referenceSet.get(index)) {
referenceSet.clear(index);
} else {
referenceSet.set(index);
if (isStaticHeader(index)) {
if (maxHeaderTableSize == 0) {
staticReferenceSet.set(index - headerTable.size());
} else {
HeaderEntry staticEntry = STATIC_HEADER_TABLE.get(index - headerTable.size());
insertIntoHeaderTable(-1, staticEntry.clone());
}
} else if (!headerTable.get(index).referenced) {
HeaderEntry existing = headerTable.get(index);
existing.referenced = true;
insertIntoHeaderTable(index, existing);
}
}
@@ -208,77 +240,60 @@ final class Hpack {
throws IOException {
String name = getName(nameIndex);
String value = readString();
int index = headerTable.size(); // append to tail
insertIntoHeaderTable(index, new HeaderEntry(name, value));
insertIntoHeaderTable(-1, new HeaderEntry(name, value));
}
private void readLiteralHeaderWithIncrementalIndexingNewName() throws IOException {
String name = readString();
String value = readString();
int index = headerTable.size(); // append to tail
insertIntoHeaderTable(index, new HeaderEntry(name, value));
}
private void readLiteralHeaderWithSubstitutionIndexingIndexedName(int nameIndex)
throws IOException {
int index = readInt(readByte(), PREFIX_8_BITS);
String name = getName(nameIndex);
String value = readString();
insertIntoHeaderTable(index, new HeaderEntry(name, value));
}
private void readLiteralHeaderWithSubstitutionIndexingNewName() throws IOException {
String name = readString();
int index = readInt(readByte(), PREFIX_8_BITS);
String value = readString();
insertIntoHeaderTable(index, new HeaderEntry(name, value));
insertIntoHeaderTable(-1, new HeaderEntry(name, value));
}
private String getName(int index) {
return headerTable.get(index).name;
if (isStaticHeader(index)) {
return STATIC_HEADER_TABLE.get(index - headerTable.size()).name;
} else {
return headerTable.get(index).name;
}
}
private String getValue(int index) {
return headerTable.get(index).value;
private boolean isStaticHeader(int index) {
return index >= headerTable.size();
}
/** index == -1 when new. */
private void insertIntoHeaderTable(int index, HeaderEntry entry) {
int delta = entry.length();
if (index != headerTable.size()) {
delta -= headerTable.get(index).length();
int delta = entry.size;
if (index != -1) { // Index -1 == new header.
delta -= headerTable.get(index).size;
}
// if the new or replacement header is too big, drop all entries.
if (delta > maxBufferSize) {
if (delta > maxHeaderTableSize) {
staticReferenceSet.clear();
headerTable.clear();
bufferSize = 0;
headerTableSize = 0;
// emit the large header to the callback.
emittedHeaders.add(entry.name);
emittedHeaders.add(entry.value);
entry.addTo(emittedHeaders);
return;
}
// Prune headers to the required length.
while (bufferSize + delta > maxBufferSize) {
remove(0);
index--;
// Evict headers to the required length.
while (headerTableSize + delta > maxHeaderTableSize) {
remove(headerTable.size() - 1);
}
if (index < 0) { // we pruned it, so insert at beginning
index = 0;
headerTable.add(index, entry);
} else if (index == headerTable.size()) { // append to the end
headerTable.add(index, entry);
} else { // replace value at same position
if (index == -1) {
headerTable.add(0, entry);
} else { // Replace value at same position.
headerTable.set(index, entry);
}
bufferSize += delta;
referenceSet.set(index);
headerTableSize += delta;
}
private void remove(int index) {
bufferSize -= headerTable.remove(index).length();
headerTableSize -= headerTable.remove(index).size;
}
private int readByte() throws IOException {
@@ -332,7 +347,7 @@ final class Hpack {
public void writeHeaders(List<String> nameValueBlock) throws IOException {
// TODO: implement a compression strategy.
for (int i = 0, size = nameValueBlock.size(); i < size; i += 2) {
out.write(0x60); // Literal Header without Indexing - New Name.
out.write(0x40); // Literal Header without Indexing - New Name.
writeString(nameValueBlock.get(i));
writeString(nameValueBlock.get(i + 1));
}

View File

@@ -25,12 +25,13 @@ import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
/**
* Read and write http/2 v06 frames.
* http://tools.ietf.org/html/draft-ietf-httpbis-http2-06
* Read and write http/2 v09 frames.
* http://tools.ietf.org/html/draft-ietf-httpbis-http2-09
*/
final class Http20Draft06 implements Variant {
public final class Http20Draft09 implements Variant {
private static final byte[] CONNECTION_HEADER;
static {
try {
@@ -52,10 +53,11 @@ final class Http20Draft06 implements Variant {
static final int TYPE_CONTINUATION = 0xa;
static final int FLAG_END_STREAM = 0x1;
/** Used for headers, push-promise and continuation. */
static final int FLAG_END_HEADERS = 0x4;
static final int FLAG_PRIORITY = 0x8;
static final int FLAG_PONG = 0x1;
static final int FLAG_ACK = 0x1;
static final int FLAG_END_FLOW_CONTROL = 0x1;
@Override public FrameReader newReader(InputStream in, boolean client) {
@@ -69,12 +71,14 @@ final class Http20Draft06 implements Variant {
static final class Reader implements FrameReader {
private final DataInputStream in;
private final boolean client;
private final Hpack.Reader hpackReader;
// Visible for testing.
final HpackDraft05.Reader hpackReader;
Reader(InputStream in, boolean client) {
this.in = new DataInputStream(in);
this.client = client;
this.hpackReader = new Hpack.Reader(this.in, client);
this.hpackReader = new HpackDraft05.Reader(this.in);
}
@Override public void readConnectionHeader() throws IOException {
@@ -96,7 +100,8 @@ final class Http20Draft06 implements Variant {
}
int w2 = in.readInt();
int length = (w1 & 0xffff0000) >> 16;
// boolean r = (w1 & 0xc0000000) != 0; // Reserved.
int length = (w1 & 0x3fff0000) >> 16; // 14-bit unsigned.
int type = (w1 & 0xff00) >> 8;
int flags = w1 & 0xff;
// boolean r = (w2 & 0x80000000) != 0; // Reserved.
@@ -155,6 +160,10 @@ final class Http20Draft06 implements Variant {
if ((flags & FLAG_END_HEADERS) != 0) {
hpackReader.emitReferenceSet();
List<String> namesAndValues = hpackReader.getAndReset();
// TODO: throw malformed if any present:
// Connection, Keep-Alive, Proxy-Connection, TE, Transfer-Encoding, Encoding, Upgrade.
// TODO: Concat multi-value headers with 0x0, except COOKIE, which uses 0x3B, 0x20.
// http://tools.ietf.org/html/draft-ietf-httpbis-http2-09#section-8.1.3
int priority = -1; // TODO: priority
handler.headers(false, inFinished, streamId, -1, priority, namesAndValues,
HeadersMode.HTTP_20_HEADERS);
@@ -165,13 +174,11 @@ final class Http20Draft06 implements Variant {
int w1 = in.readInt();
int w2 = in.readInt();
length = (w1 & 0xffff0000) >> 16;
// boolean r = (w1 & 0xc0000000) != 0; // Reserved.
length = (w1 & 0x3fff0000) >> 16; // 14-bit unsigned.
int newType = (w1 & 0xff00) >> 8;
flags = w1 & 0xff;
// TODO: remove in draft 8: CONTINUATION no longer sets END_STREAM
inFinished = (flags & FLAG_END_STREAM) != 0;
// boolean u = (w2 & 0x80000000) != 0; // Unused.
int newStreamId = (w2 & 0x7fffffff);
@@ -184,6 +191,7 @@ final class Http20Draft06 implements Variant {
private void readData(Handler handler, int flags, int length, int streamId) throws IOException {
boolean inFinished = (flags & FLAG_END_STREAM) != 0;
// TODO: checkState open or half-closed (local) or raise STREAM_CLOSED
handler.data(inFinished, streamId, in, length);
}
@@ -211,6 +219,13 @@ final class Http20Draft06 implements Variant {
private void readSettings(Handler handler, int flags, int length, int streamId)
throws IOException {
if ((flags & FLAG_ACK) != 0) {
if (length != 0) throw ioException("FRAME_SIZE_ERROR ack frame should be empty!");
// TODO: signal apply changes
// http://tools.ietf.org/html/draft-ietf-httpbis-http2-09#section-6.5.3
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();
@@ -233,7 +248,7 @@ final class Http20Draft06 implements Variant {
if (streamId != 0) throw ioException("TYPE_PING streamId != 0");
int payload1 = in.readInt();
int payload2 = in.readInt();
boolean reply = (flags & FLAG_PONG) != 0;
boolean reply = (flags & FLAG_ACK) != 0;
handler.ping(reply, payload1, payload2);
}
@@ -262,10 +277,6 @@ final class Http20Draft06 implements Variant {
handler.windowUpdate(streamId, windowSizeIncrement, endFlowControl);
}
private static IOException ioException(String message, Object... args) throws IOException {
throw new IOException(String.format(message, args));
}
@Override public void close() throws IOException {
in.close();
}
@@ -275,13 +286,13 @@ final class Http20Draft06 implements Variant {
private final DataOutputStream out;
private final boolean client;
private final ByteArrayOutputStream hpackBuffer;
private final Hpack.Writer hpackWriter;
private final HpackDraft05.Writer hpackWriter;
Writer(OutputStream out, boolean client) {
this.out = new DataOutputStream(out);
this.client = client;
this.hpackBuffer = new ByteArrayOutputStream();
this.hpackWriter = new Hpack.Writer(hpackBuffer);
this.hpackWriter = new HpackDraft05.Writer(hpackBuffer);
}
@Override public synchronized void flush() throws IOException {
@@ -313,14 +324,23 @@ final class Http20Draft06 implements Variant {
private void headers(boolean outFinished, int streamId, int priority,
List<String> nameValueBlock) throws IOException {
hpackBuffer.reset();
for (int i = 0, size = nameValueBlock.size(); i < size; i += 2) {
String headerName = nameValueBlock.get(i).toLowerCase(Locale.US);
// our SpdyTransport.writeNameValueBlock hard-codes :host
if (":host".equals(headerName)) headerName = ":authority";
nameValueBlock.set(i, headerName);
}
// TODO: throw malformed if any present:
// Connection, Keep-Alive, Proxy-Connection, TE, Transfer-Encoding, Encoding, Upgrade.
hpackWriter.writeHeaders(nameValueBlock);
int type = TYPE_HEADERS;
// TODO: implement CONTINUATION
int length = hpackBuffer.size();
checkFrameSize(length);
int flags = FLAG_END_HEADERS;
if (outFinished) flags |= FLAG_END_STREAM;
if (priority != -1) flags |= FLAG_PRIORITY;
out.writeInt((length & 0xffff) << 16 | (type & 0xff) << 8 | (flags & 0xff));
out.writeInt((length & 0x3fff) << 16 | (type & 0xff) << 8 | (flags & 0xff));
out.writeInt(streamId & 0x7fffffff);
if (priority != -1) out.writeInt(priority & 0x7fffffff);
hpackBuffer.writeTo(out);
@@ -328,7 +348,14 @@ final class Http20Draft06 implements Variant {
@Override public synchronized void rstStream(int streamId, ErrorCode errorCode)
throws IOException {
throw new UnsupportedOperationException("TODO");
if (errorCode.spdyRstCode == -1) throw new IllegalArgumentException();
int flags = 0;
int type = TYPE_RST_STREAM;
int length = 4;
out.writeInt((length & 0x3fff) << 16 | (type & 0xff) << 8 | (flags & 0xff));
out.writeInt(streamId & 0x7fffffff);
out.writeInt(errorCode.spdyRstCode);
out.flush();
}
@Override public void data(boolean outFinished, int streamId, byte[] data) throws IOException {
@@ -340,7 +367,8 @@ final class Http20Draft06 implements Variant {
int type = TYPE_DATA;
int flags = 0;
if (outFinished) flags |= FLAG_END_STREAM;
out.writeInt((byteCount & 0xffff) << 16 | (type & 0xff) << 8 | (flags & 0xff));
checkFrameSize(byteCount);
out.writeInt((byteCount & 0x3fff) << 16 | (type & 0xff) << 8 | (flags & 0xff));
out.writeInt(streamId & 0x7fffffff);
out.write(data, offset, byteCount);
}
@@ -350,7 +378,7 @@ final class Http20Draft06 implements Variant {
int length = settings.size() * 8;
int flags = 0;
int streamId = 0;
out.writeInt((length & 0xffff) << 16 | (type & 0xff) << 8 | (flags & 0xff));
out.writeInt((length & 0x3fff) << 16 | (type & 0xff) << 8 | (flags & 0xff));
out.writeInt(streamId & 0x7fffffff);
for (int i = 0; i < Settings.COUNT; i++) {
if (!settings.isSet(i)) continue;
@@ -382,4 +410,12 @@ final class Http20Draft06 implements Variant {
out.close();
}
}
private static void checkFrameSize(int bytes) throws IOException {
if (bytes > 16383) throw ioException("FRAME_SIZE_ERROR max size is 16383: %s", bytes);
}
private static IOException ioException(String message, Object... args) throws IOException {
throw new IOException(String.format(message, args));
}
}

View File

@@ -17,10 +17,14 @@ package com.squareup.okhttp.internal.spdy;
final class Settings {
/**
* From the spdy/3 spec, the default initial window size for all streams is
* 64 KiB. (Chrome 25 uses 10 MiB).
* spdy/3: The default initial window size for all streams is 64 KiB. (Chrome
* 25 uses 10 MiB).
*/
static final int DEFAULT_INITIAL_WINDOW_SIZE = 64 * 1024;
/** http/2: The default header compression table size is 4 KiB. */
static final int DEFAULT_HEADER_TABLE_SIZE = 4096;
/** http/2: The default is to enable PUSH_PROMISE frames. */
static final int DEFAULT_ENABLE_PUSH = 1;
/** Peer request to clear durable settings. */
static final int FLAG_CLEAR_PREVIOUSLY_PERSISTED_SETTINGS = 0x1;
@@ -30,24 +34,28 @@ final class Settings {
/** Sent by clients only. The client is reminding the server of a persisted value. */
static final int PERSISTED = 0x2;
/** Sender's estimate of max incoming kbps. */
/** spdy/3: Sender's estimate of max incoming kbps. */
static final int UPLOAD_BANDWIDTH = 1;
/** Sender's estimate of max outgoing kbps. */
/** http/2: Size in bytes of the table used to decode the sender's header blocks. */
static final int HEADER_TABLE_SIZE = 1;
/** spdy/3: Sender's estimate of max outgoing kbps. */
static final int DOWNLOAD_BANDWIDTH = 2;
/** Sender's estimate of milliseconds between sending a request and receiving a response. */
/** http/2: An endpoint must not send a PUSH_PROMISE frame this is 0. */
static final int ENABLE_PUSH = 2;
/** spdy/3: Sender's estimate of millis between sending a request and receiving a response. */
static final int ROUND_TRIP_TIME = 3;
/** Sender's maximum number of concurrent streams. */
static final int MAX_CONCURRENT_STREAMS = 4;
/** Current CWND in Packets. */
/** spdy/3: Current CWND in Packets. */
static final int CURRENT_CWND = 5;
/** Retransmission rate. Percentage */
/** spdy/3: Retransmission rate. Percentage */
static final int DOWNLOAD_RETRANS_RATE = 6;
/** Window size in bytes. */
static final int INITIAL_WINDOW_SIZE = 7;
/** Window size in bytes. */
/** spdy/3: Window size in bytes. */
static final int CLIENT_CERTIFICATE_VECTOR_SIZE = 8;
/** Flow control options. */
static final int FLOW_CONTROL_OPTIONS = 9;
static final int FLOW_CONTROL_OPTIONS = 10;
/** Total number of settings. */
static final int COUNT = 10;
@@ -112,47 +120,69 @@ final class Settings {
return Integer.bitCount(set);
}
/** spdy/3 only. */
int getUploadBandwidth(int defaultValue) {
int bit = 1 << UPLOAD_BANDWIDTH;
return (bit & set) != 0 ? values[UPLOAD_BANDWIDTH] : defaultValue;
}
/** http/2 only. */
// TODO: honor this setting in http/2.
int getHeaderTableSize() {
int bit = 1 << HEADER_TABLE_SIZE;
return (bit & set) != 0 ? values[HEADER_TABLE_SIZE] : DEFAULT_HEADER_TABLE_SIZE;
}
/** spdy/3 only. */
int getDownloadBandwidth(int defaultValue) {
int bit = 1 << DOWNLOAD_BANDWIDTH;
return (bit & set) != 0 ? values[DOWNLOAD_BANDWIDTH] : defaultValue;
}
/** http/2 only. */
// TODO: honor this setting in http/2.
boolean getEnablePush() {
int bit = 1 << ENABLE_PUSH;
return ((bit & set) != 0 ? values[ENABLE_PUSH] : DEFAULT_ENABLE_PUSH) == 1;
}
/** spdy/3 only. */
int getRoundTripTime(int defaultValue) {
int bit = 1 << ROUND_TRIP_TIME;
return (bit & set) != 0 ? values[ROUND_TRIP_TIME] : defaultValue;
}
// TODO: honor this setting in spdy/3 and http/2.
int getMaxConcurrentStreams(int defaultValue) {
int bit = 1 << MAX_CONCURRENT_STREAMS;
return (bit & set) != 0 ? values[MAX_CONCURRENT_STREAMS] : defaultValue;
}
/** spdy/3 only. */
int getCurrentCwnd(int defaultValue) {
int bit = 1 << CURRENT_CWND;
return (bit & set) != 0 ? values[CURRENT_CWND] : defaultValue;
}
/** spdy/3 only. */
int getDownloadRetransRate(int defaultValue) {
int bit = 1 << DOWNLOAD_RETRANS_RATE;
return (bit & set) != 0 ? values[DOWNLOAD_RETRANS_RATE] : defaultValue;
}
// TODO: honor this setting in http/2.
int getInitialWindowSize(int defaultValue) {
int bit = 1 << INITIAL_WINDOW_SIZE;
return (bit & set) != 0 ? values[INITIAL_WINDOW_SIZE] : defaultValue;
}
/** spdy/3 only. */
int getClientCertificateVectorSize(int defaultValue) {
int bit = 1 << CLIENT_CERTIFICATE_VECTOR_SIZE;
return (bit & set) != 0 ? values[CLIENT_CERTIFICATE_VECTOR_SIZE] : defaultValue;
}
// TODO: honor this setting.
// TODO: honor this setting in spdy/3 and http/2.
boolean isFlowControlDisabled() {
int bit = 1 << FLOW_CONTROL_OPTIONS;
int value = (bit & set) != 0 ? values[FLOW_CONTROL_OPTIONS] : 0;
@@ -160,7 +190,7 @@ final class Settings {
}
/**
* Returns true if this user agent should use this setting in future SPDY
* Returns true if this user agent should use this setting in future spdy/3
* connections to the same host.
*/
boolean persistValue(int id) {

View File

@@ -59,7 +59,7 @@ public final class SpdyConnection implements Closeable {
Integer.MAX_VALUE, 60, TimeUnit.SECONDS, new SynchronousQueue<Runnable>(),
Util.daemonThreadFactory("OkHttp SpdyConnection"));
/** The protocol variant, like SPDY/3 or HTTP-draft-06/2.0. */
/** The protocol variant, like SPDY/3 or HTTP-draft-09/2.0. */
final Variant variant;
/** True if this peer initiated the connection. */
@@ -151,7 +151,7 @@ public final class SpdyConnection implements Closeable {
boolean outFinished = !out;
boolean inFinished = !in;
int associatedStreamId = 0; // TODO: permit the caller to specify an associated stream?
int priority = 0; // TODO: permit the caller to specify a priority?
int priority = -1; // TODO: permit the caller to specify a priority?
int slot = 0; // TODO: permit the caller to specify a slot?
SpdyStream stream;
int streamId;
@@ -416,8 +416,8 @@ public final class SpdyConnection implements Closeable {
return this;
}
public Builder http20Draft06() {
this.variant = Variant.HTTP_20_DRAFT_06;
public Builder http20Draft09() {
this.variant = Variant.HTTP_20_DRAFT_09;
return this;
}

View File

@@ -21,7 +21,7 @@ import java.io.OutputStream;
/** A version and dialect of the framed socket protocol. */
interface Variant {
Variant SPDY3 = new Spdy3();
Variant HTTP_20_DRAFT_06 = new Http20Draft06();
Variant HTTP_20_DRAFT_09 = new Http20Draft09();
/**
* @param client true if this is the HTTP client's reader, reading frames from

View File

@@ -0,0 +1,496 @@
/*
* Copyright (C) 2013 Square, Inc.
*
* 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.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import org.junit.After;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class HpackDraft05Test {
private final MutableByteArrayInputStream bytesIn = new MutableByteArrayInputStream();
private final HpackDraft05.Reader hpackReader = new HpackDraft05.Reader(new DataInputStream(bytesIn));
@After public void resetReader(){
hpackReader.reset();
}
/**
* HPACK has a max header table size, which can be smaller than the max header message.
* Ensure the larger header content is not lost.
*/
@Test public void tooLargeToHPackIsStillEmitted() throws IOException {
char[] tooLarge = new char[4096];
Arrays.fill(tooLarge, 'a');
final List<String> sentHeaders = Arrays.asList("foo", new String(tooLarge));
bytesIn.set(literalHeaders(sentHeaders));
hpackReader.readHeaders(bytesIn.available());
hpackReader.emitReferenceSet();
assertEquals(0, hpackReader.headerTable.size());
assertEquals(sentHeaders, hpackReader.getAndReset());
}
/**
* http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-05#appendix-E.1.1
*/
@Test public void decodeLiteralHeaderFieldWithIndexing() throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(0x00); // Literal indexed
out.write(0x0a); // Literal name (len = 10)
out.write("custom-key".getBytes(), 0, 10);
out.write(0x0d); // Literal value (len = 13)
out.write("custom-header".getBytes(), 0, 13);
bytesIn.set(out.toByteArray());
hpackReader.readHeaders(bytesIn.available());
hpackReader.emitReferenceSet();
assertEquals(1, hpackReader.headerTable.size());
HpackDraft05.HeaderEntry entry = hpackReader.headerTable.get(0);
assertEquals("custom-key", entry.name);
assertEquals("custom-header", entry.value);
assertEquals(55, entry.size);
assertEquals(entry.size, hpackReader.headerTableSize);
assertEquals(Arrays.asList("custom-key", "custom-header"), hpackReader.getAndReset());
}
/**
* http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-05#appendix-E.1.2
*/
@Test public void decodeLiteralHeaderFieldWithoutIndexingIndexedName() throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(0x44); // == Literal not indexed ==
// Indexed name (idx = 4) -> :path
out.write(0x0c); // Literal value (len = 12)
out.write("/sample/path".getBytes(), 0, 12);
bytesIn.set(out.toByteArray());
hpackReader.readHeaders(bytesIn.available());
hpackReader.emitReferenceSet();
assertEquals(0, hpackReader.headerTable.size());
assertEquals(Arrays.asList(":path", "/sample/path"), hpackReader.getAndReset());
}
/**
* http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-05#appendix-E.1.3
*/
@Test public void decodeIndexedHeaderField() throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(0x82); // == Indexed - Add ==
// idx = 2 -> :method: GET
bytesIn.set(out.toByteArray());
hpackReader.readHeaders(bytesIn.available());
hpackReader.emitReferenceSet();
assertEquals(1, hpackReader.headerTable.size());
HpackDraft05.HeaderEntry entry = hpackReader.headerTable.get(0);
assertEquals(":method", entry.name);
assertEquals("GET", entry.value);
assertEquals(42, entry.size);
assertEquals(entry.size, hpackReader.headerTableSize);
assertEquals(Arrays.asList(":method", "GET"), hpackReader.getAndReset());
}
/**
* http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-05#appendix-E.1.4
*/
@Test public void decodeIndexedHeaderFieldFromStaticTableWithoutBuffering() throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(0x82); // == Indexed - Add ==
// idx = 2 -> :method: GET
bytesIn.set(out.toByteArray());
hpackReader.maxHeaderTableSize = 0; // SETTINGS_HEADER_TABLE_SIZE == 0
hpackReader.readHeaders(bytesIn.available());
hpackReader.emitReferenceSet();
// Not buffered in header table.
assertEquals(0, hpackReader.headerTable.size());
assertEquals(Arrays.asList(":method", "GET"), hpackReader.getAndReset());
}
/**
* http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-05#appendix-E.2
*/
@Test public void decodeRequestExamplesWithoutHuffman() throws IOException {
bytesIn.set(firstRequestWithoutHuffman());
hpackReader.readHeaders(bytesIn.available());
hpackReader.emitReferenceSet();
checkFirstRequestWithoutHuffman();
bytesIn.set(secondRequestWithoutHuffman());
hpackReader.readHeaders(bytesIn.available());
hpackReader.emitReferenceSet();
checkSecondRequestWithoutHuffman();
bytesIn.set(thirdRequestWithoutHuffman());
hpackReader.readHeaders(bytesIn.available());
hpackReader.emitReferenceSet();
checkThirdRequestWithoutHuffman();
}
private byte[] firstRequestWithoutHuffman() {
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(0x82); // == Indexed - Add ==
// idx = 2 -> :method: GET
out.write(0x87); // == Indexed - Add ==
// idx = 7 -> :scheme: http
out.write(0x86); // == Indexed - Add ==
// idx = 6 -> :path: /
out.write(0x04); // == Literal indexed ==
// Indexed name (idx = 4) -> :authority
out.write(0x0f); // Literal value (len = 15)
out.write("www.example.com".getBytes(), 0, 15);
return out.toByteArray();
}
private void checkFirstRequestWithoutHuffman() {
assertEquals(4, hpackReader.headerTable.size());
// [ 1] (s = 57) :authority: www.example.com
HpackDraft05.HeaderEntry entry = hpackReader.headerTable.get(0);
assertEquals(":authority", entry.name);
assertEquals("www.example.com", entry.value);
assertEquals(57, entry.size);
assertTrue(entry.referenced);
// [ 2] (s = 38) :path: /
entry = hpackReader.headerTable.get(1);
assertEquals(":path", entry.name);
assertEquals("/", entry.value);
assertEquals(38, entry.size);
assertTrue(entry.referenced);
// [ 3] (s = 43) :scheme: http
entry = hpackReader.headerTable.get(2);
assertEquals(":scheme", entry.name);
assertEquals("http", entry.value);
assertEquals(43, entry.size);
assertTrue(entry.referenced);
// [ 4] (s = 42) :method: GET
entry = hpackReader.headerTable.get(3);
assertEquals(":method", entry.name);
assertEquals("GET", entry.value);
assertEquals(42, entry.size);
assertTrue(entry.referenced);
// Table size: 180
assertEquals(180, hpackReader.headerTableSize);
// Decoded header set:
assertEquals(Arrays.asList( //
":method", "GET", //
":scheme", "http", //
":path", "/", //
":authority", "www.example.com"), hpackReader.getAndReset());
}
private byte[] secondRequestWithoutHuffman() {
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(0x1b); // == Literal indexed ==
// Indexed name (idx = 27) -> cache-control
out.write(0x08); // Literal value (len = 8)
out.write("no-cache".getBytes(), 0, 8);
return out.toByteArray();
}
private void checkSecondRequestWithoutHuffman() {
assertEquals(5, hpackReader.headerTable.size());
// [ 1] (s = 53) cache-control: no-cache
HpackDraft05.HeaderEntry entry = hpackReader.headerTable.get(0);
assertEquals("cache-control", entry.name);
assertEquals("no-cache", entry.value);
assertEquals(53, entry.size);
assertTrue(entry.referenced);
// [ 2] (s = 57) :authority: www.example.com
entry = hpackReader.headerTable.get(1);
assertEquals(":authority", entry.name);
assertEquals("www.example.com", entry.value);
assertEquals(57, entry.size);
assertTrue(entry.referenced);
// [ 3] (s = 38) :path: /
entry = hpackReader.headerTable.get(2);
assertEquals(":path", entry.name);
assertEquals("/", entry.value);
assertEquals(38, entry.size);
assertTrue(entry.referenced);
// [ 4] (s = 43) :scheme: http
entry = hpackReader.headerTable.get(3);
assertEquals(":scheme", entry.name);
assertEquals("http", entry.value);
assertEquals(43, entry.size);
assertTrue(entry.referenced);
// [ 5] (s = 42) :method: GET
entry = hpackReader.headerTable.get(4);
assertEquals(":method", entry.name);
assertEquals("GET", entry.value);
assertEquals(42, entry.size);
assertTrue(entry.referenced);
// Table size: 233
assertEquals(233, hpackReader.headerTableSize);
// Decoded header set:
assertEquals(Arrays.asList( //
":method", "GET", //
":scheme", "http", //
":path", "/", //
":authority", "www.example.com", //
"cache-control", "no-cache"), hpackReader.getAndReset());
}
private byte[] thirdRequestWithoutHuffman() {
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(0x80); // == Empty reference set ==
out.write(0x85); // == Indexed - Add ==
// idx = 5 -> :method: GET
out.write(0x8c); // == Indexed - Add ==
// idx = 12 -> :scheme: https
out.write(0x8b); // == Indexed - Add ==
// idx = 11 -> :path: /index.html
out.write(0x84); // == Indexed - Add ==
// idx = 4 -> :authority: www.example.com
out.write(0x00); // Literal indexed
out.write(0x0a); // Literal name (len = 10)
out.write("custom-key".getBytes(), 0, 10);
out.write(0x0c); // Literal value (len = 12)
out.write("custom-value".getBytes(), 0, 12);
return out.toByteArray();
}
private void checkThirdRequestWithoutHuffman() {
assertEquals(8, hpackReader.headerTable.size());
// [ 1] (s = 54) custom-key: custom-value
HpackDraft05.HeaderEntry entry = hpackReader.headerTable.get(0);
assertEquals("custom-key", entry.name);
assertEquals("custom-value", entry.value);
assertEquals(54, entry.size);
assertTrue(entry.referenced);
// [ 2] (s = 48) :path: /index.html
entry = hpackReader.headerTable.get(1);
assertEquals(":path", entry.name);
assertEquals("/index.html", entry.value);
assertEquals(48, entry.size);
assertTrue(entry.referenced);
// [ 3] (s = 44) :scheme: https
entry = hpackReader.headerTable.get(2);
assertEquals(":scheme", entry.name);
assertEquals("https", entry.value);
assertEquals(44, entry.size);
assertTrue(entry.referenced);
// [ 4] (s = 53) cache-control: no-cache
entry = hpackReader.headerTable.get(3);
assertEquals("cache-control", entry.name);
assertEquals("no-cache", entry.value);
assertEquals(53, entry.size);
assertFalse(entry.referenced);
// [ 5] (s = 57) :authority: www.example.com
entry = hpackReader.headerTable.get(4);
assertEquals(":authority", entry.name);
assertEquals("www.example.com", entry.value);
assertEquals(57, entry.size);
assertTrue(entry.referenced);
// [ 6] (s = 38) :path: /
entry = hpackReader.headerTable.get(5);
assertEquals(":path", entry.name);
assertEquals("/", entry.value);
assertEquals(38, entry.size);
assertFalse(entry.referenced);
// [ 7] (s = 43) :scheme: http
entry = hpackReader.headerTable.get(6);
assertEquals(":scheme", entry.name);
assertEquals("http", entry.value);
assertEquals(43, entry.size);
assertFalse(entry.referenced);
// [ 8] (s = 42) :method: GET
entry = hpackReader.headerTable.get(7);
assertEquals(":method", entry.name);
assertEquals("GET", entry.value);
assertEquals(42, entry.size);
assertTrue(entry.referenced);
// Table size: 379
assertEquals(379, hpackReader.headerTableSize);
// Decoded header set:
// TODO: order is not correct per docs, but then again, the spec doesn't require ordering.
assertEquals(Arrays.asList( //
":method", "GET", //
":authority", "www.example.com", //
":scheme", "https", //
":path", "/index.html", //
"custom-key", "custom-value"), hpackReader.getAndReset());
}
private ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
private final HpackDraft05.Writer hpackWriter = new HpackDraft05.Writer(new DataOutputStream(bytesOut));
@Test public void readSingleByteInt() throws IOException {
assertEquals(10, new HpackDraft05.Reader(byteStream()).readInt(10, 31));
assertEquals(10, new HpackDraft05.Reader(byteStream()).readInt(0xe0 | 10, 31));
}
@Test public void readMultibyteInt() throws IOException {
assertEquals(1337, new HpackDraft05.Reader(byteStream(154, 10)).readInt(31, 31));
}
@Test public void writeSingleByteInt() throws IOException {
hpackWriter.writeInt(10, 31, 0);
assertBytes(10);
hpackWriter.writeInt(10, 31, 0xe0);
assertBytes(0xe0 | 10);
}
@Test public void writeMultibyteInt() throws IOException {
hpackWriter.writeInt(1337, 31, 0);
assertBytes(31, 154, 10);
hpackWriter.writeInt(1337, 31, 0xe0);
assertBytes(0xe0 | 31, 154, 10);
}
@Test public void max31BitValue() throws IOException {
hpackWriter.writeInt(0x7fffffff, 31, 0);
assertBytes(31, 224, 255, 255, 255, 7);
assertEquals(0x7fffffff,
new HpackDraft05.Reader(byteStream(224, 255, 255, 255, 7)).readInt(31, 31));
}
@Test public void prefixMask() throws IOException {
hpackWriter.writeInt(31, 31, 0);
assertBytes(31, 0);
assertEquals(31, new HpackDraft05.Reader(byteStream(0)).readInt(31, 31));
}
@Test public void prefixMaskMinusOne() throws IOException {
hpackWriter.writeInt(30, 31, 0);
assertBytes(30);
assertEquals(31, new HpackDraft05.Reader(byteStream(0)).readInt(31, 31));
}
@Test public void zero() throws IOException {
hpackWriter.writeInt(0, 31, 0);
assertBytes(0);
assertEquals(0, new HpackDraft05.Reader(byteStream()).readInt(0, 31));
}
@Test public void headerName() throws IOException {
hpackWriter.writeString("foo");
assertBytes(3, 'f', 'o', 'o');
assertEquals("foo", new HpackDraft05.Reader(byteStream(3, 'f', 'o', 'o')).readString());
}
@Test public void emptyHeaderName() throws IOException {
hpackWriter.writeString("");
assertBytes(0);
assertEquals("", new HpackDraft05.Reader(byteStream(0)).readString());
}
@Test public void headersRoundTrip() throws IOException {
List<String> sentHeaders = Arrays.asList("name", "value");
hpackWriter.writeHeaders(sentHeaders);
ByteArrayInputStream bytesIn = new ByteArrayInputStream(bytesOut.toByteArray());
HpackDraft05.Reader reader = new HpackDraft05.Reader(new DataInputStream(bytesIn));
reader.readHeaders(bytesOut.size());
reader.emitReferenceSet();
List<String> receivedHeaders = reader.getAndReset();
assertEquals(sentHeaders, receivedHeaders);
}
private DataInputStream byteStream(int... bytes) {
byte[] data = intArrayToByteArray(bytes);
return new DataInputStream(new ByteArrayInputStream(data));
}
private byte[] literalHeaders(List<String> sentHeaders) throws IOException {
ByteArrayOutputStream headerBytes = new ByteArrayOutputStream();
new HpackDraft05.Writer(new DataOutputStream(headerBytes)).writeHeaders(sentHeaders);
return headerBytes.toByteArray();
}
private void assertBytes(int... bytes) {
byte[] expected = intArrayToByteArray(bytes);
byte[] actual = bytesOut.toByteArray();
assertEquals(Arrays.toString(expected), Arrays.toString(actual));
bytesOut.reset(); // So the next test starts with a clean slate.
}
private byte[] intArrayToByteArray(int[] bytes) {
byte[] data = new byte[bytes.length];
for (int i = 0; i < bytes.length; i++) {
data[i] = (byte) bytes[i];
}
return data;
}
private static class MutableByteArrayInputStream extends ByteArrayInputStream {
private MutableByteArrayInputStream() {
super(new byte[] { });
}
private void set(byte[] replacement) {
this.buf = replacement;
this.pos = 0;
this.count = replacement.length;
}
}
}

View File

@@ -1,123 +0,0 @@
/*
* Copyright (C) 2013 Square, Inc.
*
* 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.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class HpackTest {
private ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
private final Hpack.Writer hpackWriter = new Hpack.Writer(new DataOutputStream(bytesOut));
@Test public void readSingleByteInt() throws IOException {
assertEquals(10, new Hpack.Reader(byteStream(), true).readInt(10, 31));
assertEquals(10, new Hpack.Reader(byteStream(), true).readInt(0xe0 | 10, 31));
}
@Test public void readMultibyteInt() throws IOException {
assertEquals(1337, new Hpack.Reader(byteStream(154, 10), true).readInt(31, 31));
}
@Test public void writeSingleByteInt() throws IOException {
hpackWriter.writeInt(10, 31, 0);
assertBytes(10);
hpackWriter.writeInt(10, 31, 0xe0);
assertBytes(0xe0 | 10);
}
@Test public void writeMultibyteInt() throws IOException {
hpackWriter.writeInt(1337, 31, 0);
assertBytes(31, 154, 10);
hpackWriter.writeInt(1337, 31, 0xe0);
assertBytes(0xe0 | 31, 154, 10);
}
@Test public void max31BitValue() throws IOException {
hpackWriter.writeInt(0x7fffffff, 31, 0);
assertBytes(31, 224, 255, 255, 255, 7);
assertEquals(0x7fffffff,
new Hpack.Reader(byteStream(224, 255, 255, 255, 7), true).readInt(31, 31));
}
@Test public void prefixMask() throws IOException {
hpackWriter.writeInt(31, 31, 0);
assertBytes(31, 0);
assertEquals(31, new Hpack.Reader(byteStream(0), true).readInt(31, 31));
}
@Test public void prefixMaskMinusOne() throws IOException {
hpackWriter.writeInt(30, 31, 0);
assertBytes(30);
assertEquals(31, new Hpack.Reader(byteStream(0), true).readInt(31, 31));
}
@Test public void zero() throws IOException {
hpackWriter.writeInt(0, 31, 0);
assertBytes(0);
assertEquals(0, new Hpack.Reader(byteStream(), true).readInt(0, 31));
}
@Test public void headerName() throws IOException {
hpackWriter.writeString("foo");
assertBytes(3, 'f', 'o', 'o');
assertEquals("foo", new Hpack.Reader(byteStream(3, 'f', 'o', 'o'), true).readString());
}
@Test public void emptyHeaderName() throws IOException {
hpackWriter.writeString("");
assertBytes(0);
assertEquals("", new Hpack.Reader(byteStream(0), true).readString());
}
@Test public void headersRoundTrip() throws IOException {
List<String> sentHeaders = Arrays.asList("name", "value");
hpackWriter.writeHeaders(sentHeaders);
ByteArrayInputStream bytesIn = new ByteArrayInputStream(bytesOut.toByteArray());
Hpack.Reader reader = new Hpack.Reader(new DataInputStream(bytesIn), true);
reader.readHeaders(bytesOut.size());
reader.emitReferenceSet();
List<String> receivedHeaders = reader.getAndReset();
assertEquals(sentHeaders, receivedHeaders);
}
private DataInputStream byteStream(int... bytes) {
byte[] data = intArrayToByteArray(bytes);
return new DataInputStream(new ByteArrayInputStream(data));
}
private void assertBytes(int... bytes) {
byte[] expected = intArrayToByteArray(bytes);
byte[] actual = bytesOut.toByteArray();
assertEquals(Arrays.toString(expected), Arrays.toString(actual));
bytesOut.reset(); // So the next test starts with a clean slate.
}
private byte[] intArrayToByteArray(int[] bytes) {
byte[] data = new byte[bytes.length];
for (int i = 0; i < bytes.length; i++) {
data[i] = (byte) bytes[i];
}
return data;
}
}

View File

@@ -1,235 +0,0 @@
/*
* Copyright (C) 2013 Square, Inc.
*
* 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.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class Http20Draft06Test {
static final int expectedStreamId = 15;
@Test public void onlyOneLiteralHeadersFrame() throws IOException {
final List<String> sentHeaders = Arrays.asList("name", "value");
ByteArrayOutputStream out = new ByteArrayOutputStream();
DataOutputStream dataOut = new DataOutputStream(out);
// Write the headers frame, specifying no more frames are expected.
{
byte[] headerBytes = literalHeaders(sentHeaders);
dataOut.writeShort(headerBytes.length);
dataOut.write(Http20Draft06.TYPE_HEADERS);
dataOut.write(Http20Draft06.FLAG_END_HEADERS | Http20Draft06.FLAG_END_STREAM);
dataOut.writeInt(expectedStreamId & 0x7fffffff); // stream with reserved bit set
dataOut.write(headerBytes);
}
FrameReader fr = new Http20Draft06.Reader(new ByteArrayInputStream(out.toByteArray()), false);
// Consume the headers frame.
fr.nextFrame(new BaseTestHandler() {
@Override
public void headers(boolean outFinished, boolean inFinished, int streamId,
int associatedStreamId, int priority, List<String> nameValueBlock,
HeadersMode headersMode) {
assertFalse(outFinished);
assertTrue(inFinished);
assertEquals(expectedStreamId, streamId);
assertEquals(-1, associatedStreamId);
assertEquals(-1, priority);
assertEquals(sentHeaders, nameValueBlock);
assertEquals(HeadersMode.HTTP_20_HEADERS, headersMode);
}
});
}
@Test public void headersFrameThenContinuation() throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
DataOutputStream dataOut = new DataOutputStream(out);
// Write the first headers frame.
{
byte[] headerBytes = literalHeaders(Arrays.asList("foo", "bar"));
dataOut.writeShort(headerBytes.length);
dataOut.write(Http20Draft06.TYPE_HEADERS);
dataOut.write(0); // no flags
dataOut.writeInt(expectedStreamId & 0x7fffffff); // stream with reserved bit set
dataOut.write(headerBytes);
}
// Write the continuation frame, specifying no more frames are expected.
{
byte[] headerBytes = literalHeaders(Arrays.asList("baz", "qux"));
dataOut.writeShort(headerBytes.length);
dataOut.write(Http20Draft06.TYPE_CONTINUATION);
dataOut.write(Http20Draft06.FLAG_END_HEADERS | Http20Draft06.FLAG_END_STREAM);
dataOut.writeInt(expectedStreamId & 0x7fffffff); // stream with reserved bit set
dataOut.write(headerBytes);
}
FrameReader fr = new Http20Draft06.Reader(new ByteArrayInputStream(out.toByteArray()), false);
// Reading the above frames should result in a concatenated nameValueBlock.
fr.nextFrame(new BaseTestHandler() {
@Override
public void headers(boolean outFinished, boolean inFinished, int streamId,
int associatedStreamId, int priority, List<String> nameValueBlock,
HeadersMode headersMode) {
assertFalse(outFinished);
assertTrue(inFinished);
assertEquals(expectedStreamId, streamId);
assertEquals(-1, associatedStreamId);
assertEquals(-1, priority);
assertEquals(Arrays.asList("foo", "bar", "baz", "qux"), nameValueBlock);
assertEquals(HeadersMode.HTTP_20_HEADERS, headersMode);
}
});
}
/**
* HPACK has a max header table size, which can be smaller than the max header message.
* Ensure the larger header content is not lost.
*/
@Test public void tooLargeToHPackIsStillEmitted() throws IOException {
char[] tooLarge = new char[4096];
Arrays.fill(tooLarge, 'a');
final List<String> sentHeaders = Arrays.asList("foo", new String(tooLarge));
ByteArrayOutputStream out = new ByteArrayOutputStream();
DataOutputStream dataOut = new DataOutputStream(out);
writeOnlyHeadersFrame(literalHeaders(sentHeaders), dataOut);
FrameReader fr = new Http20Draft06.Reader(new ByteArrayInputStream(out.toByteArray()), false);
// Consume the large header set.
fr.nextFrame(new BaseTestHandler() {
@Override
public void headers(boolean outFinished, boolean inFinished, int streamId,
int associatedStreamId, int priority, List<String> nameValueBlock,
HeadersMode headersMode) {
assertEquals(sentHeaders, nameValueBlock);
}
});
}
@Test public void usingDraft06Examples() throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
DataOutputStream dataOut = new DataOutputStream(out);
writeOnlyHeadersFrame(firstHeaderSetBytes(), dataOut);
writeOnlyHeadersFrame(secondHeaderSetBytes(), dataOut);
FrameReader fr = new Http20Draft06.Reader(new ByteArrayInputStream(out.toByteArray()), false);
// Consume the first header set.
fr.nextFrame(new BaseTestHandler() {
@Override
public void headers(boolean outFinished, boolean inFinished, int streamId,
int associatedStreamId, int priority, List<String> nameValueBlock,
HeadersMode headersMode) {
assertEquals(Arrays.asList(":path", "/my-example/index.html", "user-agent", "my-user-agent",
"mynewheader", "first"), nameValueBlock);
}
});
// Consume the second header set.
fr.nextFrame(new BaseTestHandler() {
@Override
public void headers(boolean outFinished, boolean inFinished, int streamId,
int associatedStreamId, int priority, List<String> nameValueBlock,
HeadersMode headersMode) {
assertEquals(Arrays.asList(
":path", "/my-example/resources/script.js",
"user-agent", "my-user-agent",
"mynewheader", "second"
), nameValueBlock);
}
});
}
// Deviates from draft only to fix doc bugs noted in https://github.com/igrigorik/http-2 specs.
// http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-03#appendix-C.1
static byte[] firstHeaderSetBytes() {
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(0x44); // literal header with incremental indexing, name index = 3
out.write(0x16); // header value string length = 22
out.write("/my-example/index.html".getBytes(), 0, 22);
out.write(0x4C); // literal header with incremental indexing, name index = 11
out.write(0x0D); // header value string length = 13
out.write("my-user-agent".getBytes(), 0, 13);
out.write(0x40); // literal header with incremental indexing, new name
out.write(0x0B); // header name string length = 11
out.write("mynewheader".getBytes(), 0, 11);
out.write(0x05); // header value string length = 5
out.write("first".getBytes(), 0, 5);
return out.toByteArray();
}
// http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-03#appendix-C.2
static byte[] secondHeaderSetBytes() {
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(0x9e); // indexed header, index = 30: removal from reference set
out.write(0xa0); // indexed header, index = 32: removal from reference set
out.write(0x04); // literal header, substitution indexing, name index = 3
out.write(0x1e); // replaced entry index = 30
out.write(0x1f); // header value string length = 31
out.write("/my-example/resources/script.js".getBytes(), 0, 31);
out.write(0x5f);
out.write(0x02); // literal header, incremental indexing, name index = 32
out.write(0x06); // header value string length = 6
out.write("second".getBytes(), 0, 6);
return out.toByteArray();
}
static void writeOnlyHeadersFrame(byte[] headersSet, DataOutputStream dataOut)
throws IOException {
dataOut.writeShort(headersSet.length);
dataOut.write(Http20Draft06.TYPE_HEADERS);
dataOut.write(Http20Draft06.FLAG_END_HEADERS | Http20Draft06.FLAG_END_STREAM);
dataOut.writeInt(expectedStreamId & 0x7fffffff); // stream 15 with reserved bit set
dataOut.write(headersSet);
}
static byte[] literalHeaders(List<String> sentHeaders) throws IOException {
ByteArrayOutputStream headerBytes = new ByteArrayOutputStream();
new Hpack.Writer(new DataOutputStream(headerBytes)).writeHeaders(sentHeaders);
return headerBytes.toByteArray();
}
}

View File

@@ -0,0 +1,141 @@
/*
* Copyright (C) 2013 Square, Inc.
*
* 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.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class Http20Draft09Test {
static final int expectedStreamId = 15;
@Test public void onlyOneLiteralHeadersFrame() throws IOException {
final List<String> sentHeaders = Arrays.asList("name", "value");
ByteArrayOutputStream out = new ByteArrayOutputStream();
DataOutputStream dataOut = new DataOutputStream(out);
// Write the headers frame, specifying no more frames are expected.
{
byte[] headerBytes = literalHeaders(sentHeaders);
dataOut.writeShort(headerBytes.length);
dataOut.write(Http20Draft09.TYPE_HEADERS);
dataOut.write(Http20Draft09.FLAG_END_HEADERS | Http20Draft09.FLAG_END_STREAM);
dataOut.writeInt(expectedStreamId & 0x7fffffff); // stream with reserved bit set
dataOut.write(headerBytes);
}
FrameReader fr = new Http20Draft09.Reader(new ByteArrayInputStream(out.toByteArray()), false);
// Consume the headers frame.
fr.nextFrame(new BaseTestHandler() {
@Override
public void headers(boolean outFinished, boolean inFinished, int streamId,
int associatedStreamId, int priority, List<String> nameValueBlock,
HeadersMode headersMode) {
assertFalse(outFinished);
assertTrue(inFinished);
assertEquals(expectedStreamId, streamId);
assertEquals(-1, associatedStreamId);
assertEquals(-1, priority);
assertEquals(sentHeaders, nameValueBlock);
assertEquals(HeadersMode.HTTP_20_HEADERS, headersMode);
}
});
}
@Test public void headersFrameThenContinuation() throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
DataOutputStream dataOut = new DataOutputStream(out);
// Write the first headers frame.
{
byte[] headerBytes = literalHeaders(Arrays.asList("foo", "bar"));
dataOut.writeShort(headerBytes.length);
dataOut.write(Http20Draft09.TYPE_HEADERS);
dataOut.write(0); // no flags
dataOut.writeInt(expectedStreamId & 0x7fffffff); // stream with reserved bit set
dataOut.write(headerBytes);
}
// Write the continuation frame, specifying no more frames are expected.
{
byte[] headerBytes = literalHeaders(Arrays.asList("baz", "qux"));
dataOut.writeShort(headerBytes.length);
dataOut.write(Http20Draft09.TYPE_CONTINUATION);
dataOut.write(Http20Draft09.FLAG_END_HEADERS | Http20Draft09.FLAG_END_STREAM);
dataOut.writeInt(expectedStreamId & 0x7fffffff); // stream with reserved bit set
dataOut.write(headerBytes);
}
FrameReader fr = new Http20Draft09.Reader(new ByteArrayInputStream(out.toByteArray()), false);
// Reading the above frames should result in a concatenated nameValueBlock.
fr.nextFrame(new BaseTestHandler() {
@Override
public void headers(boolean outFinished, boolean inFinished, int streamId,
int associatedStreamId, int priority, List<String> nameValueBlock,
HeadersMode headersMode) {
assertFalse(outFinished);
assertFalse(inFinished);
assertEquals(expectedStreamId, streamId);
assertEquals(-1, associatedStreamId);
assertEquals(-1, priority);
assertEquals(Arrays.asList("foo", "bar", "baz", "qux"), nameValueBlock);
assertEquals(HeadersMode.HTTP_20_HEADERS, headersMode);
}
});
}
@Test public void readRstStreamFrame() throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
DataOutputStream dataOut = new DataOutputStream(out);
dataOut.writeShort(4);
dataOut.write(Http20Draft09.TYPE_RST_STREAM);
dataOut.write(0); // No flags
dataOut.writeInt(expectedStreamId & 0x7fffffff); // stream with reserved bit set
dataOut.writeInt(ErrorCode.COMPRESSION_ERROR.httpCode);
FrameReader fr = new Http20Draft09.Reader(new ByteArrayInputStream(out.toByteArray()), false);
// Consume the reset frame.
fr.nextFrame(new BaseTestHandler() {
@Override public void rstStream(int streamId, ErrorCode errorCode) {
assertEquals(expectedStreamId, streamId);
assertEquals(ErrorCode.COMPRESSION_ERROR, errorCode);
}
});
}
private byte[] literalHeaders(List<String> sentHeaders) throws IOException {
ByteArrayOutputStream headerBytes = new ByteArrayOutputStream();
new HpackDraft05.Writer(new DataOutputStream(headerBytes)).writeHeaders(sentHeaders);
return headerBytes.toByteArray();
}
}

View File

@@ -36,13 +36,21 @@ public final class SettingsTest {
@Test public void setFields() {
Settings settings = new Settings();
// WARNING: clash on flags between spdy/3 and http/2!
assertEquals(-3, settings.getUploadBandwidth(-3));
assertEquals(4096, settings.getHeaderTableSize());
settings.set(Settings.UPLOAD_BANDWIDTH, 0, 42);
assertEquals(42, settings.getUploadBandwidth(-3));
settings.set(Settings.HEADER_TABLE_SIZE, 0, 8096);
assertEquals(8096, settings.getHeaderTableSize());
// WARNING: clash on flags between spdy/3 and http/2!
assertEquals(-3, settings.getDownloadBandwidth(-3));
assertTrue(settings.getEnablePush());
settings.set(Settings.DOWNLOAD_BANDWIDTH, 0, 53);
assertEquals(53, settings.getDownloadBandwidth(-3));
settings.set(Settings.ENABLE_PUSH, 0, 0);
assertFalse(settings.getEnablePush());
assertEquals(-3, settings.getRoundTripTime(-3));
settings.set(Settings.ROUND_TRIP_TIME, 0, 64);

View File

@@ -64,10 +64,25 @@ import static java.net.HttpURLConnection.HTTP_PROXY_AUTH;
* should the attempt fail.
*/
public final class Connection implements Closeable {
private static final byte[] NPN_PROTOCOLS = new byte[] {
private static final byte[] ALL_PROTOCOLS = new byte[] {
17, 'H', 'T', 'T', 'P', '-', 'd', 'r', 'a', 'f', 't', '-', '0', '9', '/', '2', '.', '0',
6, 's', 'p', 'd', 'y', '/', '3',
8, 'h', 't', 't', 'p', '/', '1', '.', '1'
};
private static final byte[] SPDY_AND_HTTP = new byte[] {
6, 's', 'p', 'd', 'y', '/', '3',
8, 'h', 't', 't', 'p', '/', '1', '.', '1'
};
private static final byte[] HTTP2_AND_HTTP = new byte[] {
17, 'H', 'T', 'T', 'P', '-', 'd', 'r', 'a', 'f', 't', '-', '0', '9', '/', '2', '.', '0',
8, 'h', 't', 't', 'p', '/', '1', '.', '1'
};
private static final byte[] HTTP_20_DRAFT_09 = new byte[] {
'H', 'T', 'T', 'P', '-', 'd', 'r', 'a', 'f', 't', '-', '0', '9', '/', '2', '.', '0'
};
private static final byte[] SPDY3 = new byte[] {
's', 'p', 'd', 'y', '/', '3'
};
@@ -130,9 +145,20 @@ public final class Connection implements Closeable {
platform.supportTlsIntolerantServer(sslSocket);
}
boolean useNpn = route.modernTls && route.address.transports.contains("spdy/3");
boolean useNpn = route.modernTls && (
route.address.transports.contains("HTTP-draft-09/2.0")
|| route.address.transports.contains("spdy/3")
);
if (useNpn) {
platform.setNpnProtocols(sslSocket, NPN_PROTOCOLS);
if (route.address.transports.contains("HTTP-draft-09/2.0")
&& route.address.transports.contains("spdy/3")) {
platform.setNpnProtocols(sslSocket, ALL_PROTOCOLS);
} else if (route.address.transports.contains("HTTP-draft-09/2.0")) {
platform.setNpnProtocols(sslSocket, HTTP2_AND_HTTP);
} else {
platform.setNpnProtocols(sslSocket, SPDY_AND_HTTP);
}
}
// Force handshake. This can throw!
@@ -150,10 +176,13 @@ public final class Connection implements Closeable {
byte[] selectedProtocol;
if (useNpn && (selectedProtocol = platform.getNpnSelectedProtocol(sslSocket)) != null) {
if (Arrays.equals(selectedProtocol, SPDY3)) {
if (Arrays.equals(selectedProtocol, HTTP_20_DRAFT_09)
|| Arrays.equals(selectedProtocol, SPDY3)) {
SpdyConnection.Builder builder =
new SpdyConnection.Builder(route.address.getUriHost(), true, in, out);
if (Arrays.equals(selectedProtocol, HTTP_20_DRAFT_09)) builder.http20Draft09();
sslSocket.setSoTimeout(0); // SPDY timeouts are set per-stream.
spdyConnection = new SpdyConnection.Builder(route.address.getUriHost(), true, in, out)
.build();
spdyConnection = builder.build();
spdyConnection.sendConnectionHeader();
} else if (!Arrays.equals(selectedProtocol, HTTP_11)) {
throw new IOException(

View File

@@ -39,7 +39,7 @@ import javax.net.ssl.SSLSocketFactory;
/** Configures and creates HTTP connections. */
public final class OkHttpClient implements URLStreamHandlerFactory, Cloneable {
private static final List<String> DEFAULT_TRANSPORTS
= Util.immutableList(Arrays.asList("spdy/3", "http/1.1"));
= Util.immutableList(Arrays.asList("HTTP-draft-09/2.0", "spdy/3", "http/1.1"));
private final RouteDatabase routeDatabase;
private final Dispatcher dispatcher;

View File

@@ -0,0 +1,24 @@
/*
* Copyright (C) 2013 Square, Inc.
*
* 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.http;
public class HttpOverHttp20Draft09Test extends HttpOverSpdyTest {
public HttpOverHttp20Draft09Test() {
super("HTTP-draft-09/2.0");
this.hostHeader = ":authority";
}
}

View File

@@ -0,0 +1,23 @@
/*
* Copyright (C) 2013 Square, Inc.
*
* 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.http;
public class HttpOverSpdy3Test extends HttpOverSpdyTest {
public HttpOverSpdy3Test() {
super("spdy/3");
}
}

View File

@@ -56,7 +56,16 @@ import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/** Test how SPDY interacts with HTTP features. */
public final class HttpOverSpdyTest {
public abstract class HttpOverSpdyTest {
/** Transport to test, for example {@code spdy/3} */
private final String transport;
protected String hostHeader = ":host";
protected HttpOverSpdyTest(String transport){
this.transport = transport;
}
private static final HostnameVerifier NULL_HOSTNAME_VERIFIER = new HostnameVerifier() {
public boolean verify(String hostname, SSLSession session) {
return true;
@@ -71,10 +80,11 @@ public final class HttpOverSpdyTest {
@Before public void setUp() throws Exception {
server.useHttps(sslContext.getSocketFactory(), false);
client.setTransports(Arrays.asList(transport, "http/1.1"));
client.setSslSocketFactory(sslContext.getSocketFactory());
client.setHostnameVerifier(NULL_HOSTNAME_VERIFIER);
String systemTmpDir = System.getProperty("java.io.tmpdir");
File cacheDir = new File(systemTmpDir, "HttpCache-" + UUID.randomUUID());
File cacheDir = new File(systemTmpDir, "HttpCache-" + transport + "-" + UUID.randomUUID());
cache = new HttpResponseCache(cacheDir, Integer.MAX_VALUE);
}
@@ -96,7 +106,7 @@ public final class HttpOverSpdyTest {
RecordedRequest request = server.takeRequest();
assertEquals("GET /foo HTTP/1.1", request.getRequestLine());
assertContains(request.getHeaders(), ":scheme: https");
assertContains(request.getHeaders(), ":host: " + hostName + ":" + server.getPort());
assertContains(request.getHeaders(), hostHeader + ": " + hostName + ":" + server.getPort());
}
@Test public void emptyResponse() throws IOException {