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

Merge pull request #423 from adriancole/optimize-bitset

Change header table to array and track references with long
This commit is contained in:
Adrian Cole
2014-01-09 14:19:15 -08:00
5 changed files with 462 additions and 224 deletions

View File

@@ -5,12 +5,19 @@ import java.io.DataInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Arrays;
import java.util.List;
/**
* Read and write HPACK v05.
*
* http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-05
*
* This implementation uses an array for the header table with a bitset for
* references. Dynamic entries are added to the array, starting in the last
* position moving forward. When the array fills, it is doubled, up to the
* supported maximum of 64 headers. HTTP requests or responses that require
* more than 64 headers are hence not currently supported.
*/
final class HpackDraft05 {
@@ -19,35 +26,19 @@ final class HpackDraft05 {
final ByteString name;
final ByteString value;
final int size;
// Static entries can be shared safely, as long as {@code referenced} is not mutated.
final boolean isStatic;
// Only read when in headerTable.
// Mutable to avoid needing another BitSet for referenced header indexes. Using a BitSet for
// reference entries sounds good, except that entries are added at index zero. This implies
// shifting the BitSet, which would be expensive to implement.
boolean referenced = true;
HeaderEntry(ByteString name, ByteString value, boolean isStatic) {
this(name, value, 32 + name.size() + value.size(), isStatic);
HeaderEntry(String name, String value) {
this(ByteString.encodeUtf8(name), ByteString.encodeUtf8(value));
}
private HeaderEntry(ByteString name, ByteString value, int size, boolean isStatic) {
HeaderEntry(ByteString name, ByteString value) {
this(name, value, 32 + name.size() + value.size());
}
private HeaderEntry(ByteString name, ByteString value, int size) {
this.name = name;
this.value = value;
this.size = size;
this.isStatic = isStatic;
}
/** Adds name and value, if this entry is referenced. */
void addTo(List<ByteString> out) {
if (!referenced) return;
out.add(name);
out.add(value);
}
/** Copies this header entry and designates it as not a static entry. */
@Override public HeaderEntry clone() {
return new HeaderEntry(name, value, size, false);
}
}
@@ -56,66 +47,66 @@ final class HpackDraft05 {
private static final int PREFIX_8_BITS = 0xff;
private static final HeaderEntry[] STATIC_HEADER_TABLE = new HeaderEntry[] {
staticEntry(":authority", ""),
staticEntry(":method", "GET"),
staticEntry(":method", "POST"),
staticEntry(":path", "/"),
staticEntry(":path", "/index.html"),
staticEntry(":scheme", "http"),
staticEntry(":scheme", "https"),
staticEntry(":status", "200"),
staticEntry(":status", "500"),
staticEntry(":status", "404"),
staticEntry(":status", "403"),
staticEntry(":status", "400"),
staticEntry(":status", "401"),
staticEntry("accept-charset", ""),
staticEntry("accept-encoding", ""),
staticEntry("accept-language", ""),
staticEntry("accept-ranges", ""),
staticEntry("accept", ""),
staticEntry("access-control-allow-origin", ""),
staticEntry("age", ""),
staticEntry("allow", ""),
staticEntry("authorization", ""),
staticEntry("cache-control", ""),
staticEntry("content-disposition", ""),
staticEntry("content-encoding", ""),
staticEntry("content-language", ""),
staticEntry("content-length", ""),
staticEntry("content-location", ""),
staticEntry("content-range", ""),
staticEntry("content-type", ""),
staticEntry("cookie", ""),
staticEntry("date", ""),
staticEntry("etag", ""),
staticEntry("expect", ""),
staticEntry("expires", ""),
staticEntry("from", ""),
staticEntry("host", ""),
staticEntry("if-match", ""),
staticEntry("if-modified-since", ""),
staticEntry("if-none-match", ""),
staticEntry("if-range", ""),
staticEntry("if-unmodified-since", ""),
staticEntry("last-modified", ""),
staticEntry("link", ""),
staticEntry("location", ""),
staticEntry("max-forwards", ""),
staticEntry("proxy-authenticate", ""),
staticEntry("proxy-authorization", ""),
staticEntry("range", ""),
staticEntry("referer", ""),
staticEntry("refresh", ""),
staticEntry("retry-after", ""),
staticEntry("server", ""),
staticEntry("set-cookie", ""),
staticEntry("strict-transport-security", ""),
staticEntry("transfer-encoding", ""),
staticEntry("user-agent", ""),
staticEntry("vary", ""),
staticEntry("via", ""),
staticEntry("www-authenticate", "")
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(":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("accept-ranges", ""),
new HeaderEntry("accept", ""),
new HeaderEntry("access-control-allow-origin", ""),
new HeaderEntry("age", ""),
new HeaderEntry("allow", ""),
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", "")
};
private HpackDraft05() {
@@ -129,10 +120,25 @@ final class HpackDraft05 {
private long bytesLeft = 0;
// Visible for testing.
final List<HeaderEntry> headerTable = new ArrayList<HeaderEntry>(5); // average of 5 headers
final BitSet staticReferenceSet = new BitSet();
long headerTableSize = 0;
long maxHeaderTableSize = 4096; // TODO: needs to come from SETTINGS_HEADER_TABLE_SIZE.
HeaderEntry[] headerTable = new HeaderEntry[8]; // must be less than 64
// Array is populated back to front, so new entries always have lowest index.
int nextHeaderIndex = headerTable.length - 1;
int headerCount = 0;
/**
* Set bit positions indicate {@code headerTable[pos]} should be emitted.
*/
// Using a long since the reference table < 64 entries.
long referencedHeaders = 0x0000000000000000L;
/**
* Set bit positions indicate {@code STATIC_HEADER_TABLE[pos]} should be
* emitted.
*/
// Using a long since the static table < 64 entries.
long referencedStaticHeaders = 0x0000000000000000L;
int headerTableByteCount = 0;
int maxHeaderTableByteCount = 4096; // TODO: needs to come from SETTINGS_HEADER_TABLE_SIZE.
Reader(DataInputStream in) {
this.in = in;
@@ -149,51 +155,47 @@ final class HpackDraft05 {
while (bytesLeft > 0) {
int b = readByte();
if ((b & 0x80) != 0) {
if (b == 0x80) { // 10000000
clearReferenceSet();
} else if ((b & 0x80) == 0x80) { // 1NNNNNNN
int index = readInt(b, PREFIX_7_BITS);
if (index == 0) {
clearReferenceSet();
readIndexedHeader(index - 1);
} else { // 0NNNNNNN
if (b == 0x40) { // 01000000
readLiteralHeaderWithoutIndexingNewName();
} else if ((b & 0xe0) == 0x40) { // 01NNNNNN
int index = readInt(b, PREFIX_6_BITS);
readLiteralHeaderWithoutIndexingIndexedName(index - 1);
} else if (b == 0) { // 00000000
readLiteralHeaderWithIncrementalIndexingNewName();
} else if ((b & 0xc0) == 0) { // 00NNNNNN
int index = readInt(b, PREFIX_6_BITS);
readLiteralHeaderWithIncrementalIndexingIndexedName(index - 1);
} else {
readIndexedHeader(index - 1);
// TODO: we should throw something that we can coerce to a PROTOCOL_ERROR
throw new AssertionError("unhandled byte: " + Integer.toBinaryString(b));
}
} else if (b == 0x40) {
readLiteralHeaderWithoutIndexingNewName();
} else if ((b & 0xe0) == 0x40) {
int index = readInt(b, PREFIX_6_BITS);
readLiteralHeaderWithoutIndexingIndexedName(index - 1);
} else if (b == 0) {
readLiteralHeaderWithIncrementalIndexingNewName();
} else if ((b & 0xc0) == 0) {
int index = readInt(b, PREFIX_6_BITS);
readLiteralHeaderWithIncrementalIndexingIndexedName(index - 1);
} else {
// TODO: we should throw something that we can coerce to a PROTOCOL_ERROR
throw new AssertionError("unhandled byte: " + Integer.toBinaryString(b));
}
}
}
private void clearReferenceSet() {
staticReferenceSet.clear();
for (int i = 0, size = headerTable.size(); i < size; i++) {
HeaderEntry entry = headerTable.get(i);
if (entry.isStatic) { // lazy clone static entries on mutation.
entry = entry.clone();
entry.referenced = false;
headerTable.set(i, entry);
} else {
entry.referenced = false;
}
}
referencedStaticHeaders = 0x0000000000000000L;
referencedHeaders = 0x0000000000000000L;
}
public void emitReferenceSet() {
for (int i = staticReferenceSet.nextSetBit(0); i != -1;
i = staticReferenceSet.nextSetBit(i + 1)) {
STATIC_HEADER_TABLE[i].addTo(emittedHeaders);
for (int i = 0; i < STATIC_HEADER_TABLE.length; ++i) {
if (bitPositionSet(referencedStaticHeaders, i)) {
emittedHeaders.add(STATIC_HEADER_TABLE[i].name);
emittedHeaders.add(STATIC_HEADER_TABLE[i].value);
}
}
for (int i = headerTable.size() - 1; i != -1; i--) {
headerTable.get(i).addTo(emittedHeaders);
for (int i = headerTable.length - 1; i != nextHeaderIndex; --i) {
if (bitPositionSet(referencedHeaders, i)) {
emittedHeaders.add(headerTable[i].name);
emittedHeaders.add(headerTable[i].value);
}
}
}
@@ -208,33 +210,36 @@ final class HpackDraft05 {
}
private void readIndexedHeader(int index) {
if (isStaticHeader(index)) {
if (maxHeaderTableSize == 0) {
staticReferenceSet.set(index - headerTable.size());
if (maxHeaderTableByteCount == 0) {
// Set bit designating this static entry is referenced.
referencedStaticHeaders |= (1L << (index - headerCount));
} else {
HeaderEntry staticEntry = STATIC_HEADER_TABLE[index - headerTable.size()];
HeaderEntry staticEntry = STATIC_HEADER_TABLE[index - headerCount];
insertIntoHeaderTable(-1, staticEntry);
}
} else if (!headerTable.get(index).referenced) {
HeaderEntry existing = headerTable.get(index);
existing.referenced = true;
insertIntoHeaderTable(index, existing);
}
} else if (!bitPositionSet(referencedHeaders, headerTableIndex(index))) {
referencedHeaders |= (1L << headerTableIndex(index));
} else {
// TODO: we should throw something that we can coerce to a PROTOCOL_ERROR
throw new AssertionError("invalid index " + index);
}
}
private void readLiteralHeaderWithoutIndexingIndexedName(int index)
throws IOException {
// referencedHeaders is relative to nextHeaderIndex + 1.
private int headerTableIndex(int index) {
return nextHeaderIndex + 1 + index;
}
private void readLiteralHeaderWithoutIndexingIndexedName(int index) throws IOException {
ByteString name = getName(index);
ByteString value = readString();
emittedHeaders.add(name);
emittedHeaders.add(value);
}
private void readLiteralHeaderWithoutIndexingNewName()
throws IOException {
private void readLiteralHeaderWithoutIndexingNewName() throws IOException {
ByteString name = readString();
ByteString value = readString();
emittedHeaders.add(name);
@@ -245,60 +250,89 @@ final class HpackDraft05 {
throws IOException {
ByteString name = getName(nameIndex);
ByteString value = readString();
insertIntoHeaderTable(-1, new HeaderEntry(name, value, false));
insertIntoHeaderTable(-1, new HeaderEntry(name, value));
}
private void readLiteralHeaderWithIncrementalIndexingNewName() throws IOException {
ByteString name = readString();
ByteString value = readString();
insertIntoHeaderTable(-1, new HeaderEntry(name, value, false));
insertIntoHeaderTable(-1, new HeaderEntry(name, value));
}
private ByteString getName(int index) {
if (isStaticHeader(index)) {
return STATIC_HEADER_TABLE[index - headerTable.size()].name;
return STATIC_HEADER_TABLE[index - headerCount].name;
} else {
return headerTable.get(index).name;
return headerTable[headerTableIndex(index)].name;
}
}
private boolean isStaticHeader(int index) {
return index >= headerTable.size();
return index >= headerCount;
}
/** index == -1 when new. */
private void insertIntoHeaderTable(int index, HeaderEntry entry) {
int delta = entry.size;
if (index != -1) { // Index -1 == new header.
delta -= headerTable.get(index).size;
delta -= headerTable[headerTableIndex(index)].size;
}
// if the new or replacement header is too big, drop all entries.
if (delta > maxHeaderTableSize) {
staticReferenceSet.clear();
headerTable.clear();
headerTableSize = 0;
if (delta > maxHeaderTableByteCount) {
referencedStaticHeaders = 0x0000000000000000L;
referencedHeaders = 0x0000000000000000L;
Arrays.fill(headerTable, null);
nextHeaderIndex = headerTable.length - 1;
headerCount = 0;
headerTableByteCount = 0;
// emit the large header to the callback.
entry.addTo(emittedHeaders);
emittedHeaders.add(entry.name);
emittedHeaders.add(entry.value);
return;
}
// Evict headers to the required length.
while (headerTableSize + delta > maxHeaderTableSize) {
remove(headerTable.size() - 1);
int bytesToRecover = (headerTableByteCount + delta) - maxHeaderTableByteCount;
int entriesToEvict = 0;
if (bytesToRecover > 0) {
// determine how many headers need to be evicted.
for (int j = headerTable.length - 1; j >= nextHeaderIndex && bytesToRecover > 0; j--) {
bytesToRecover -= headerTable[j].size;
headerTableByteCount -= headerTable[j].size;
headerCount--;
entriesToEvict++;
}
// shift elements over
referencedHeaders = referencedHeaders << entriesToEvict;
System.arraycopy(headerTable, nextHeaderIndex + 1, headerTable,
nextHeaderIndex + 1 + entriesToEvict, headerCount);
nextHeaderIndex += entriesToEvict;
}
if (index == -1) {
headerTable.add(0, entry);
if (headerCount + 1 > headerTable.length) {
if (headerTable.length == 64) {
// We would need to switch off long to bitset to support > 64 headers.
throw new UnsupportedOperationException(
"Header tables with count > 64 not yet supported!");
}
HeaderEntry[] doubled = new HeaderEntry[headerTable.length * 2];
System.arraycopy(headerTable, 0, doubled, headerTable.length, headerTable.length);
referencedHeaders = referencedHeaders << headerTable.length;
nextHeaderIndex = headerTable.length - 1;
headerTable = doubled;
}
index = nextHeaderIndex--;
referencedHeaders |= (1L << index);
headerTable[index] = entry;
headerCount++;
} else { // Replace value at same position.
headerTable.set(index, entry);
index += headerTableIndex(index) + entriesToEvict;
referencedHeaders |= (1L << index);
headerTable[index] = entry;
}
headerTableSize += delta;
}
private void remove(int index) {
headerTableSize -= headerTable.remove(index).size;
headerTableByteCount += delta;
}
private int readByte() throws IOException {
@@ -335,13 +369,19 @@ final class HpackDraft05 {
public ByteString readString() throws IOException {
int firstByte = readByte();
int length = readInt(firstByte, PREFIX_8_BITS);
byte[] encoded = new byte[length];
if ((length & 0x80) == 0x80) { // 1NNNNNNN
length &= ~0x80;
// TODO: actually decode huffman!
}
bytesLeft -= length;
in.readFully(encoded);
return ByteString.of(encoded);
return ByteString.read(in, length);
}
}
static boolean bitPositionSet(long referenceBitSet, int i) {
return ((referenceBitSet >> i) & 1L) == 1;
}
static class Writer {
private final OutputStream out;
@@ -383,8 +423,4 @@ final class HpackDraft05 {
data.write(out);
}
}
private static HeaderEntry staticEntry(String name, String value) {
return new HeaderEntry(ByteString.encodeUtf8(name), ByteString.encodeUtf8(value), true);
}
}

View File

@@ -150,7 +150,7 @@ public final class Http20Draft09 implements Variant {
return true;
}
throw new UnsupportedOperationException("TODO");
throw new UnsupportedOperationException(Integer.toBinaryString(type));
}
private void readHeaders(Handler handler, int flags, int length, int streamId)

View File

@@ -27,7 +27,11 @@ import org.junit.Before;
import org.junit.Test;
import static com.squareup.okhttp.internal.Util.byteStringList;
import static com.squareup.okhttp.internal.spdy.HpackDraft05.bitPositionSet;
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 HpackDraft05Test {
@@ -43,18 +47,137 @@ public class HpackDraft05Test {
* 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<ByteString> sentHeaders = byteStringList("foo", new String(tooLarge));
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.maxHeaderTableByteCount = 1;
hpackReader.readHeaders(out.size());
hpackReader.emitReferenceSet();
assertEquals(0, hpackReader.headerCount);
assertEquals(byteStringList("custom-key", "custom-header"), hpackReader.getAndReset());
}
/** Oldest entries are evicted to support newer ones. */
@Test public void testEviction() throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(0x00); // Literal indexed
out.write(0x0a); // Literal name (len = 10)
out.write("custom-foo".getBytes(), 0, 10);
out.write(0x0d); // Literal value (len = 13)
out.write("custom-header".getBytes(), 0, 13);
out.write(0x00); // Literal indexed
out.write(0x0a); // Literal name (len = 10)
out.write("custom-bar".getBytes(), 0, 10);
out.write(0x0d); // Literal value (len = 13)
out.write("custom-header".getBytes(), 0, 13);
out.write(0x00); // Literal indexed
out.write(0x0a); // Literal name (len = 10)
out.write("custom-baz".getBytes(), 0, 10);
out.write(0x0d); // Literal value (len = 13)
out.write("custom-header".getBytes(), 0, 13);
bytesIn.set(out.toByteArray());
hpackReader.maxHeaderTableByteCount = 110;
hpackReader.readHeaders(out.size());
hpackReader.emitReferenceSet();
assertEquals(2, hpackReader.headerCount);
HpackDraft05.HeaderEntry entry = hpackReader.headerTable[headerTableLength() - 1];
checkEntry(entry, "custom-bar", "custom-header", 55);
assertHeaderReferenced(headerTableLength() - 1);
entry = hpackReader.headerTable[headerTableLength() - 2];
checkEntry(entry, "custom-baz", "custom-header", 55);
assertHeaderReferenced(headerTableLength() - 2);
// foo isn't here as it is no longer in the table.
// TODO: emit before eviction?
assertEquals(byteStringList("custom-bar", "custom-header", "custom-baz", "custom-header"),
hpackReader.getAndReset());
}
/** Header table backing array is initially 8 long, let's ensure it grows. */
@Test public void dynamicallyGrowsUpTo64Entries() throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
for (int i = 0; i < 64; i++) {
out.write(0x00); // Literal indexed
out.write(0x0a); // Literal name (len = 10)
out.write("custom-foo".getBytes(), 0, 10);
out.write(0x0d); // Literal value (len = 13)
out.write("custom-header".getBytes(), 0, 13);
}
ByteArrayOutputStream out = literalHeaders(sentHeaders);
bytesIn.set(out.toByteArray());
hpackReader.readHeaders(out.size());
hpackReader.emitReferenceSet();
assertEquals(0, hpackReader.headerTable.size());
assertEquals(64, hpackReader.headerCount);
}
assertEquals(sentHeaders, hpackReader.getAndReset());
@Test public void greaterThan64HeadersNotYetSupported() throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
for (int i = 0; i < 65; i++) {
out.write(0x00); // Literal indexed
out.write(0x0a); // Literal name (len = 10)
out.write("custom-foo".getBytes(), 0, 10);
out.write(0x0d); // Literal value (len = 13)
out.write("custom-header".getBytes(), 0, 13);
}
bytesIn.set(out.toByteArray());
try {
hpackReader.readHeaders(out.size());
fail();
} catch (UnsupportedOperationException expected) {
}
}
/** Huffman headers are accepted, but come out as garbage for now. */
@Test public void huffmanDecodingNotYetSupported() throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write(0x04); // == Literal indexed ==
// Indexed name (idx = 4) -> :path
out.write(0x8b); // Literal value Huffman encoded 11 bytes
// decodes to www.example.com which is length 15
byte[] huffmanBytes = new byte[] {
(byte) 0xdb, (byte) 0x6d, (byte) 0x88, (byte) 0x3e,
(byte) 0x68, (byte) 0xd1, (byte) 0xcb, (byte) 0x12,
(byte) 0x25, (byte) 0xba, (byte) 0x7f};
out.write(huffmanBytes, 0, huffmanBytes.length);
bytesIn.set(out.toByteArray());
hpackReader.readHeaders(out.size());
hpackReader.emitReferenceSet();
assertEquals(1, hpackReader.headerCount);
// this will change when we decode huffman
assertEquals(48, hpackReader.headerTableByteCount);
HpackDraft05.HeaderEntry entry = hpackReader.headerTable[headerTableLength() - 1];
// TODO: huffman bytes are not what we want!
checkEntry(entry, ":path", new String(huffmanBytes, "UTF-8"), 48);
assertHeaderReferenced(headerTableLength() - 1);
}
/**
@@ -74,11 +197,12 @@ public class HpackDraft05Test {
hpackReader.readHeaders(out.size());
hpackReader.emitReferenceSet();
assertEquals(1, hpackReader.headerTable.size());
assertEquals(55, hpackReader.headerTableSize);
assertEquals(1, hpackReader.headerCount);
assertEquals(55, hpackReader.headerTableByteCount);
HpackDraft05.HeaderEntry entry = hpackReader.headerTable.get(0);
checkEntry(entry, "custom-key", "custom-header", 55, true);
HpackDraft05.HeaderEntry entry = hpackReader.headerTable[headerTableLength() - 1];
checkEntry(entry, "custom-key", "custom-header", 55);
assertHeaderReferenced(headerTableLength() - 1);
assertEquals(byteStringList("custom-key", "custom-header"), hpackReader.getAndReset());
}
@@ -98,7 +222,7 @@ public class HpackDraft05Test {
hpackReader.readHeaders(out.size());
hpackReader.emitReferenceSet();
assertEquals(0, hpackReader.headerTable.size());
assertEquals(0, hpackReader.headerCount);
assertEquals(byteStringList(":path", "/sample/path"), hpackReader.getAndReset());
}
@@ -116,11 +240,12 @@ public class HpackDraft05Test {
hpackReader.readHeaders(out.size());
hpackReader.emitReferenceSet();
assertEquals(1, hpackReader.headerTable.size());
assertEquals(42, hpackReader.headerTableSize);
assertEquals(1, hpackReader.headerCount);
assertEquals(42, hpackReader.headerTableByteCount);
HpackDraft05.HeaderEntry entry = hpackReader.headerTable.get(0);
checkEntry(entry, ":method", "GET", 42, true);
HpackDraft05.HeaderEntry entry = hpackReader.headerTable[headerTableLength() - 1];
checkEntry(entry, ":method", "GET", 42);
assertHeaderReferenced(headerTableLength() - 1);
assertEquals(byteStringList(":method", "GET"), hpackReader.getAndReset());
}
@@ -135,12 +260,12 @@ public class HpackDraft05Test {
// idx = 2 -> :method: GET
bytesIn.set(out.toByteArray());
hpackReader.maxHeaderTableSize = 0; // SETTINGS_HEADER_TABLE_SIZE == 0
hpackReader.maxHeaderTableByteCount = 0; // SETTINGS_HEADER_TABLE_SIZE == 0
hpackReader.readHeaders(out.size());
hpackReader.emitReferenceSet();
// Not buffered in header table.
assertEquals(0, hpackReader.headerTable.size());
assertEquals(0, hpackReader.headerCount);
assertEquals(byteStringList(":method", "GET"), hpackReader.getAndReset());
}
@@ -186,26 +311,30 @@ public class HpackDraft05Test {
}
private void checkFirstRequestWithoutHuffman() {
assertEquals(4, hpackReader.headerTable.size());
assertEquals(4, hpackReader.headerCount);
// [ 1] (s = 57) :authority: www.example.com
HpackDraft05.HeaderEntry entry = hpackReader.headerTable.get(0);
checkEntry(entry, ":authority", "www.example.com", 57, true);
HpackDraft05.HeaderEntry entry = hpackReader.headerTable[headerTableLength() - 4];
checkEntry(entry, ":authority", "www.example.com", 57);
assertHeaderReferenced(headerTableLength() - 4);
// [ 2] (s = 38) :path: /
entry = hpackReader.headerTable.get(1);
checkEntry(entry, ":path", "/", 38, true);
entry = hpackReader.headerTable[headerTableLength() - 3];
checkEntry(entry, ":path", "/", 38);
assertHeaderReferenced(headerTableLength() - 3);
// [ 3] (s = 43) :scheme: http
entry = hpackReader.headerTable.get(2);
checkEntry(entry, ":scheme", "http", 43, true);
entry = hpackReader.headerTable[headerTableLength() - 2];
checkEntry(entry, ":scheme", "http", 43);
assertHeaderReferenced(headerTableLength() - 2);
// [ 4] (s = 42) :method: GET
entry = hpackReader.headerTable.get(3);
checkEntry(entry, ":method", "GET", 42, true);
entry = hpackReader.headerTable[headerTableLength() - 1];
checkEntry(entry, ":method", "GET", 42);
assertHeaderReferenced(headerTableLength() - 1);
// Table size: 180
assertEquals(180, hpackReader.headerTableSize);
assertEquals(180, hpackReader.headerTableByteCount);
// Decoded header set:
assertEquals(byteStringList(
@@ -227,30 +356,35 @@ public class HpackDraft05Test {
}
private void checkSecondRequestWithoutHuffman() {
assertEquals(5, hpackReader.headerTable.size());
assertEquals(5, hpackReader.headerCount);
// [ 1] (s = 53) cache-control: no-cache
HpackDraft05.HeaderEntry entry = hpackReader.headerTable.get(0);
checkEntry(entry, "cache-control", "no-cache", 53, true);
HpackDraft05.HeaderEntry entry = hpackReader.headerTable[headerTableLength() - 5];
checkEntry(entry, "cache-control", "no-cache", 53);
assertHeaderReferenced(headerTableLength() - 5);
// [ 2] (s = 57) :authority: www.example.com
entry = hpackReader.headerTable.get(1);
checkEntry(entry, ":authority", "www.example.com", 57, true);
entry = hpackReader.headerTable[headerTableLength() - 4];
checkEntry(entry, ":authority", "www.example.com", 57);
assertHeaderReferenced(headerTableLength() - 4);
// [ 3] (s = 38) :path: /
entry = hpackReader.headerTable.get(2);
checkEntry(entry, ":path", "/", 38, true);
entry = hpackReader.headerTable[headerTableLength() - 3];
checkEntry(entry, ":path", "/", 38);
assertHeaderReferenced(headerTableLength() - 3);
// [ 4] (s = 43) :scheme: http
entry = hpackReader.headerTable.get(3);
checkEntry(entry, ":scheme", "http", 43, true);
entry = hpackReader.headerTable[headerTableLength() - 2];
checkEntry(entry, ":scheme", "http", 43);
assertHeaderReferenced(headerTableLength() - 2);
// [ 5] (s = 42) :method: GET
entry = hpackReader.headerTable.get(4);
checkEntry(entry, ":method", "GET", 42, true);
entry = hpackReader.headerTable[headerTableLength() - 1];
checkEntry(entry, ":method", "GET", 42);
assertHeaderReferenced(headerTableLength() - 1);
// Table size: 233
assertEquals(233, hpackReader.headerTableSize);
assertEquals(233, hpackReader.headerTableByteCount);
// Decoded header set:
assertEquals(byteStringList(
@@ -283,42 +417,50 @@ public class HpackDraft05Test {
}
private void checkThirdRequestWithoutHuffman() {
assertEquals(8, hpackReader.headerTable.size());
assertEquals(8, hpackReader.headerCount);
// [ 1] (s = 54) custom-key: custom-value
HpackDraft05.HeaderEntry entry = hpackReader.headerTable.get(0);
checkEntry(entry, "custom-key", "custom-value", 54, true);
HpackDraft05.HeaderEntry entry = hpackReader.headerTable[headerTableLength() - 8];
checkEntry(entry, "custom-key", "custom-value", 54);
assertHeaderReferenced(headerTableLength() - 8);
// [ 2] (s = 48) :path: /index.html
entry = hpackReader.headerTable.get(1);
checkEntry(entry, ":path", "/index.html", 48, true);
entry = hpackReader.headerTable[headerTableLength() - 7];
checkEntry(entry, ":path", "/index.html", 48);
assertHeaderReferenced(headerTableLength() - 7);
// [ 3] (s = 44) :scheme: https
entry = hpackReader.headerTable.get(2);
checkEntry(entry, ":scheme", "https", 44, true);
entry = hpackReader.headerTable[headerTableLength() - 6];
checkEntry(entry, ":scheme", "https", 44);
assertHeaderReferenced(headerTableLength() - 6);
// [ 4] (s = 53) cache-control: no-cache
entry = hpackReader.headerTable.get(3);
checkEntry(entry, "cache-control", "no-cache", 53, false);
entry = hpackReader.headerTable[headerTableLength() - 5];
checkEntry(entry, "cache-control", "no-cache", 53);
assertHeaderNotReferenced(headerTableLength() - 5);
// [ 5] (s = 57) :authority: www.example.com
entry = hpackReader.headerTable.get(4);
checkEntry(entry, ":authority", "www.example.com", 57, true);
entry = hpackReader.headerTable[headerTableLength() - 4];
checkEntry(entry, ":authority", "www.example.com", 57);
assertHeaderReferenced(headerTableLength() - 4);
// [ 6] (s = 38) :path: /
entry = hpackReader.headerTable.get(5);
checkEntry(entry, ":path", "/", 38, false);
entry = hpackReader.headerTable[headerTableLength() - 3];
checkEntry(entry, ":path", "/", 38);
assertHeaderNotReferenced(headerTableLength() - 3);
// [ 7] (s = 43) :scheme: http
entry = hpackReader.headerTable.get(6);
checkEntry(entry, ":scheme", "http", 43, false);
entry = hpackReader.headerTable[headerTableLength() - 2];
checkEntry(entry, ":scheme", "http", 43);
assertHeaderNotReferenced(headerTableLength() - 2);
// [ 8] (s = 42) :method: GET
entry = hpackReader.headerTable.get(7);
checkEntry(entry, ":method", "GET", 42, true);
entry = hpackReader.headerTable[headerTableLength() - 1];
checkEntry(entry, ":method", "GET", 42);
assertHeaderReferenced(headerTableLength() - 1);
// Table size: 379
assertEquals(379, hpackReader.headerTableSize);
assertEquals(379, hpackReader.headerTableByteCount);
// Decoded header set:
// TODO: order is not correct per docs, but then again, the spec doesn't require ordering.
@@ -331,7 +473,8 @@ public class HpackDraft05Test {
}
private ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
private final HpackDraft05.Writer hpackWriter = new HpackDraft05.Writer(new DataOutputStream(bytesOut));
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));
@@ -415,12 +558,10 @@ public class HpackDraft05Test {
return headerBytes;
}
private void checkEntry(HpackDraft05.HeaderEntry entry, String name, String value, int size,
boolean referenced) {
private void checkEntry(HpackDraft05.HeaderEntry entry, String name, String value, int size) {
assertEquals(name, entry.name.utf8());
assertEquals(value, entry.value.utf8());
assertEquals(size, entry.size);
assertEquals(referenced, entry.referenced);
}
private void assertBytes(int... bytes) {
@@ -438,6 +579,18 @@ public class HpackDraft05Test {
return data;
}
private void assertHeaderReferenced(int index) {
assertTrue(bitPositionSet(hpackReader.referencedHeaders, index));
}
private void assertHeaderNotReferenced(int index) {
assertFalse(bitPositionSet(hpackReader.referencedHeaders, index));
}
private int headerTableLength() {
return hpackReader.headerTable.length;
}
private static class MutableByteArrayInputStream extends ByteArrayInputStream {
private MutableByteArrayInputStream() {

View File

@@ -154,7 +154,7 @@ public final class SpdyTransport implements Transport {
throw new IllegalArgumentException("Unexpected name value block: " + nameValueBlock);
}
String status = null;
String version = null;
String version = "HTTP/1.1"; // TODO: why are we expecting :version?
Headers.Builder headersBuilder = new Headers.Builder();
headersBuilder.set(OkHeaders.SELECTED_TRANSPORT, protocol);

View File

@@ -0,0 +1,49 @@
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.squareup.okhttp.internal.http;
import com.squareup.okhttp.OkHttpClient;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSession;
public final class ExternalHttp2Example {
public static void main(String[] args) throws Exception {
URL url = new URL("https://twitter.com/");
HttpsURLConnection connection = (HttpsURLConnection) new OkHttpClient().open(url);
connection.setHostnameVerifier(new HostnameVerifier() {
@Override public boolean verify(String s, SSLSession sslSession) {
System.out.println("VERIFYING " + s);
return true;
}
});
int responseCode = connection.getResponseCode();
System.out.println(responseCode);
BufferedReader reader =
new BufferedReader(new InputStreamReader(connection.getInputStream(), "UTF-8"));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}
}