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连接建立、释放
三报文而不是两报文...
IDEA运行Tomcat出现乱码问题解决汇总
最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…...
vue3 定时器-定义全局方法 vue+ts
1.创建ts文件 路径:src/utils/timer.ts 完整代码: import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...
leetcodeSQL解题:3564. 季节性销售分析
leetcodeSQL解题:3564. 季节性销售分析 题目: 表:sales ---------------------- | Column Name | Type | ---------------------- | sale_id | int | | product_id | int | | sale_date | date | | quantity | int | | price | decimal | -…...
Yolov8 目标检测蒸馏学习记录
yolov8系列模型蒸馏基本流程,代码下载:这里本人提交了一个demo:djdll/Yolov8_Distillation: Yolov8轻量化_蒸馏代码实现 在轻量化模型设计中,**知识蒸馏(Knowledge Distillation)**被广泛应用,作为提升模型…...
【电力电子】基于STM32F103C8T6单片机双极性SPWM逆变(硬件篇)
本项目是基于 STM32F103C8T6 微控制器的 SPWM(正弦脉宽调制)电源模块,能够生成可调频率和幅值的正弦波交流电源输出。该项目适用于逆变器、UPS电源、变频器等应用场景。 供电电源 输入电压采集 上图为本设计的电源电路,图中 D1 为二极管, 其目的是防止正负极电源反接, …...
MFC 抛体运动模拟:常见问题解决与界面美化
在 MFC 中开发抛体运动模拟程序时,我们常遇到 轨迹残留、无效刷新、视觉单调、物理逻辑瑕疵 等问题。本文将针对这些痛点,详细解析原因并提供解决方案,同时兼顾界面美化,让模拟效果更专业、更高效。 问题一:历史轨迹与小球残影残留 现象 小球运动后,历史位置的 “残影”…...
Vue ③-生命周期 || 脚手架
生命周期 思考:什么时候可以发送初始化渲染请求?(越早越好) 什么时候可以开始操作dom?(至少dom得渲染出来) Vue生命周期: 一个Vue实例从 创建 到 销毁 的整个过程。 生命周期四个…...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现指南针功能
指南针功能是许多位置服务应用的基础功能之一。下面我将详细介绍如何在HarmonyOS 5中使用DevEco Studio实现指南针功能。 1. 开发环境准备 确保已安装DevEco Studio 3.1或更高版本确保项目使用的是HarmonyOS 5.0 SDK在项目的module.json5中配置必要的权限 2. 权限配置 在mo…...
机器学习的数学基础:线性模型
线性模型 线性模型的基本形式为: f ( x ) ω T x b f\left(\boldsymbol{x}\right)\boldsymbol{\omega}^\text{T}\boldsymbol{x}b f(x)ωTxb 回归问题 利用最小二乘法,得到 ω \boldsymbol{\omega} ω和 b b b的参数估计$ \boldsymbol{\hat{\omega}}…...
Python环境安装与虚拟环境配置详解
本文档旨在为Python开发者提供一站式的环境安装与虚拟环境配置指南,适用于Windows、macOS和Linux系统。无论你是初学者还是有经验的开发者,都能在此找到适合自己的环境搭建方法和常见问题的解决方案。 快速开始 一分钟快速安装与虚拟环境配置 # macOS/…...
