日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

OkHttp3中的HTTP/2首部压缩

發(fā)布時(shí)間:2024/4/11 编程问答 43 豆豆
生活随笔 收集整理的這篇文章主要介紹了 OkHttp3中的HTTP/2首部压缩 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

當(dāng)前網(wǎng)絡(luò)環(huán)境中,同一個(gè)頁面發(fā)出幾十個(gè)HTTP請(qǐng)求已經(jīng)是司空見慣的事情了。在HTTP/1.1中,請(qǐng)求之間完全相互獨(dú)立,使得請(qǐng)求中冗余的首部字段不必要地浪費(fèi)了大量的網(wǎng)絡(luò)帶寬,并增加了網(wǎng)絡(luò)延時(shí)。以對(duì)某站點(diǎn)的一次頁面訪問為例,直觀地看一下這種狀況:


Header 1
Header 2

如上圖,同一個(gè)頁面中對(duì)兩個(gè)資源的請(qǐng)求,請(qǐng)求中的頭部字段絕大部分是完全相同的。"User-Agent" 等頭部字段通常還會(huì)消耗大量的帶寬。

首部壓縮正是為了解決這樣的問題而設(shè)計(jì)。

首部壓縮是HTTP/2中一個(gè)非常重要的特性,它大大減少了網(wǎng)絡(luò)中HTTP請(qǐng)求/響應(yīng)頭部傳輸所需的帶寬。HTTP/2的首部壓縮,主要從兩個(gè)方面實(shí)現(xiàn),一是首部表示,二是請(qǐng)求間首部字段內(nèi)容的復(fù)用。

首部表示

在HTTP中,首部字段是一個(gè)名值隊(duì),所有的首部字段組成首部字段列表。在HTTP/1.x中,首部字段都被表示為字符串,一行一行的首部字段字符串組成首部字段列表。而在HTTP/2的首部壓縮HPACK算法中,則有著不同的表示方法。

HPACK算法表示的對(duì)象,主要有原始數(shù)據(jù)類型的整型值和字符串,頭部字段,以及頭部字段列表。

整數(shù)的表示

在HPACK中,整數(shù)用于表示 頭部字段的名字的索引頭部字段索引字符串長(zhǎng)度。整數(shù)的表示可在字節(jié)內(nèi)的任何位置開始。但為了處理上的優(yōu)化,整數(shù)的表示總是在字節(jié)的結(jié)尾處結(jié)束。

整數(shù)由兩部分表示:填滿當(dāng)前字節(jié)的前綴,以及在前綴不足以表示整數(shù)時(shí)的一個(gè)可選字節(jié)列表。如果整數(shù)值足夠小,比如,小于2^N-1,那么把它編碼進(jìn)前綴即可,而不需要額外的空間。如:

0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | ? | ? | ? | Value | +---+---+---+-------------------+

在這個(gè)圖中,前綴有5位,而要表示的數(shù)足夠小,因此無需更多空間就可以表示整數(shù)了。

當(dāng)前綴不足以表示整數(shù)時(shí),前綴的所有位被置為1,再將值減去2^N-1之后用一個(gè)或多個(gè)字節(jié)編碼。每個(gè)字節(jié)的最高有效位被用作連續(xù)標(biāo)記:除列表的最后一個(gè)字節(jié)外,該位的值都被設(shè)為1。字節(jié)中剩余的位被用于編碼減小后的值。

0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | ? | ? | ? | 1 1 1 1 1 | +---+---+---+-------------------+ | 1 | Value-(2^N-1) LSB | +---+---------------------------+... +---+---------------------------+ | 0 | Value-(2^N-1) MSB | +---+---------------------------+

要由字節(jié)列表解碼出整數(shù)值,首先需要將列表中的字節(jié)順序反過來。然后,移除每個(gè)字節(jié)的最高有效位。連接字節(jié)的剩余位,再將結(jié)果加2^N-1獲得整數(shù)值。

前綴大小N,總是在1到8之間。從字節(jié)邊界處開始編碼的整數(shù)值其前綴為8位。

這種整數(shù)表示法允許編碼無限大的值。

表示整數(shù)I的偽代碼如下:

if I < 2^N - 1, encode I on N bits elseencode (2^N - 1) on N bitsI = I - (2^N - 1)while I >= 128encode (I % 128 + 128) on 8 bitsI = I / 128encode I on 8 bits

encode (I % 128 + 128) on 8 bits 一行中,加上128的意思是,最高有效位是1。如果要編碼的整數(shù)值等于 (2^N - 1),則用前綴和緊跟在前綴后面的值位0的一個(gè)字節(jié)來表示。

OkHttp中,這個(gè)算法的實(shí)現(xiàn)在 okhttp3.internal.http2.Hpack.Writer 中:

// http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#section-4.1.1void writeInt(int value, int prefixMask, int bits) {// Write the raw value for a single byte value.if (value < prefixMask) {out.writeByte(bits | value);return;}// Write the mask to start a multibyte value.out.writeByte(bits | prefixMask);value -= prefixMask;// Write 7 bits at a time 'til we're done.while (value >= 0x80) {int b = value & 0x7f;out.writeByte(b | 0x80);value >>>= 7;}out.writeByte(value);}

這里給最高有效位置 1 的方法就不是加上128,而是與0x80執(zhí)行或操作。

解碼整數(shù)I的偽代碼如下:

decode I from the next N bits if I < 2^N - 1, return I elseM = 0repeatB = next octetI = I + (B & 127) * 2^MM = M + 7while B & 128 == 128return I

decode I from the next N bits 這一行等價(jià)于一個(gè)賦值語句 *I = byteValue & (2^N - 1)

OkHttp中,這個(gè)算法的實(shí)現(xiàn)在 okhttp3.internal.http2.Hpack.Reader

int readInt(int firstByte, int prefixMask) throws IOException {int prefix = firstByte & prefixMask;if (prefix < prefixMask) {return prefix; // This was a single byte value.}// This is a multibyte value. Read 7 bits at a time.int result = prefixMask;int shift = 0;while (true) {int b = readByte();if ((b & 0x80) != 0) { // Equivalent to (b >= 128) since b is in [0..255].result += (b & 0x7f) << shift;shift += 7;} else {result += b << shift; // Last byte.break;}}return result;}

盡管HPACK的整數(shù)表示方法可以表示無限大的數(shù),但實(shí)際的實(shí)現(xiàn)中并不會(huì)將整數(shù)當(dāng)做無限大的整數(shù)來處理。

字符串字面量的編碼

頭部字段名和頭部字段值可使用字符串字面量表示。字符串字面量有兩種表示方式,一種是直接用UTF-8這樣的字符串編碼方式表示,另一種是將字符串編碼用Huffman 碼表示。 字符串表示的格式如下:

0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | H | String Length (7+) | +---+---------------------------+ | String Data (Length octets) | +-------------------------------+

先是標(biāo)記位 H + 字符串長(zhǎng)度,然后是字符串的實(shí)際數(shù)據(jù)。各部分說明如下:

  • H: 一位的標(biāo)記,指示字符串的字節(jié)是否為Huffman編碼。
  • 字符串長(zhǎng)度: 編碼字符串字面量的字節(jié)數(shù),一個(gè)整數(shù),編碼方式可以參考前面 整數(shù)的表示 的部分,一個(gè)7位前綴的整數(shù)編碼。
  • 字符串?dāng)?shù)據(jù): 字符串的實(shí)際數(shù)據(jù)。如果H是'0',則數(shù)據(jù)是字符串字面量的原始字節(jié)。如果H是'1',則數(shù)據(jù)是字符串字面量的Huffman編碼。

在OkHttp3中,總是會(huì)使用直接的字符串編碼,而不是Huffman編碼, okhttp3.internal.http2.Hpack.Writer 中編碼字符串的過程如下:

void writeByteString(ByteString data) throws IOException {writeInt(data.size(), PREFIX_7_BITS, 0);out.write(data);}

OkHttp中,解碼字符串在 okhttp3.internal.http2.Hpack.Reader 中實(shí)現(xiàn):

/** Reads a potentially Huffman encoded byte string. */ByteString readByteString() throws IOException {int firstByte = readByte();boolean huffmanDecode = (firstByte & 0x80) == 0x80; // 1NNNNNNNint length = readInt(firstByte, PREFIX_7_BITS);if (huffmanDecode) {return ByteString.of(Huffman.get().decode(source.readByteArray(length)));} else {return source.readByteString(length);}}

字符串編碼沒有使用Huffman編碼時(shí),解碼過程比較簡(jiǎn)單,而使用了Huffman編碼時(shí)會(huì)借助于Huffman類來解碼。

Huffman編碼是一種變長(zhǎng)字節(jié)編碼,對(duì)于使用頻率高的字節(jié),使用更少的位數(shù),對(duì)于使用頻率低的字節(jié)則使用更多的位數(shù)。每個(gè)字節(jié)的Huffman碼是根據(jù)統(tǒng)計(jì)經(jīng)驗(yàn)值分配的。為每個(gè)字節(jié)分配Huffman碼的方法可以參考 哈夫曼(huffman)樹和哈夫曼編碼 。

哈夫曼樹的構(gòu)造

Huffman 類被設(shè)計(jì)為一個(gè)單例類。對(duì)象在創(chuàng)建時(shí)構(gòu)造一個(gè)哈夫曼樹以用于編碼和解碼操作。

private static final Huffman INSTANCE = new Huffman();public static Huffman get() {return INSTANCE;}private final Node root = new Node();private Huffman() {buildTree();} ......private void buildTree() {for (int i = 0; i < CODE_LENGTHS.length; i++) {addCode(i, CODES[i], CODE_LENGTHS[i]);}}private void addCode(int sym, int code, byte len) {Node terminal = new Node(sym, len);Node current = root;while (len > 8) {len -= 8;int i = ((code >>> len) & 0xFF);if (current.children == null) {throw new IllegalStateException("invalid dictionary: prefix not unique");}if (current.children[i] == null) {current.children[i] = new Node();}current = current.children[i];}int shift = 8 - len;int start = (code << shift) & 0xFF;int end = 1 << shift;for (int i = start; i < start + end; i++) {current.children[i] = terminal;}} ......private static final class Node {// Null if terminal.private final Node[] children;// Terminal nodes have a symbol.private final int symbol;// Number of bits represented in the terminal node.private final int terminalBits;/** Construct an internal node. */Node() {this.children = new Node[256];this.symbol = 0; // Not read.this.terminalBits = 0; // Not read.}/*** Construct a terminal node.** @param symbol symbol the node represents* @param bits length of Huffman code in bits*/Node(int symbol, int bits) {this.children = null;this.symbol = symbol;int b = bits & 0x07;this.terminalBits = b == 0 ? 8 : b;}}

OkHttp3中的 哈夫曼樹 并不是一個(gè)二叉樹,它的每個(gè)節(jié)點(diǎn)最多都可以有256個(gè)字節(jié)點(diǎn)。OkHttp3用這種方式來優(yōu)化Huffman編碼解碼的效率。用一個(gè)圖來表示,將是下面這個(gè)樣子的:


Huffman Tree

Huffman 編碼

void encode(byte[] data, OutputStream out) throws IOException {long current = 0;int n = 0;for (int i = 0; i < data.length; i++) {int b = data[i] & 0xFF;int code = CODES[b];int nbits = CODE_LENGTHS[b];current <<= nbits;current |= code;n += nbits;while (n >= 8) {n -= 8;out.write(((int) (current >> n)));}}if (n > 0) {current <<= (8 - n);current |= (0xFF >>> n);out.write((int) current);}}

逐個(gè)字節(jié)地編碼數(shù)據(jù)。編碼的最后一個(gè)字節(jié)沒有字節(jié)對(duì)齊時(shí),會(huì)在低位填充1。

Huffman 解碼

byte[] decode(byte[] buf) {ByteArrayOutputStream baos = new ByteArrayOutputStream();Node node = root;int current = 0;int nbits = 0;for (int i = 0; i < buf.length; i++) {int b = buf[i] & 0xFF;current = (current << 8) | b;nbits += 8;while (nbits >= 8) {int c = (current >>> (nbits - 8)) & 0xFF;node = node.children[c];if (node.children == null) {// terminal nodebaos.write(node.symbol);nbits -= node.terminalBits;node = root;} else {// non-terminal nodenbits -= 8;}}}while (nbits > 0) {int c = (current << (8 - nbits)) & 0xFF;node = node.children[c];if (node.children != null || node.terminalBits > nbits) {break;}baos.write(node.symbol);nbits -= node.terminalBits;node = root;}return baos.toByteArray();}

配合Huffman樹的構(gòu)造過程,分幾種情況來看。Huffman碼自己對(duì)齊時(shí);Huffman碼沒有字節(jié)對(duì)齊,最后一個(gè)字節(jié)的最低有效位包含了數(shù)據(jù)流中下一個(gè)Huffman碼的最高有效位;Huffman碼沒有字節(jié)對(duì)齊,最后一個(gè)字節(jié)的最低有效位包含了填充的1。

有興趣的可以參考其它文檔對(duì)Huffman編碼算法做更多了解。

首部字段及首部塊的表示

首部字段主要有兩種表示方法,分別是索引表示和字面量表示。字面量表示又分為首部字段的名字用索引表示值用字面量表示和名字及值都用字面量表示等方法。

說到用索引表示首部字段,就不能不提一下HPACK的動(dòng)態(tài)表和靜態(tài)表。

HPACK使用兩個(gè)表將 頭部字段 與 索引 關(guān)聯(lián)起來。 靜態(tài)表 是預(yù)定義的,它包含了常見的頭部字段(其中的大多數(shù)值為空)。 動(dòng)態(tài)表 是動(dòng)態(tài)的,它可被編碼器用于編碼重復(fù)的頭部字段。

靜態(tài)表由一個(gè)預(yù)定義的頭部字段靜態(tài)列表組成。它的條目在 HPACK規(guī)范的 附錄 A 中定義。

動(dòng)態(tài)表由以先進(jìn)先出順序維護(hù)的 頭部字段列表 組成。動(dòng)態(tài)表中第一個(gè)且最新的條目索引值最低,動(dòng)態(tài)表最舊的條目索引值最高。

動(dòng)態(tài)表最初是空的。條目隨著每個(gè)頭部塊的解壓而添加。

靜態(tài)表和動(dòng)態(tài)表被組合為統(tǒng)一的索引地址空間。

在 (1 ~ 靜態(tài)表的長(zhǎng)度(包含)) 之間的索引值指向靜態(tài)表中的元素。

大于靜態(tài)表長(zhǎng)度的索引值指向動(dòng)態(tài)表中的元素。通過將頭部字段的索引減去靜態(tài)表的長(zhǎng)度來查找指向動(dòng)態(tài)表的索引。

對(duì)于靜態(tài)表大小為 s,動(dòng)態(tài)表大小為 k 的情況,下圖展示了完整的有效索引地址空間。

<---------- Index Address Space ----------><-- Static Table --> <-- Dynamic Table -->+---+-----------+---+ +---+-----------+---+| 1 | ... | s | |s+1| ... |s+k|+---+-----------+---+ +---+-----------+---+^ || VInsertion Point Dropping Point

用索引表示頭部字段

當(dāng)一個(gè)頭部字段的名-值已經(jīng)包含在了靜態(tài)表或動(dòng)態(tài)表中時(shí),就可以用一個(gè)指向靜態(tài)表或動(dòng)態(tài)表的索引來表示它了。表示方法如下:

0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | 1 | Index (7+) | +---+---------------------------+

頭部字段表示的最高有效位置1,然后用前面看到的表示整數(shù)的方法表示索引,即索引是一個(gè)7位前綴編碼的整數(shù)。

用字面量表示頭部字段

在這種表示法中,頭部字段的值是用字面量表示的,但頭部字段的名字則不一定。根據(jù)名字的表示方法的差異,以及是否將頭部字段加進(jìn)動(dòng)態(tài)表等,而分為多種情況。

增量索引的字面量表示

以這種方法表示的頭部字段需要被 加進(jìn)動(dòng)態(tài)表中。在這種表示方法下,頭部字段的值用索引表示時(shí),頭部字段的表示如下:

0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | 0 | 1 | Index (6+) | +---+---+-----------------------+ | H | Value Length (7+) | +---+---------------------------+ | Value String (Length octets) | +-------------------------------+

頭部字段的名字和值都用字面量表示時(shí),表示如下:

0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | 0 | 1 | 0 | +---+---+-----------------------+ | H | Name Length (7+) | +---+---------------------------+ | Name String (Length octets) | +---+---------------------------+ | H | Value Length (7+) | +---+---------------------------+ | Value String (Length octets) | +-------------------------------+

增量索引的字面量頭部字段表示以'01' 的2位模式開始。

如果頭部字段名與靜態(tài)表或動(dòng)態(tài)表中存儲(chǔ)的條目的頭部字段名匹配,則頭部字段名稱可用那個(gè)條目的索引表示。在這種情況下,條目的索引以一個(gè)具有6位前綴的整數(shù) 表示。這個(gè)值總是非0。否則,頭部字段名由一個(gè)字符串字面量 表示,使用0值代替6位索引,其后是頭部字段名。

兩種形式的 頭部字段名表示 之后是字符串字面量表示的頭部字段值。

無索引的字面量頭部字段

這種表示方法不改變動(dòng)態(tài)表。頭部字段名用索引表示時(shí)的頭部字段表示如下:

0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | 0 | 0 | 0 | 0 | Index (4+) | +---+---+-----------------------+ | H | Value Length (7+) | +---+---------------------------+ | Value String (Length octets) | +-------------------------------+

頭部字段名不用索引表示時(shí)的頭部字段表示如下:

0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | 0 | 0 | 0 | 0 | 0 | +---+---+-----------------------+ | H | Name Length (7+) | +---+---------------------------+ | Name String (Length octets) | +---+---------------------------+ | H | Value Length (7+) | +---+---------------------------+ | Value String (Length octets) | +-------------------------------+

無索引的字面量頭部字段表示以'0000' 的4位模式開始,其它方面與 增量索引的字面量表示 類似。

從不索引的字面量頭部字段

這種表示方法與 無索引的字面量頭部字段 類似,但它主要影響網(wǎng)絡(luò)中的中間節(jié)點(diǎn)。頭部字段名用索引表示時(shí)的頭部字段如:

0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | 0 | 0 | 0 | 1 | Index (4+) | +---+---+-----------------------+ | H | Value Length (7+) | +---+---------------------------+ | Value String (Length octets) | +-------------------------------+

頭部字段名不用索引表示時(shí)的頭部字段如:

0 1 2 3 4 5 6 7 +---+---+---+---+---+---+---+---+ | 0 | 0 | 0 | 1 | 0 | +---+---+-----------------------+ | H | Name Length (7+) | +---+---------------------------+ | Name String (Length octets) | +---+---------------------------+ | H | Value Length (7+) | +---+---------------------------+ | Value String (Length octets) | +-------------------------------+

首部列表的表示

各個(gè)首部字段表示合并起來形成首部列表。在 okhttp3.internal.framed.Hpack.Writer 的writeHeaders() 中完成編碼首部塊的動(dòng)作:

/** This does not use "never indexed" semantics for sensitive headers. */// http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-12#section-6.2.3void writeHeaders(List<Header> headerBlock) throws IOException {if (emitDynamicTableSizeUpdate) {if (smallestHeaderTableSizeSetting < maxDynamicTableByteCount) {// Multiple dynamic table size updates!writeInt(smallestHeaderTableSizeSetting, PREFIX_5_BITS, 0x20);}emitDynamicTableSizeUpdate = false;smallestHeaderTableSizeSetting = Integer.MAX_VALUE;writeInt(maxDynamicTableByteCount, PREFIX_5_BITS, 0x20);}// TODO: implement index trackingfor (int i = 0, size = headerBlock.size(); i < size; i++) {Header header = headerBlock.get(i);ByteString name = header.name.toAsciiLowercase();ByteString value = header.value;Integer staticIndex = NAME_TO_FIRST_INDEX.get(name);if (staticIndex != null) {// Literal Header Field without Indexing - Indexed Name.writeInt(staticIndex + 1, PREFIX_4_BITS, 0);writeByteString(value);} else {int dynamicIndex = Util.indexOf(dynamicTable, header);if (dynamicIndex != -1) {// Indexed Header.writeInt(dynamicIndex - nextHeaderIndex + STATIC_HEADER_TABLE.length, PREFIX_7_BITS,0x80);} else {// Literal Header Field with Incremental Indexing - New Nameout.writeByte(0x40);writeByteString(name);writeByteString(value);insertIntoDynamicTable(header);}}}}

HPACK的規(guī)范描述了多種頭部字段的表示方法,但并沒有指明各個(gè)表示方法的適用場(chǎng)景。

在OkHttp3中,實(shí)現(xiàn)了3種表示頭部字段的表示方法:

  • 頭部字段名在靜態(tài)表中,頭部字段名用指向靜態(tài)表的索引表示,值用字面量表示。頭部字段無需加入動(dòng)態(tài)表。
  • 頭部字段的 名-值 對(duì)在動(dòng)態(tài)表中,用指向動(dòng)態(tài)表的索引表示頭部字段。
  • 其它情況,用字面量表示頭部字段名和值,頭部字段需要加入動(dòng)態(tài)表。
  • 如果頭部字段的 名-值 對(duì)在靜態(tài)表中,OkHttp3也不會(huì)用索引表示。

    請(qǐng)求間首部字段內(nèi)容的復(fù)用

    HPACK中,最重要的優(yōu)化就是消除請(qǐng)求間冗余的首部字段。在實(shí)現(xiàn)上,主要有兩個(gè)方面,一是前面看到的首部字段的索引表示,另一方面則是動(dòng)態(tài)表的維護(hù)。

    HTTP/2中數(shù)據(jù)發(fā)送方向和數(shù)據(jù)接收方向各有一個(gè)動(dòng)態(tài)表。通信的雙方,一端發(fā)送方向的動(dòng)態(tài)表需要與另一端接收方向的動(dòng)態(tài)表保持一致,反之亦然。

    HTTP/2的連接復(fù)用及請(qǐng)求并發(fā)執(zhí)行指的是邏輯上的并發(fā)。由于底層傳輸還是用的TCP協(xié)議,因而,發(fā)送方發(fā)送數(shù)據(jù)的順序,與接收方接收數(shù)據(jù)的順序是一致的。

    數(shù)據(jù)發(fā)送方在發(fā)送一個(gè)請(qǐng)求的首部數(shù)據(jù)時(shí)會(huì)順便維護(hù)自己的動(dòng)態(tài)表,接收方在收到首部數(shù)據(jù)時(shí),也需要立馬維護(hù)自己接收方向的動(dòng)態(tài)表,然后將解碼之后的首部字段列表dispatch出去。

    如果通信雙方同時(shí)在進(jìn)行2個(gè)HTTP請(qǐng)求,分別稱為Req1和Req2,假設(shè)在發(fā)送方Req1的頭部字段列表先發(fā)送,Req2的頭部字段后發(fā)送。接收方必然先收到Req1的頭部字段列表,然后是Req2的。如果接收方在收到Req1的頭部字段列表后,沒有立即解碼,而是等Req2的首部字段列表接收并處理完成之后,再來處理Req1的,則兩端的動(dòng)態(tài)表必然是不一致的。

    這里來看一下OkHttp3中的動(dòng)態(tài)表維護(hù)。

    發(fā)送方向的動(dòng)態(tài)表,在 okhttp3.internal.framed.Hpack.Writer 中維護(hù)。在HTTP/2中,動(dòng)態(tài)表的最大大小在連接建立的初期會(huì)進(jìn)行協(xié)商,后面在數(shù)據(jù)收發(fā)過程中也會(huì)進(jìn)行更新。

    在編碼頭部字段列表的 writeHeaders(List<Header> headerBlock) 中,會(huì)在需要的時(shí)候,將頭部字段插入動(dòng)態(tài)表,具體來說,就是在頭部字段的名字不在靜態(tài)表中,同時(shí) 名-值對(duì)不在動(dòng)態(tài)表中的情況。

    將頭部字段插入動(dòng)態(tài)表的過程如下:

    private void clearDynamicTable() {Arrays.fill(dynamicTable, null);nextHeaderIndex = dynamicTable.length - 1;headerCount = 0;dynamicTableByteCount = 0;}/** Returns the count of entries evicted. */private int evictToRecoverBytes(int bytesToRecover) {int entriesToEvict = 0;if (bytesToRecover > 0) {// determine how many headers need to be evicted.for (int j = dynamicTable.length - 1; j >= nextHeaderIndex && bytesToRecover > 0; j--) {bytesToRecover -= dynamicTable[j].hpackSize;dynamicTableByteCount -= dynamicTable[j].hpackSize;headerCount--;entriesToEvict++;}System.arraycopy(dynamicTable, nextHeaderIndex + 1, dynamicTable,nextHeaderIndex + 1 + entriesToEvict, headerCount);Arrays.fill(dynamicTable, nextHeaderIndex + 1, nextHeaderIndex + 1 + entriesToEvict, null);nextHeaderIndex += entriesToEvict;}return entriesToEvict;}private void insertIntoDynamicTable(Header entry) {int delta = entry.hpackSize;// if the new or replacement header is too big, drop all entries.if (delta > maxDynamicTableByteCount) {clearDynamicTable();return;}// Evict headers to the required length.int bytesToRecover = (dynamicTableByteCount + delta) - maxDynamicTableByteCount;evictToRecoverBytes(bytesToRecover);if (headerCount + 1 > dynamicTable.length) { // Need to grow the dynamic table.Header[] doubled = new Header[dynamicTable.length * 2];System.arraycopy(dynamicTable, 0, doubled, dynamicTable.length, dynamicTable.length);nextHeaderIndex = dynamicTable.length - 1;dynamicTable = doubled;}int index = nextHeaderIndex--;dynamicTable[index] = entry;headerCount++;dynamicTableByteCount += delta;}

    動(dòng)態(tài)表占用的空間超出限制時(shí),老的頭部字段將被移除。在OkHttp3中,動(dòng)態(tài)表是一個(gè)自后向前生長(zhǎng)的表。

    在數(shù)據(jù)的接收防線,okhttp3.internal.http2.Http2Reader 的 nextFrame(Handler handler) 會(huì)不停從網(wǎng)絡(luò)讀取一幀幀的數(shù)據(jù):

    public boolean nextFrame(Handler handler) throws IOException {try {source.require(9); // Frame header size} catch (IOException e) {return false; // This might be a normal socket close.}/* 0 1 2 3* 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+* | Length (24) |* +---------------+---------------+---------------+* | Type (8) | Flags (8) |* +-+-+-----------+---------------+-------------------------------+* |R| Stream Identifier (31) |* +=+=============================================================+* | Frame Payload (0...) ...* +---------------------------------------------------------------+*/int length = readMedium(source);if (length < 0 || length > INITIAL_MAX_FRAME_SIZE) {throw ioException("FRAME_SIZE_ERROR: %s", length);}byte type = (byte) (source.readByte() & 0xff);byte flags = (byte) (source.readByte() & 0xff);int streamId = (source.readInt() & 0x7fffffff); // Ignore reserved bit.if (logger.isLoggable(FINE)) logger.fine(frameLog(true, streamId, length, type, flags));switch (type) {case TYPE_DATA:readData(handler, length, flags, streamId);break;case TYPE_HEADERS:readHeaders(handler, length, flags, streamId);break;

    讀到頭部塊時(shí),會(huì)立即維護(hù)本地接收方向的動(dòng)態(tài)表:

    private void readHeaders(Handler handler, int length, byte flags, int streamId)throws IOException {if (streamId == 0) throw ioException("PROTOCOL_ERROR: TYPE_HEADERS streamId == 0");boolean endStream = (flags & FLAG_END_STREAM) != 0;short padding = (flags & FLAG_PADDED) != 0 ? (short) (source.readByte() & 0xff) : 0;if ((flags & FLAG_PRIORITY) != 0) {readPriority(handler, streamId);length -= 5; // account for above read.}length = lengthWithoutPadding(length, flags, padding);List<Header> headerBlock = readHeaderBlock(length, padding, flags, streamId);handler.headers(endStream, streamId, -1, headerBlock);}private List<Header> readHeaderBlock(int length, short padding, byte flags, int streamId)throws IOException {continuation.length = continuation.left = length;continuation.padding = padding;continuation.flags = flags;continuation.streamId = streamId;// TODO: Concat multi-value headers with 0x0, except COOKIE, which uses 0x3B, 0x20.// http://tools.ietf.org/html/draft-ietf-httpbis-http2-17#section-8.1.2.5hpackReader.readHeaders();return hpackReader.getAndResetHeaderList();}

    okhttp3.internal.http2.Hpack.Reader的readHeaders()如下:

    static final class Reader {private final List<Header> headerList = new ArrayList<>();private final BufferedSource source;private final int headerTableSizeSetting;private int maxDynamicTableByteCount;// Visible for testing.Header[] dynamicTable = new Header[8];// Array is populated back to front, so new entries always have lowest index.int nextHeaderIndex = dynamicTable.length - 1;int headerCount = 0;int dynamicTableByteCount = 0;Reader(int headerTableSizeSetting, Source source) {this(headerTableSizeSetting, headerTableSizeSetting, source);}Reader(int headerTableSizeSetting, int maxDynamicTableByteCount, Source source) {this.headerTableSizeSetting = headerTableSizeSetting;this.maxDynamicTableByteCount = maxDynamicTableByteCount;this.source = Okio.buffer(source);}int maxDynamicTableByteCount() {return maxDynamicTableByteCount;}private void adjustDynamicTableByteCount() {if (maxDynamicTableByteCount < dynamicTableByteCount) {if (maxDynamicTableByteCount == 0) {clearDynamicTable();} else {evictToRecoverBytes(dynamicTableByteCount - maxDynamicTableByteCount);}}}private void clearDynamicTable() {headerList.clear();Arrays.fill(dynamicTable, null);nextHeaderIndex = dynamicTable.length - 1;headerCount = 0;dynamicTableByteCount = 0;}/** Returns the count of entries evicted. */private int evictToRecoverBytes(int bytesToRecover) {int entriesToEvict = 0;if (bytesToRecover > 0) {// determine how many headers need to be evicted.for (int j = dynamicTable.length - 1; j >= nextHeaderIndex && bytesToRecover > 0; j--) {bytesToRecover -= dynamicTable[j].hpackSize;dynamicTableByteCount -= dynamicTable[j].hpackSize;headerCount--;entriesToEvict++;}System.arraycopy(dynamicTable, nextHeaderIndex + 1, dynamicTable,nextHeaderIndex + 1 + entriesToEvict, headerCount);nextHeaderIndex += entriesToEvict;}return entriesToEvict;}/*** Read {@code byteCount} bytes of headers from the source stream. This implementation does not* propagate the never indexed flag of a header.*/void readHeaders() throws IOException {while (!source.exhausted()) {int b = source.readByte() & 0xff;if (b == 0x80) { // 10000000throw new IOException("index == 0");} else if ((b & 0x80) == 0x80) { // 1NNNNNNNint index = readInt(b, PREFIX_7_BITS);readIndexedHeader(index - 1);} else if (b == 0x40) { // 01000000readLiteralHeaderWithIncrementalIndexingNewName();} else if ((b & 0x40) == 0x40) { // 01NNNNNNint index = readInt(b, PREFIX_6_BITS);readLiteralHeaderWithIncrementalIndexingIndexedName(index - 1);} else if ((b & 0x20) == 0x20) { // 001NNNNNmaxDynamicTableByteCount = readInt(b, PREFIX_5_BITS);if (maxDynamicTableByteCount < 0|| maxDynamicTableByteCount > headerTableSizeSetting) {throw new IOException("Invalid dynamic table size update " + maxDynamicTableByteCount);}adjustDynamicTableByteCount();} else if (b == 0x10 || b == 0) { // 000?0000 - Ignore never indexed bit.readLiteralHeaderWithoutIndexingNewName();} else { // 000?NNNN - Ignore never indexed bit.int index = readInt(b, PREFIX_4_BITS);readLiteralHeaderWithoutIndexingIndexedName(index - 1);}}}public List<Header> getAndResetHeaderList() {List<Header> result = new ArrayList<>(headerList);headerList.clear();return result;}private void readIndexedHeader(int index) throws IOException {if (isStaticHeader(index)) {Header staticEntry = STATIC_HEADER_TABLE[index];headerList.add(staticEntry);} else {int dynamicTableIndex = dynamicTableIndex(index - STATIC_HEADER_TABLE.length);if (dynamicTableIndex < 0 || dynamicTableIndex > dynamicTable.length - 1) {throw new IOException("Header index too large " + (index + 1));}headerList.add(dynamicTable[dynamicTableIndex]);}}// referencedHeaders is relative to nextHeaderIndex + 1.private int dynamicTableIndex(int index) {return nextHeaderIndex + 1 + index;}private void readLiteralHeaderWithoutIndexingIndexedName(int index) throws IOException {ByteString name = getName(index);ByteString value = readByteString();headerList.add(new Header(name, value));}private void readLiteralHeaderWithoutIndexingNewName() throws IOException {ByteString name = checkLowercase(readByteString());ByteString value = readByteString();headerList.add(new Header(name, value));}private void readLiteralHeaderWithIncrementalIndexingIndexedName(int nameIndex)throws IOException {ByteString name = getName(nameIndex);ByteString value = readByteString();insertIntoDynamicTable(-1, new Header(name, value));}private void readLiteralHeaderWithIncrementalIndexingNewName() throws IOException {ByteString name = checkLowercase(readByteString());ByteString value = readByteString();insertIntoDynamicTable(-1, new Header(name, value));}private ByteString getName(int index) {if (isStaticHeader(index)) {return STATIC_HEADER_TABLE[index].name;} else {return dynamicTable[dynamicTableIndex(index - STATIC_HEADER_TABLE.length)].name;}}private boolean isStaticHeader(int index) {return index >= 0 && index <= STATIC_HEADER_TABLE.length - 1;}/** index == -1 when new. */private void insertIntoDynamicTable(int index, Header entry) {headerList.add(entry);int delta = entry.hpackSize;if (index != -1) { // Index -1 == new header.delta -= dynamicTable[dynamicTableIndex(index)].hpackSize;}// if the new or replacement header is too big, drop all entries.if (delta > maxDynamicTableByteCount) {clearDynamicTable();return;}// Evict headers to the required length.int bytesToRecover = (dynamicTableByteCount + delta) - maxDynamicTableByteCount;int entriesEvicted = evictToRecoverBytes(bytesToRecover);if (index == -1) { // Adding a value to the dynamic table.if (headerCount + 1 > dynamicTable.length) { // Need to grow the dynamic table.Header[] doubled = new Header[dynamicTable.length * 2];System.arraycopy(dynamicTable, 0, doubled, dynamicTable.length, dynamicTable.length);nextHeaderIndex = dynamicTable.length - 1;dynamicTable = doubled;}index = nextHeaderIndex--;dynamicTable[index] = entry;headerCount++;} else { // Replace value at same position.index += dynamicTableIndex(index) + entriesEvicted;dynamicTable[index] = entry;}dynamicTableByteCount += delta;}

    HTTP/2中數(shù)據(jù)收發(fā)兩端的動(dòng)態(tài)表一致性主要是依賴TCP來實(shí)現(xiàn)的。

    Done。

    總結(jié)

    以上是生活随笔為你收集整理的OkHttp3中的HTTP/2首部压缩的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。