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配置
随机推荐
- MySQL8新特性窗口函数详解
本文博主给大家详细讲解一波 MySQL8 的新特性:窗口函数,相信大伙看完一定能有所收获. 本文提供的 sql 示例都是基于 MySQL8,由博主亲自执行确保可用 博主github地址:http:// ...
- 统信UOS系统开发笔记(七):在统信UOS系统上使用linuxdeployqt发布qt程序
前言 在ubuntu上发布qt程序相对还好,使用脚本,但是在统信UOS麒麟上发布的时候,因为银河麒麟等不同版本,使用脚本就不太兼容,同时为了实现直接点击应用可以启动应用的效果,使用linuxdep ...
- Winform 巨好看的控件库推荐:MaterialSkin.2
MaterialSkin.2 控件包是在 MaterialSkin 及基础上二次开发而来的,在原控件基础上修复了一些Bug,丰富了主题以及动画效果,效果非常好. MaterialSkin.2 现在处于 ...
- Java 集合框架体系简介
为什么要使用集合 存储多个数据可以使用数组,但由于数组在内存中是连续存储的,所以会有一些限制.比如数组在创建时就要指定长度,即可以容纳的元素个数,且指定后无法更改:数组在创建时需要指定元素的类型,并且 ...
- MYSQL之批量删除(mybatis)
如果参数是array数组 <update id="deleteAll"> delete from C_V WHERE UUID in <foreach item= ...
- 【Azure Event Hub】自定义告警(Alert Rule)用来提示Event Hub的消息incoming(生产)与outgoing(消费)的异常情况
问题描述 在使用Azure Service Bus的时候,我们可以根据Queue中目前存在的消息数来判断当前消息是否有积压的情况. 但是,在Event Hub中,因为所有消息都会被存留到预先设定的保留 ...
- 每日一题力扣 1262 https://leetcode.cn/problems/greatest-sum-divisible-by-three/
. 题解 这道题目核心就算是要知道如果x%3=2的话,应该要去拿%3=1的数字,这样子才能满足%3=0 贪心 sum不够%3的时候,就减去余数为1的或者余数为2的 需要注意 两个余数为1会变成余数为2 ...
- 多光源渲染方案 - Many Lights Sampling
目录 Importance Sampling(IS) Light BVH [2018~2019] 预构建 BVH 重建 BVH 基于 BVH node 的 IS Real-time Stochasti ...
- Mysql基础4-数据查询
一.DQL介绍 DQL全称:Data Query Language(数据查询语言),用来查询数据库中表的记录. 关键字:select 二.DQL语法 select 字段列表 from 表名列表 whe ...
- CS与反向代理
Cobalt Strike Cobalt Strike是一款基于java的渗透测试神器,常被业界人称为CS神器.自3.0以后已经不在使用Metasploit框架而作为一个独立的平台使用,分为客户端与服 ...