mirror of
https://github.com/square/okhttp.git
synced 2026-01-24 04:02:07 +03:00
Merge pull request #400 from adriancole/http2-draft09
update http2 to draft 9 and hpack to draft 5
This commit is contained in:
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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. */
|
||||
@@ -153,7 +153,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;
|
||||
@@ -418,8 +418,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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user