前言
HTTP2终于支持对头部进行压缩传输了,Netty很早就支持HTTP2了,看下Netty对HPACK的实现源码,可以对HPACK理解的更深一下。
HpackDecoder
Netty内置的编解码器Http2FrameCodec专门用来对HTTP2的各种Frame进行编解码,其中就包含将ByteBuf解码为HeadersFrame,解码的工作最终交给了io.netty.handler.codec.http2.HpackDecoder。
状态
HpackDecoder维护了一组状态常量,代表的是当前对Header的读取状态,不同的状态做的事情是不一样的,HpackDecoder通过一个While循环来读取Header,因为一个Frame里面包含若干个Header,根据读取到的数据判断,State会不断变化流转,理解了这些State,再看代码就简单的多了。
| State | 说明 |
|---|---|
| READ_HEADER_REPRESENTATION | 读取header的初试状态 |
| READ_INDEXED_HEADER | 读取被索引的完整header,即name和value均被索引 |
| READ_INDEXED_HEADER_NAME | 读取被索引的header name |
| READ_LITERAL_HEADER_NAME_LENGTH_PREFIX | name未被索引,读取name长度前缀,判断是否使用哈夫曼编码 |
| READ_LITERAL_HEADER_NAME_LENGTH | name未被索引,读取name长度 |
| READ_LITERAL_HEADER_NAME | name未被索引,读取header name |
| READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX | 读取value长度前缀,判断是否使用哈夫曼编码 |
| READ_LITERAL_HEADER_VALUE_LENGTH | 读取value长度 |
| READ_LITERAL_HEADER_VALUE | value未被索引,读取value |
decode()
解码的方法是io.netty.handler.codec.http2.HpackDecoder#decode(),这里直接贴出源码,核心代码已写注释:
private void decode(ByteBuf in, Http2HeadersSink sink) throws Http2Exception {
int index = 0;
int nameLength = 0;
int valueLength = 0;
byte state = READ_HEADER_REPRESENTATION;// 初始状态 准备读取Header
boolean huffmanEncoded = false;// 是否使用哈夫曼编码 长度的第1个Bit来标记
AsciiString name = null;
IndexType indexType = IndexType.NONE;// 索引类型
while (in.isReadable()) {// 只要有数据,就循环读
switch (state) {
case READ_HEADER_REPRESENTATION:
byte b = in.readByte();// 读取首字节
if (maxDynamicTableSizeChangeRequired && (b & 0xE0) != 0x20) {
// HpackEncoder MUST signal maximum dynamic table size change
throw MAX_DYNAMIC_TABLE_SIZE_CHANGE_REQUIRED;
}
if (b < 0) {// 小于0 即最高位是1,代表name和value均被索引
// Indexed Header Field
index = b & 0x7F;
switch (index) {
case 0:
throw DECODE_ILLEGAL_INDEX_VALUE;
case 0x7F: // 索引号超过了1字节,需要继续读取 才能获取最终索引号
state = READ_INDEXED_HEADER;
break;
default:// 索引号没超 直接从静态/动态表读取即可
HpackHeaderField indexedHeader = getIndexedHeader(index);
sink.appendToHeaderList(
(AsciiString) indexedHeader.name,
(AsciiString) indexedHeader.value);
}
} else if ((b & 0x40) == 0x40) {// 01开头 header需要加入到动态表
// Literal Header Field with Incremental Indexing
indexType = IndexType.INCREMENTAL;
index = b & 0x3F;
switch (index) {
case 0:// name未被索引,读取name长度 再读name
state = READ_LITERAL_HEADER_NAME_LENGTH_PREFIX;
break;
case 0x3F:// name 索引号超了1字节 需要继续读
state = READ_INDEXED_HEADER_NAME;
break;
default:
// name被索引,继续读value
name = readName(index);
nameLength = name.length();
state = READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX;
}
} else if ((b & 0x20) == 0x20) {
// Dynamic Table Size Update
// See https://www.rfc-editor.org/rfc/rfc7541.html#section-4.2
throw connectionError(COMPRESSION_ERROR, "Dynamic table size update must happen " +
"at the beginning of the header block");
} else {
// Literal Header Field without Indexing / never Indexed
indexType = (b & 0x10) == 0x10 ? IndexType.NEVER : IndexType.NONE;
index = b & 0x0F;
switch (index) {
case 0:
state = READ_LITERAL_HEADER_NAME_LENGTH_PREFIX;
break;
case 0x0F:
state = READ_INDEXED_HEADER_NAME;
break;
default:
// Index was stored as the prefix
name = readName(index);
nameLength = name.length();
state = READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX;
}
}
break;
case READ_INDEXED_HEADER:// 被索引的header,读取可变长度的索引号,从表中获取header
HpackHeaderField indexedHeader = getIndexedHeader(decodeULE128(in, index));
sink.appendToHeaderList(
(AsciiString) indexedHeader.name,
(AsciiString) indexedHeader.value);
state = READ_HEADER_REPRESENTATION;
break;
case READ_INDEXED_HEADER_NAME:
// Header Name matches an entry in the Header Table
name = readName(decodeULE128(in, index));
nameLength = name.length();
state = READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX;
break;
case READ_LITERAL_HEADER_NAME_LENGTH_PREFIX:// 读取name前缀 判断是否使用哈夫曼编码
b = in.readByte();
huffmanEncoded = (b & 0x80) == 0x80;
index = b & 0x7F;
if (index == 0x7f) {
state = READ_LITERAL_HEADER_NAME_LENGTH;
} else {
nameLength = index;
state = READ_LITERAL_HEADER_NAME;
}
break;
case READ_LITERAL_HEADER_NAME_LENGTH:
// Header Name is a Literal String
nameLength = decodeULE128(in, index);
state = READ_LITERAL_HEADER_NAME;
break;
case READ_LITERAL_HEADER_NAME:
// Wait until entire name is readable
if (in.readableBytes() < nameLength) {
throw notEnoughDataException(in);
}
name = readStringLiteral(in, nameLength, huffmanEncoded);
state = READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX;
break;
case READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX:
b = in.readByte();
huffmanEncoded = (b & 0x80) == 0x80;
index = b & 0x7F;
switch (index) {
case 0x7f:
state = READ_LITERAL_HEADER_VALUE_LENGTH;
break;
case 0:
insertHeader(sink, name, EMPTY_STRING, indexType);
state = READ_HEADER_REPRESENTATION;
break;
default:
valueLength = index;
state = READ_LITERAL_HEADER_VALUE;
}
break;
case READ_LITERAL_HEADER_VALUE_LENGTH:
// Header Value is a Literal String
valueLength = decodeULE128(in, index);
state = READ_LITERAL_HEADER_VALUE;
break;
case READ_LITERAL_HEADER_VALUE:
// Wait until entire value is readable
if (in.readableBytes() < valueLength) {
throw notEnoughDataException(in);
}
AsciiString value = readStringLiteral(in, valueLength, huffmanEncoded);
insertHeader(sink, name, value, indexType);
state = READ_HEADER_REPRESENTATION;
break;
default:
throw new Error("should not reach here state: " + state);
}
}
if (state != READ_HEADER_REPRESENTATION) {
throw connectionError(COMPRESSION_ERROR, "Incomplete header block fragment.");
}
}
该方法做的事情:
- 读取首字节,判断最高位是否是1开头。如果是代表name和value都是被索引的,继续读取索引号从静态/动态表获取header即可。
- 判断高位是否是01开头,是的话就需要把读取到的header添加到动态表中,进行维护。
- 如果name被索引,则读取索引号,从静态/动态表获取。如果没有被索引,则判断长度的第1Bit是否是1,如果是代表使用了哈夫曼编码,否则使用常规的ASCII字符编码。
- 读取value的规则类似,也是先判断是否使用哈夫曼编码,再按规则读取。
静态表
Netty通过io.netty.handler.codec.http2.HpackStaticTable类来维护静态表,硬编码写死的,不支持动态修改。
final class HpackStaticTable {
static final int NOT_FOUND = -1;
// Appendix A: Static Table
// https://tools.ietf.org/html/rfc7541#appendix-A
private static final List<HpackHeaderField> STATIC_TABLE = Arrays.asList(
/* 1 */ newEmptyHeaderField(":authority"),
/* 2 */ newHeaderField(":method", "GET"),
/* 3 */ newHeaderField(":method", "POST"),
/* 4 */ newHeaderField(":path", "/"),
/* 5 */ newHeaderField(":path", "/index.html"),
/* 6 */ newHeaderField(":scheme", "http"),
/* 7 */ newHeaderField(":scheme", "https"),
/* 61 */ newEmptyHeaderField("www-authenticate")
。。。。。。
);
}
动态表
Netty通过io.netty.handler.codec.http2.HpackDynamicTable类来维护动态表,动态表初始是空的,只有读到01开头的Header才会加入到动态表中维护。
final class HpackDynamicTable {
// a circular queue of header fields
HpackHeaderField[] hpackHeaderFields;
int head;
int tail;
private long size;
private long capacity = -1;
public void add(HpackHeaderField header) {
int headerSize = header.size();
if (headerSize > capacity) {
clear();
return;
}
while (capacity - size < headerSize) {
remove();
}
hpackHeaderFields[head++] = header;
size += headerSize;
if (head == hpackHeaderFields.length) {
head = 0;
}
}
。。。。。。
}