用Rust手把手编写一个wmproxy(代理,内网穿透等), HTTP中的压缩gzip,deflate,brotli算法

项目 ++wmproxy++

gite: https://gitee.com/tickbh/wmproxy

github: https://github.com/tickbh/wmproxy

HTTP中压缩的意义

HTTP中压缩的意义在于降低了网络传输的数据量,从而提高客户端浏览器的访问速度。当然,同时也会增加一点服务器的负担。

HTTP/1.1协议中压缩主要包括gzip压缩和deflate压缩两种方法。其中gzip压缩使用的是LZ77和哈夫曼编码,而deflate压缩使用的是LZ77和哈夫曼编码以及霍夫曼编码。

此外在2015年由Google公司开发的Brotli算法是也基本全面普及开来,Brotli算法的核心原理包括两个部分:预定义的字典和无损压缩算法。预定义的字典是Brotli算法中的一项关键技术,它包含了一些常见的字符序列,例如Web标记、HTML、CSS和JavaScript代码等。Brotli算法的无损压缩算法采用了一种基于模式匹配的压缩方法。它通过预测数据中出现的重复模式,对数据进行压缩。

在HTTP的压缩协议中,这三种压缩算法基本上可以全部被支持。

gzip,deflate,brotli的优劣势

gzip、deflate和brotli这三种压缩算法都有各自的优势和劣势,具体如下:

  1. gzip
  • 优势:是Web上最常见的压缩算法之一,具有较高的压缩效率和广泛的支持程度,可以被几乎所有的浏览器和服务器支持。
  • 劣势:算法的压缩比相对较低,可能会增加文件的大小。
  1. deflate
  • 优势:具有较高的压缩效率和广泛的支持程度,同时算法的实现在不同的浏览器和服务器之间非常一致。
  • 劣势:由于某些实现上的缺陷,可能会导致一些浏览器和服务器无法正常解压缩。
  1. brotli
  • 优势:具有更高的压缩效率和更快的压缩速度,可以进一步减少传输数据的大小,从而提高页面加载速度,并且被较新版本的浏览器和服务器支持。
  • 劣势:由于算法目前仅被较新版本的浏览器和服务器支持,因此需要根据实际情况进行选择。

以下是压缩解压的数率图:





数据来源src

可以看出brotli的压缩比大概在9左右,gzip大概在7左右,deflate也大概在7左右,压缩比brotli最高,适应网络传输慢的情况,压缩速度gzip和deflate相对较快,解压缩deflate较快,brotli和gzip差不多。

rust中三种压缩方式库的选择

通常寻找rust中的第三方库的时候,可以通过https://crates.io/进行选择,这里公开的第三方库都会在这里显示,包括使用次数,流行热度,最近下载量,最近更新时间等,可以从多维度的知道该库的好与坏再进行相应的选择。

  • flate2



    该库支持三种压缩格式的算法,deflate, zlib, gzip,我们选择用他来做deflate, gzip的支持。

  • brotli



    该库如库名一般,只支持brotli算法,相对热度较高,算是支持brolti里最好的一个,我们进行选择。

三种方式的压缩实现

三种方式均可实现流式的压缩,即边写入数据,边读出压缩数据,不用完全的写入所有数据,完整的实现方法在 RecvStream里,将压缩的数据缓存到self.cache_body_data

定义压缩方法值

pub const COMPRESS_METHOD_NONE: i8 = 0;
pub const COMPRESS_METHOD_GZIP: i8 = 1;
pub const COMPRESS_METHOD_DEFLATE: i8 = 2;
pub const COMPRESS_METHOD_BROTLI: i8 = 3;
  • gzip

此处利用的是类use flate2::write::GzEncoder,定义为GzEncoder<BinaryMut>,其中BinaryMut为压缩后的数据,需要具备std::io::Write方法。

Consts::COMPRESS_METHOD_GZIP => {
// 数据结束,需要主动调用结束以导出全部结果
if data.len() == 0 {
self.compress.open_write_gz();
let gz = self.compress.write_gz.take().unwrap();
let value = gz.finish().unwrap();
if value.remaining() > 0 {
Self::inner_encode_data(&mut self.cache_body_data, &value, self.is_chunked)?;
}
if self.is_chunked {
Helper::encode_chunk_data(&mut self.cache_body_data, data)
} else {
Ok(0)
}
} else {
self.compress.open_write_gz();
let gz = self.compress.write_gz.as_mut().unwrap();
gz.write_all(data).unwrap();
// 每次写入,在尝试读取出数据
if gz.get_mut().remaining() > 0 {
let s =
Self::inner_encode_data(&mut self.cache_body_data, &gz.get_mut().chunk(), self.is_chunked);
gz.get_mut().clear();
s
} else {
Ok(0)
}
}
}
  • deflate

此处利用的是类use flate2::write::DeflateEncoder,定义为DeflateEncoder<BinaryMut>,其中BinaryMut为压缩后的数据,需要具备std::io::Write方法。

Consts::COMPRESS_METHOD_DEFLATE => {
// 数据结束,需要主动调用结束以导出全部结果
if data.len() == 0 {
self.compress.open_write_de();
let de = self.compress.write_de.take().unwrap();
let value = de.finish().unwrap();
if value.remaining() > 0 {
Self::inner_encode_data(&mut self.cache_body_data, &value, self.is_chunked)?;
}
if self.is_chunked {
Helper::encode_chunk_data(&mut self.cache_body_data, data)
} else {
Ok(0)
}
} else {
self.compress.open_write_de();
let de = self.compress.write_de.as_mut().unwrap();
de.write_all(data).unwrap();
// 每次写入,在尝试读取出数据
if de.get_mut().remaining() > 0 {
let s =
Self::inner_encode_data(&mut self.cache_body_data, &de.get_mut().chunk(), self.is_chunked);
de.get_mut().clear();
s
} else {
Ok(0)
}
}
}
  • brotli

此处利用的是类use brotli::CompressorWriter;,定义为CompressorWriter<BinaryMut>,其中BinaryMut为压缩后的数据,需要具备std::io::Write方法。

Consts::COMPRESS_METHOD_BROTLI => {
// 数据结束,需要主动调用结束以导出全部结果
if data.len() == 0 {
self.compress.open_write_br();
let mut de = self.compress.write_br.take().unwrap();
de.flush()?;
let value = de.into_inner();
if value.remaining() > 0 {
Self::inner_encode_data(&mut self.cache_body_data, &value, self.is_chunked)?;
}
if self.is_chunked {
Helper::encode_chunk_data(&mut self.cache_body_data, data)
} else {
Ok(0)
}
} else {
self.compress.open_write_br();
let de = self.compress.write_br.as_mut().unwrap();
de.write_all(data).unwrap();
// 每次写入,在尝试读取出数据
if de.get_mut().remaining() > 0 {
let s =
Self::inner_encode_data(&mut self.cache_body_data, &de.get_mut().chunk(), self.is_chunked);
de.get_mut().clear();
s
} else {
Ok(0)
}
}
}

三种方式的解压实现

和压缩不同的是,解压的时候必须将完整的数据进行解压,所以需要收到全部的数据的时候才尝试进行解压,可能我的理解有误,欢迎指出,当下的实现方式可能会占用大量的内存,非我所愿。主要源码在 SendStream中实现。

三种方式均类似,以下

// 收到数据进行缓存,只有到结束时才进行解压缩
match self.compress_method {
Consts::COMPRESS_METHOD_GZIP => {
self.cache_body_data.put_slice(data);
if self.is_end {
self.compress.open_reader_gz(self.cache_body_data.clone());
let gz = self.compress.reader_gz.as_mut().unwrap();
let s = Self::read_all_data(&mut self.cache_buf, &mut self.real_read_buf, gz);
self.cache_body_data.clear();
s
} else {
Ok(0)
}
}
Consts::COMPRESS_METHOD_DEFLATE => {
self.cache_body_data.put_slice(data);
if self.is_end {
self.compress.open_reader_de(self.cache_body_data.clone());
let de = self.compress.reader_de.as_mut().unwrap();
let s = Self::read_all_data(&mut self.cache_buf, &mut self.real_read_buf, de);
self.cache_body_data.clear();
s
} else {
Ok(0)
}
}
Consts::COMPRESS_METHOD_BROTLI => {
self.cache_body_data.put_slice(data);
if self.is_end {
self.compress.open_reader_br(self.cache_body_data.clone());
let br = self.compress.reader_br.as_mut().unwrap();
let s = Self::read_all_data(&mut self.cache_buf, &mut self.real_read_buf, br);
self.cache_body_data.clear();
s
} else {
Ok(0)
}
}
_ => {
self.real_read_buf.put_slice(data);
Ok(data.len())
},
}

如果数据包非常的巨大的时候,可能需要将内存内容写入缓存文件来缓解内存的压力。

结语

压缩为了可以更好的存储,也可以更好的传输,是我们日常生活中必不可少的存在,虽然现在比以前带宽更高,存储比之前的更便宜,但是现在的数据更多,传输延时要求更少,所以高压缩的能力依然非常受欢迎。

13. 用Rust手把手编写一个wmproxy(代理,内网穿透等), HTTP中的压缩gzip,deflate,brotli算法的更多相关文章

  1. 借助FRP反向代理实现内网穿透

    一.frp 是什么? frp 是一个专注于内网穿透的高性能的反向代理应用,支持 TCP.UDP.HTTP.HTTPS 等多种协议.可以将内网服务以安全.便捷的方式通过具有公网 IP 节点的中转暴露到公 ...

  2. 分享一个内网穿透工具frp

    首先简单介绍一下内网穿透: 内网穿透:通过公网,访问局域网里的IP地址与端口,这需要将局域网里的电脑端口映射到公网的端口上:这就需要用到反向代理,即在公网服务器上必须运行一个服务程序,然后在局域网中需 ...

  3. 代理内网上网-iptables

    代理内网上网-iptables 1.1 环境说明 主机A:(能上网) ip:内172.16.1.7/24 外10.0.0.7/24 系统CentOS 6.9 主机B:(不能上网) ip:内172.16 ...

  4. 【代理】内网穿透工具 frp&frps

    frp 是一个高性能的反向代理应用,可以帮助您轻松地进行内网穿透,对外网提供服务,支持 tcp, http, https 等协议类型,并且 web 服务支持根据域名进行路由转发. ### frp 的作 ...

  5. frp实现基于反向代理的内网穿透

    个人博客主页: xzajyjs.cn frp是什么 简单地说,frp就是一个反向代理软件,它体积轻量但功能很强大,可以使处于内网或防火墙后的设备对外界提供服务,它支持HTTP.TCP.UDP等众多协议 ...

  6. 【新晋开源项目】内网穿透神器[中微子代理] 加入 Dromara 开源社区

    1.关于作者 dromara开源组织成员,dromara/neutrino-proxy项目作者 名称:傲世孤尘.雨韵诗泽 名言: 扎根土壤,心向太阳.积蓄能量,绽放微光. 拘浊酒邀明月,借赤日暖苍穹. ...

  7. [原创]K8飞刀20150725 支持SOCKS5代理(内网渗透)

    工具: K8飞刀编译: 自己查壳组织: K8搞基大队[K8team]作者: K8拉登哥哥博客: http://qqhack8.blog.163.com发布: 2015/7/26 3:41:11 简介: ...

  8. iptables之NAT代理-内网访问外网

    1.前言 本文使用NAT功能:内网服务器,想上网又不想被攻击. 工作原理:内网主机向公网发送数据包时,由于目的主机跟源主机不在同一网段,所以数据包暂时发往内网默认网关处理,而本网段的主机对此数据包不做 ...

  9. Mysql-proxy代理内网数据库

    Mysql-proxy 参考:https://segmentfault.com/q/1010000000394160 情景分析:首先您需要正在使用UCloud云主机(uhoust)以及云数据库(udb ...

  10. ssh后门反向代理实现内网穿透

    如图所示,内网主机ginger 无公网IP地址,防火墙只允许ginger连接blackbox.example.com主机 假如你是ginger的管理员root,你想要用tech主机连接ginger主机 ...

随机推荐

  1. SpringIoc容器之Aware

    1 前言 Aware是Spring提供的一个标记超接口,指示bean有资格通过回调样式的方法由Spring容器通知特定的框架对象,以获取到容器中特有对象的实例的方法之一.实际的方法签名由各个子接口确定 ...

  2. Flutter ListView 不满屏 无法滚动

    场景: flutter ListView 在做上拉加载的时候,未list添加controller后,数据不满屏,无法滚动 解决: 未listview 设置: physics: const Always ...

  3. 浅析本地缓存技术-Guava Cache

    1 引言 作为java开发工作者,相信大家对于guava这个工具包都不会太陌生,而对于本地缓存技术guava cache,大家在日常的工作开发中也都有所了解,接下来本文就从各个角度入手来对于Googl ...

  4. vulnhub billu:b0x

    知识点 SQLi.目录爆破.数据库操作.文件包含漏洞.提权.反弹shell 解题步骤 nmap扫描有80,22端口 nmap -sV -Pn -T 4 192.168.220.132 访问网页提示sq ...

  5. Django: AssertionError: `HyperlinkedIdentityField` requires the request in the serializer context. Add `context={'request': request}` when instantiating the serializer.

    错误翻译 AssertionError: ' HyperlinkedIdentityField '需要在序列化器上下文中请求.在实例化序列化器时添加' context={'request': requ ...

  6. 常见的 NoSQL 数据库有哪些?

    前言 今天我们来介绍一下工作开发中常见的一些NoSQL数据库及其基本特点.欢迎在评论区留下文章中没有介绍且好用的​NOSQL数据库. 什么是NOSQL数据库 非关系型数据库又被称为 NoSQL(Not ...

  7. 静态vlan的划分实验

    静态vlan的划分 1,toupu图 2,配置id与子网掩码 2.1,pc,server的ip与子网配置 pc5 pc6 pc7 pc8 server1 server2 3,vlan的静态划分 1,v ...

  8. 文心一言 VS 讯飞星火 VS chatgpt (72)-- 算法导论7.1 3题

    三.请简要地证明:在规模为 n 的子数组上,PARTITION 的时间复杂度为 Θ(n). 文心一言: 首先,我们需要明确PARTITION函数的具体定义.PARTITION函数通常用于快速排序算法中 ...

  9. [minio]简介与安装

    简介 MinIO是一款高性能的分布式对象存储系统. 官网地址 特性 轻便 高性能 跨平台 高扩展性 云原生支持 兼容Amazon S3 基本概念 s3:simple storage service,简 ...

  10. Tibos.Devops项目介绍

    诞生背景 随着微服务的普及,更多的企业选择迁移到云,传统的部署方式已经无法满足需求,市面上devops产品也应运而生,结合自己使用的经验,也制作了一款同类产品,并开源出来,与大家一起探讨学习 前置条件 ...