Netty对HPACK头部压缩的支持
前言
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;// 初始状态 准备读取Headerboolean 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 changethrow MAX_DYNAMIC_TABLE_SIZE_CHANGE_REQUIRED;}if (b < 0) {// 小于0 即最高位是1,代表name和value均被索引// Indexed Header Fieldindex = 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 IndexingindexType = IndexType.INCREMENTAL;index = b & 0x3F;switch (index) {case 0:// name未被索引,读取name长度 再读namestate = READ_LITERAL_HEADER_NAME_LENGTH_PREFIX;break;case 0x3F:// name 索引号超了1字节 需要继续读state = READ_INDEXED_HEADER_NAME;break;default:// name被索引,继续读valuename = 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.2throw connectionError(COMPRESSION_ERROR, "Dynamic table size update must happen " +"at the beginning of the header block");} else {// Literal Header Field without Indexing / never IndexedindexType = (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 prefixname = readName(index);nameLength = name.length();state = READ_LITERAL_HEADER_VALUE_LENGTH_PREFIX;}}break;case READ_INDEXED_HEADER:// 被索引的header,读取可变长度的索引号,从表中获取headerHpackHeaderField 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 Tablename = 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 StringnameLength = decodeULE128(in, index);state = READ_LITERAL_HEADER_NAME;break;case READ_LITERAL_HEADER_NAME:// Wait until entire name is readableif (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 StringvalueLength = decodeULE128(in, index);state = READ_LITERAL_HEADER_VALUE;break;case READ_LITERAL_HEADER_VALUE:// Wait until entire value is readableif (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-Aprivate 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 fieldsHpackHeaderField[] 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;}}。。。。。。
}
相关文章:
Netty对HPACK头部压缩的支持
前言 HTTP2终于支持对头部进行压缩传输了,Netty很早就支持HTTP2了,看下Netty对HPACK的实现源码,可以对HPACK理解的更深一下。 HpackDecoder Netty内置的编解码器Http2FrameCodec专门用来对HTTP2的各种Frame进行编解码,其中就包…...
C++:替换string中的字符
1.按照位置进行替换 string的成员函数replace可以满足这种需求,其变体有很多种,请参考官方文档,以下列举常用的两种: #include <iostream> #include <string> using namespace std;int main() {string s = "hello world";s.replace(s.begin(), s.b…...
【ChatGPT】自我救赎
ChatGPT辅助学习C之【在C中如果大数据类型转小数据类型会发生什么呢?】,今天问ChatGPT一个问题,让它解析下面这个C程序: #include <iostream> #include <cstdio> using namespace std; int main() {int a;long long b532165478…...
微信小程序(由浅到深)
文章目录 一. 项目基本配置1. 项目组成2. 常见的配置文件解析3. app.json全局的五大配置4.单个页面中的page配置5. App函数6.tabBar配置 二. 基本语法,事件,单位1. 语法2. 事件3. 单位 三. 数据响应式修改四 . 内置组件1. button2. image3. input4. 组件…...
冒泡排序 简单选择排序 插入排序 快速排序
bubblesort 两个for循环,从最右端开始一个一个逐渐有序 #include <stdio.h> #include <string.h> #include <stdlib.h>void bubble(int *arr, int len); int main(int argc, char *argv[]) {int arr[] {1, 2, 3, 4, 5, 6, 7};int len sizeof(…...
linux文件I/O之 open() 函数用法
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> typedef unsigned int mode_t ; int open(const char *pathname, int flags); int open(const char *pathname, int flags, mode_t mode); 函数功能 打开或创建一个文件 返回值 成功…...
用Java操作MySQL数据库
新建Maven项目 创建Maven项目 添加依赖 在pom.xml的标签里加上下面的内容 如果是MySQL 5.8那么的版本号是5.x.x, 例如5.1.49 如果是MySQL 8.0那么的版本号是8.x.x, 例如 8.0.28 <dependencies><!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java …...
SpringBoot启动报错:java: 无法访问org.springframework.boot.SpringApplication
报错原因:jdk 1.8版本与SpringBoot 3.1.2版本不匹配 解决方案:将SpringBoot版本降到2系列版本(例如2.5.4)。如下图: 修改版本后切记刷新Meavn依赖 然后重新启动即可成功。如下图:...
Vue3 setup语法糖 解决富文本编辑器上传图片64位码过长问题 quill-image-extend-module
引言: 富文本编辑器传图片会解码成64位,非常长导致数据库会报错第一种方法:将数据库类型改成 mediumtext第二种办法:本文中的方法 说明,本周文所用语法糖为Vue3 setup语法,即<script setup> 思路 拦…...
百度坐标(BD09)、国测局坐标(火星坐标,GCJ02)、和WGS84坐标系之间的转换
<!DOCTYPE html> <html><head><meta charset="UTF-8"><title></title></head><body><script>/*** * 百度坐标(BD09)、国测局坐标(火星坐标,GCJ02)、和WGS84坐标系之间的转换*///定义一些常量var x_PI = …...
论文浅尝 | CI4MRC:基于因果推断去除机器阅读理解中的名字偏差
笔记整理:朱珈徵,天津大学硕士,研究方向:问答 链接:https://aclanthology.org/2023.findings-acl.812/ 动机 机器阅读理解(Machine Reading Comprehension,MRC)是根据给定的文章回答…...
【校招VIP】测试计划之黑盒测试白盒测试
考点介绍: 黑盒测试&白盒测试是大厂和三四线公司校招的必考点。黑盒是以结果说话,白盒往往需要理解实现逻辑。现在商业项目的接口测试往往以白盒为主,也就是需要测试同学自己观察和修改数据库的值进行用例的测试。 但是无论采用哪种测试方…...
学习笔记整理-JS-01-语法与变量
文章目录 一、语法与变量1. 初识JavaScript2. JavaScript的历史3. JavaScript与ECMAScript的关系4. JavaScript的体系5. JavaScript的语言风格和特性 二、语法1. JavaScript的书写位置2. 认识输出语句3. REPL环境,交互式解析器4. 变量是什么5. 重点内容 一、语法与变…...
PHP之PHPExcel
include PHPExcel.php; include PHPExcel/Writer/Excel2007.php; //或者include PHPExcel/Writer/Excel5.php; 用于输出.xls的 //创建一个excel $objPHPExcel new PHPExcel(); // 输出Excel表格到浏览器下载 header(Content-Type: application/vnd.ms-excel); header(Content-…...
Redis系列(一):深入了解Redis数据类型和底层数据结构
Redis有以下几种常用的数据类型: redis数据是如何组织的 为了实现从键到值的快速访问,Redis 使用了一个哈希表来保存所有键值对。 Redis全局哈希表(Global Hash Table)是指在Redis数据库内部用于存储所有键值对的主要数据结构。…...
javaScript:如何获取html中的元素对象
目录 前言: 方法 1.通过id获取元素 2.通过标签名获取元素 3.通过类名class获取元素 获取body的方法 1.document.getElementsByTagName(body)[0] 2.document.body 相关代码 前言: 通过获取HTML中的元素对象,JavaScript可以对网页进行动…...
面试总结-webpack/git
说说你对webpack的理解 webpack 是一个静态模块打包器,整个打包过程就像是一条生产线,把资源从入口放进去,经过一系列的加工(loader),最终转换成我们想要的结果,整个加工过程还会有监控&#x…...
深入解析美颜SDK:算法、效果与实现
在当今数字化社会中,图像处理和美化技术已经成为了许多应用领域的重要组成部分,尤其在视频直播领域,美颜技术更是无处不在。直播美颜SDK作为一种集成的软件工具包,为开发者和应用提供了强大的美颜功能。 一、算法原理 磨皮算法…...
ChatGPT Plus和ChatGPT对比
模型规模更大,参数数量超过6万亿,比ChatGPT大很多训练数据更丰富,包括不同语言、领域和类型的数据语言理解和生成能力更强,能够更准确地理解和生成文本可解释性和可控性更好,支持更多的调参和控制参数,生成…...
计算机网络 运输层 TCP连接建立、释放
三报文而不是两报文...
Docker 离线安装指南
参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性,不同版本的Docker对内核版本有不同要求。例如,Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本,Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...
React Native 开发环境搭建(全平台详解)
React Native 开发环境搭建(全平台详解) 在开始使用 React Native 开发移动应用之前,正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南,涵盖 macOS 和 Windows 平台的配置步骤,如何在 Android 和 iOS…...
【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力
引言: 在人工智能快速发展的浪潮中,快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型(LLM)。该模型代表着该领域的重大突破,通过独特方式融合思考与非思考…...
【算法训练营Day07】字符串part1
文章目录 反转字符串反转字符串II替换数字 反转字符串 题目链接:344. 反转字符串 双指针法,两个指针的元素直接调转即可 class Solution {public void reverseString(char[] s) {int head 0;int end s.length - 1;while(head < end) {char temp …...
Unit 1 深度强化学习简介
Deep RL Course ——Unit 1 Introduction 从理论和实践层面深入学习深度强化学习。学会使用知名的深度强化学习库,例如 Stable Baselines3、RL Baselines3 Zoo、Sample Factory 和 CleanRL。在独特的环境中训练智能体,比如 SnowballFight、Huggy the Do…...
Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
中医有效性探讨
文章目录 西医是如何发展到以生物化学为药理基础的现代医学?传统医学奠基期(远古 - 17 世纪)近代医学转型期(17 世纪 - 19 世纪末)现代医学成熟期(20世纪至今) 中医的源远流长和一脉相承远古至…...
安宝特方案丨船舶智造的“AR+AI+作业标准化管理解决方案”(装配)
船舶制造装配管理现状:装配工作依赖人工经验,装配工人凭借长期实践积累的操作技巧完成零部件组装。企业通常制定了装配作业指导书,但在实际执行中,工人对指导书的理解和遵循程度参差不齐。 船舶装配过程中的挑战与需求 挑战 (1…...
视频行为标注工具BehaviLabel(源码+使用介绍+Windows.Exe版本)
前言: 最近在做行为检测相关的模型,用的是时空图卷积网络(STGCN),但原有kinetic-400数据集数据质量较低,需要进行细粒度的标注,同时粗略搜了下已有开源工具基本都集中于图像分割这块,…...
Linux 内存管理实战精讲:核心原理与面试常考点全解析
Linux 内存管理实战精讲:核心原理与面试常考点全解析 Linux 内核内存管理是系统设计中最复杂但也最核心的模块之一。它不仅支撑着虚拟内存机制、物理内存分配、进程隔离与资源复用,还直接决定系统运行的性能与稳定性。无论你是嵌入式开发者、内核调试工…...
