8. 用Rust手把手编写一个wmproxy(代理,内网穿透等), HTTP改造篇之HPACK原理
用Rust手把手编写一个wmproxy(代理,内网穿透等), HTTP改造篇之HPACK原理
项目 ++wmproxy++
gite: https://gitee.com/tickbh/wmproxy
github: https://github.com/tickbh/wmproxy
HTTP/2的简介
HTTP/1.1发表于1999年,该协议持续被使用到了至今
HTTP/2标准于2015年5月以RFC7540正式发表。由于HTTP2对1.1协议保持有高度的兼容,并且主要以字节传输,相比于1.1有更好的传输效率和更强大的传输能力,所以他快速流行起来
- 在2017年5月,全球排名前1000万的网站中,有13.7%支持了HTTP/2。
- 在2019年6月,全球有36.5%的网站支持了HTTP/2
而http/3的标准基于UDP协议的制定,而UDP在很多场合受限相对严重,由此可以得出http/1.1和http/2将会并存非常长的一段时间。所以我们需要对两种协议进行完全支持
库的选择
在这里我们选择自研的解析库 webparse和HTTP服务框架wenmeng,可以将服务转化成可以支持多协议的结构。此过程可能涉及到重写头信息,也可能添加头信息,会涉及到HTTP2的头部编码问题,所以必须将HTTP2的代码做完整的解析。
HTTP2的定义
HTTP2的RFC主要由7540和补充的7541,因为少量数据的请求如GET请求头占比100%,头部流量的优化变成了极为重要的性能提升点,所以很大的一部分把重点放在头部(7541)的优化。
HPACK: HTTP2的头部压缩
头部信息主要以下部分组成:
Header Field: 一个名称-值对。无论是名称还是值都由8位的字节组成,但名称限定为常见的编码(可解析成String),值可以为纯二进制。
Dynamic Table: 动态表是一个将存储的头部字段与索引值相关联的表。这个表是动态的,特定于一个编码或解码上下文。
Static Table: 静态表是一个静态地将频繁出现的头部字段与索引值相关联的表。这个表是有序的、只读的、始终可访问的,并且可以在所有编码或解码上下文之间共享。定义了一个常用的键值对以更好的压缩如(:method, GET)可由一个字节0x82表示
Header List: 一个头部列表是有序的头字段集合,这些头字段被联合编码并可以包含重复的头字段。一个完整的HTTP/2头部块中的头字段列表就是一个头部列表。如返回:status必须在其它的头前面,请求:method必须在其它前面,另一个因为需要动态添加到动态表里,如果前后顺序不一致的话,可能会导致动态表的映射值不一致,所以必须用列表的形式保证顺序。
Header Field Representation: 一个头字段可以在编码形式中表示为字面量或索引
Header Block: 一个有序的头字段表示列表,当解码时,会产生一个完整的头部列表。
动态表索引值
静态表和动态表如何结合成一个单独的索引地址空间。
在HPACK中,静态表和动态表都被结合到一个单独的索引地址空间中。
索引值在1和静态表长度之间(包括两端)指的是静态表中的元素。
索引值严格大于静态表长度指的是动态表中的元素。需要从索引中减去静态表的长度以找到动态表中的索引。
严格大于两个表长度之和的索引值必须被视为解码错误。
对于一个大小为s的静态表和大小为k的动态表,下面的图表展示了整个有效的索引地址空间。
<---------- 索引地址空间 ---------->
<-- 静态表 --> <-- 动态表 -->
+---+-----------+---+ +---+-----------+---+
| 1 | ... | s | |s+1| ... |s+k|
+---+-----------+---+ +---+-----------+---+
^ |
| V
新值插入位置 超出大小删除位置
如何索引
编码的头部字段可以表示为索引或文字。
索引表示将头部字段定义为静态表或动态表中的条目的引用。
文字表示通过指定其名称和值来定义头部字段。头部字段名称可以文字地表示或作为静态表或动态表中的条目的引用。头部字段值以文字形式表示。
定义了三种不同的文字表示:
将头部字段作为新条目添加到动态表开头的文字表示,如(custom-key, custom-value)。
不将头部字段添加到动态表的文字表示,如(:path, /wmproxy)。
不将头部字段添加到动态表,同时附加规定,此头部字段始终使用文字表示,尤其是当由中间人重新编码时。此表示旨在保护不因压缩而处于危险之中的头部字段值这些文字表示的选择可以通过安全考虑来指导,以保护敏感的头部字段值(如Authorization: xxxx),不能做任何的数据索引缓存。
头部字段名称或头部字段值的文字表示可以直接或使用静态Huffman编码对八位字节序列进行编码。
长度编码
整数被用于表示名称索引、头部字段索引或字符串长度。整数的表示可以在一个八位字节内的任何位置开始。为了允许优化的处理,整数的表示始终在八位字节的末尾完成。
整数由两部分表示:一个前缀,该前缀填充当前八位字节,以及一个可选的八位字节列表,当整数值不适合前缀时使用。前缀的位数(称为N)是整数表示的参数。
如果整数值足够小,即严格小于2^N-1,则在前缀的N位中对其进行编码。
可表示0-31的值,前面三位被用来做别的用途,如string的长度前缀为1,如索引头前缀为2
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| ? | ? | ? | Value |
+---+---+---+-------------------+
图:前缀中编码的整数值(以N=5为例),动态表长度更新即N=5
否则,前缀的所有位均设置为1,并且使用一个或多个八位字节列表对值进行编码,该值减去2^N-1。每个八位字节的最高有效位用作续表标记:其值设置为1,列表中的最后一个八位字节除外。八位字节的其余位用于编码减小后的值。
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 |
+---+---------------------------+
图:前缀后编码的整数值(以N=5为例)
从八位字节列表解码整数值首先从列表中反转八位字节的顺序。然后,对于每个八位字节,删除其最高有效位。将八位字节的剩余位连接起来,并增加2N-1以获得整数值。
前缀大小N始终介于1和8位之间。以八位字节边界开始的整数具有8位前缀。
表示整数I的伪代码如下:
if I < 2^N - 1, encode I on N bits
else
encode (2^N - 1) on N bits #即N位的1
I = I - (2^N - 1)
while I >= 128
encode (I % 128 + 128) on 8 bits
I = I / 128
encode I on 8 bits
解码整数I的伪代码如下:
decode I from the next N bits
if I < 2^N - 1, return I
else
M = 0
repeat
B = next octet
I = I + (B & 127) * 2^M
M = M + 7
while B & 128 == 128
return I
在HPACK中跟长度相关的编解码都用如上方式
字符串的表示
标题字段名称和标题字段值可以表示为字符串文字。字符串文字编码为一系列八进制数字,通过直接编码字符串文字的八进制数字或使用霍夫曼编码(参见[HUFFMAN])。
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| H | String Length (7+) |
+---+---------------------------+
| String Data (Length octets) |
+-------------------------------+
图:字符串文字表示
字符串文字表示包含以下字段:
- H:一个1位的标记H,指示字符串的八进制数字是否使用霍夫曼编码。如果1则表示用HUFFMAN编码,如果为0则普通编码
- 字符串长度: 用于编码字符串文字的八进制数字的数量,编码为具有7位前缀的整数。
- 字符串数据:字符串文字的编码数据。如果H为“0”,则编码数据是字符串文字的原始八进制数字。如果H为“1”,则编码数据是字符串文字的霍夫曼编码。
霍夫曼编码的优势:
在头信息中,可读信息出现的频率远大于不可读字节出现的概率,也就是把0-255字节按照频率出现在概率进行数据的重编码,如‘0’则编码成‘00000’,按照ASCII的方式如果表示0需要8位的编码,这里就可以节省(8-5)/8=37.5%的数据大小。
头信息索引
因为HTTP2是多次复用同一个连接,头基本上会相同,如Cookie,HTTP2会将常用的(静态编码)和常用的(动态编码)将其缓存下来,在下次发送的时候,只要发送一个字节接收方就可以知道对方发送的头文件信息。
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| 1 | Index (7+) |
+---+---------------------------+
图: 头信息索引,Index为长度编码,遵循前缀1的长度编码
增量索引的文字头部字段
增量索引文字头部字段表示法将一个头部字段附加到解码后的头部列表,并将其作为新条目插入到动态表中。
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| 0 | 1 | Index (6+) |
+---+---+-----------------------+
| H | Value Length (7+) |
+---+---------------------------+
| Value String (Length octets) |
+-------------------------------+
表示名字是索引,即Name命中,如:path命中,值为新的值,添加到动态表里
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) |
+-------------------------------+
表示名字和值均没有在原有的表里,现将name和value添加到动态表里。
索引的两个前缀必须为01
,如01000010
十六进制写法则是0x82
,<61,查静态表可得(:method, GET),即我们通过一字节表示请求方法为GET。
以下两种情况前缀为0001开头的则表示不添加到动态表中。
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| 0 | 0 | 0 | 1 | Index (4+) |
+---+---+-----------------------+
| H | Value Length (7+) |
+---+---------------------------+
| Value String (Length octets) |
+-------------------------------+
不进行索引,但是从表中查Name,如authorization。
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) |
+-------------------------------+
从不进行索引
更新动态表的长度
动态表大小更新表示动态表大小发生了变化。
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| 0 | 0 | 1 | Max size (5+) |
+---+---------------------------+
可以设置新的最大的动态表长度。
至此HTTP2的头部压缩协议已基本了解完成,下一章将进行具体示例的分析。
8. 用Rust手把手编写一个wmproxy(代理,内网穿透等), HTTP改造篇之HPACK原理的更多相关文章
- 借助FRP反向代理实现内网穿透
一.frp 是什么? frp 是一个专注于内网穿透的高性能的反向代理应用,支持 TCP.UDP.HTTP.HTTPS 等多种协议.可以将内网服务以安全.便捷的方式通过具有公网 IP 节点的中转暴露到公 ...
- 分享一个内网穿透工具frp
首先简单介绍一下内网穿透: 内网穿透:通过公网,访问局域网里的IP地址与端口,这需要将局域网里的电脑端口映射到公网的端口上:这就需要用到反向代理,即在公网服务器上必须运行一个服务程序,然后在局域网中需 ...
- 【代理】内网穿透工具 frp&frps
frp 是一个高性能的反向代理应用,可以帮助您轻松地进行内网穿透,对外网提供服务,支持 tcp, http, https 等协议类型,并且 web 服务支持根据域名进行路由转发. ### frp 的作 ...
- frp实现基于反向代理的内网穿透
个人博客主页: xzajyjs.cn frp是什么 简单地说,frp就是一个反向代理软件,它体积轻量但功能很强大,可以使处于内网或防火墙后的设备对外界提供服务,它支持HTTP.TCP.UDP等众多协议 ...
- 【新晋开源项目】内网穿透神器[中微子代理] 加入 Dromara 开源社区
1.关于作者 dromara开源组织成员,dromara/neutrino-proxy项目作者 名称:傲世孤尘.雨韵诗泽 名言: 扎根土壤,心向太阳.积蓄能量,绽放微光. 拘浊酒邀明月,借赤日暖苍穹. ...
- 代理内网上网-iptables
代理内网上网-iptables 1.1 环境说明 主机A:(能上网) ip:内172.16.1.7/24 外10.0.0.7/24 系统CentOS 6.9 主机B:(不能上网) ip:内172.16 ...
- [原创]K8飞刀20150725 支持SOCKS5代理(内网渗透)
工具: K8飞刀编译: 自己查壳组织: K8搞基大队[K8team]作者: K8拉登哥哥博客: http://qqhack8.blog.163.com发布: 2015/7/26 3:41:11 简介: ...
- Mysql-proxy代理内网数据库
Mysql-proxy 参考:https://segmentfault.com/q/1010000000394160 情景分析:首先您需要正在使用UCloud云主机(uhoust)以及云数据库(udb ...
- ssh后门反向代理实现内网穿透
如图所示,内网主机ginger 无公网IP地址,防火墙只允许ginger连接blackbox.example.com主机 假如你是ginger的管理员root,你想要用tech主机连接ginger主机 ...
- CentOS squid代理内网主机上网 openVpn配置
随机推荐
- 声音克隆,精致细腻,人工智能AI打造国师“一镜到底”鬼畜视频,基于PaddleSpeech(Python3.10)
电影<满江红>上映之后,国师的一段采访视频火了,被无数段子手恶搞做成鬼畜视频,诚然,国师的这段采访文本相当经典,他生动地描述了一个牛逼吹完,大家都信了,结果发现自己没办法完成最后放弃,随后 ...
- Blazor前后端框架Known-V1.2.2
V1.2.2 Known是基于C#和Blazor开发的前后端分离快速开发框架,开箱即用,跨平台,一处代码,多处运行. 概述 基于C#和Blazor实现的快速开发框架,前后端分离,开箱即用. 跨平台,单 ...
- CAN转PROFINET协议网关 JM-PN-CAN
1 产品功能 捷米特JM-PN-CAN 是自主研发的一款 PROFINET 从站功能的通讯网关.主要功能是将各种 CAN 设备接入到 PROFINET 网络中. 捷米特JM-PN-CAN连接到 ...
- 一张图告诉你如何提高 API 性能
API 性能是指一个 API 在执行其功能时的效率和性能表现,通常用于衡量 API 的响应时间.吞吐量.可伸缩性和稳定性等方面的表现. API 性能的指标包括: 响应时间: API 的响应时间是指从发 ...
- CS144 LAB5~LAB6
CS144 lab5~6 最后两个lab了,虽然很多大佬都说剩下的两个lab比起TCP的实现,"简直太简单了",但是我认为做这两个之前需要补充一些额外的网络知识,不然直接上手去做的 ...
- Redis核心技术与实践 02 | 数据结构:快速的Redis有哪些慢操作?
原文地址:https://time.geekbang.org/column/article/268262 博客地址:http://njpkhuan.cn/archives/redis-he-xin-j ...
- Tomcat改jar
Tomcat改jar 插件修改 <!-- <plugin>--> <!-- <groupId>org.apache.maven.plugins</gro ...
- AI识别检验报告 -PaddleNLP UIE-X 在医疗领域的实战
目录 UIE-X在医疗领域的实战 1.项目背景 2.案例简介 3.环境准备 数据转换 5.模型微调 6.模型评估 7.Taskflow一键部署 UIE-X在医疗领域的实战 PaddleNLP全新发布U ...
- IIC、SPI、UART三者对比
下面将对比三者的各自差异: 参考资料: 1.(112条消息) UART, SPI, IIC的详解及三者的区别和联系_iic spi uart_静思心远的博客-CSDN博客
- Robot Framework 自动化测试随笔(一)
一.安装Robot Framework步骤(安装目录避免中文和特殊字符,建议以管理员身份进行全部安装过程): 1.查看ride最新支持的python版本,据此下载对应python版本: https:/ ...