Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -490,7 +490,7 @@ void writeHeaders(List<io.grpc.okhttp.internal.framed.Header> headerBlock) throw
out.writeByte(0x40);
writeByteString(name);
writeByteString(value);
insertIntoDynamicTable(new io.grpc.okhttp.internal.framed.Header(name, value));
insertIntoDynamicTable(canonicalHeader(header, name, value));
} else if (name.startsWith(PSEUDO_PREFIX)
&& !io.grpc.okhttp.internal.framed.Header.TARGET_AUTHORITY.equals(name)
&& !io.grpc.okhttp.internal.framed.Header.TARGET_PATH.equals(name)) {
Expand All @@ -504,11 +504,25 @@ void writeHeaders(List<io.grpc.okhttp.internal.framed.Header> headerBlock) throw
// Literal Header Field with Incremental Indexing - Indexed Name.
writeInt(headerNameIndex, PREFIX_6_BITS, 0x40);
writeByteString(value);
insertIntoDynamicTable(new io.grpc.okhttp.internal.framed.Header(name, value));
insertIntoDynamicTable(canonicalHeader(header, name, value));
}
}
}

/**
* Returns {@code header} as-is when its name is already the lowercase {@code name} used for
* indexing, avoiding an extra Header allocation on the common path where header names are
* already lowercase (e.g. gRPC's generated headers and the {@code :path}/{@code :authority}
* pseudo headers). A value comparison detects that case while also avoiding an allocation if
* an equivalent lowercase name is represented by a different {@link ByteString} instance.
*/
private static io.grpc.okhttp.internal.framed.Header canonicalHeader(
io.grpc.okhttp.internal.framed.Header header, ByteString name, ByteString value) {
return name.equals(header.name)
? header
: new io.grpc.okhttp.internal.framed.Header(name, value);
}

// http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#section-4.1.1
void writeInt(int value, int prefixMask, int bits) throws IOException {
// Write the raw value for a single byte value.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import static okio.ByteString.decodeHex;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.fail;

import java.io.IOException;
Expand Down Expand Up @@ -1140,6 +1141,28 @@ public void pseudoHeaderIndexingForPathAndAuthority() throws IOException {
assertEquals(3, hpackWriter.dynamicTableHeaderCount);
}

@Test
public void mixedCaseHeaderNameIndexedOnRepeat() throws IOException {
hpackWriter.writeHeaders(Arrays.asList(new Header("FoO", "BaR")));
assertBytes(0x40, 3, 'f', 'o', 'o', 3, 'B', 'a', 'R');
assertEquals(1, hpackWriter.dynamicTableHeaderCount);

// Even though the header name is mixed case again, the dynamic table entry was stored
// with the lowercase name, so the repeat is emitted as an indexed reference.
hpackWriter.writeHeaders(Arrays.asList(new Header("FoO", "BaR")));
assertBytes(0xbe);
assertEquals(1, hpackWriter.dynamicTableHeaderCount);
}

@Test
public void lowercaseHeaderIsReusedInDynamicTable() throws IOException {
Header header = new Header("custom-key", "custom-value");

hpackWriter.writeHeaders(Arrays.asList(header));

assertSame(header, hpackWriter.dynamicTable[hpackWriter.dynamicTable.length - 1]);
}

@Test
public void evictToRecoverBytesDoesNotNpeWhenBytesToRecoverExceedsTable() throws IOException {
hpackWriter.writeHeaders(headerEntries("custom-key", "custom-value"));
Expand Down