13. 从零用Rust编写正反向代理, HTTP中的压缩gzip,deflate,brotli算法

wmproxy

wmproxy是由Rust编写,已实现http/https代理,socks5代理, 反向代理,静态文件服务器,内网穿透,配置热更新等, 后续将实现websocket代理等,同时会将实现过程分享出来, 感兴趣的可以一起造个轮子法

项目 ++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())},
}

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

结语

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/285635.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

预测性维护在汽车制造行业中的应用

汽车制造行业是一个高度复杂和精细化的领域&#xff0c;依赖于各种设备来完成生产流程。这些设备包括机械装配线、焊接机器人、喷涂设备、传送带等。然而&#xff0c;这些设备在长时间运行中不可避免地会遇到各种故障&#xff0c;给生产进程带来延误和成本增加。为了应对这一挑…

记录下系统异常到数据库中,方便查找定位,省去翻日志的麻烦。

将异常消息&#xff0c;产生时间&#xff0c;帧信息&#xff0c;操作信息等存入表中&#xff0c;方便查阅修改。适合小系统。大系统没试过。 在异常通知对象HandlerExceptionResolver解析完异常后&#xff0c;构建一个异常相关信息实体。存入表中。代码如下&#xff1a; publ…

Redis-内存模型

参考资料&#xff1a; 极客时间 Redis 亚风 内存管理 从两个问题入手: 1 Redis是如何知道⼀个key是否过期呢&#xff1f; 2 是不是TTL到期就⽴即删除了呢&#xff1f; Redis是K-V内存数据库&#xff0c;所有的K、V都保存在Dict中。不过在其db结构体中有5个Dict&#xff0c;我…

Neural Network——神经网络

1.feature reusing——特征复用 1.1 什么是特征复用 回顾我们之前所学习的模型&#xff0c;本质上都是基于线性回归&#xff0c;但却都可以运用于非线性相关的数据&#xff0c;包括使用了如下方法 增加更多的特征产生新的特征&#xff08;多项式回归&#xff09;核函数 在本身…

C#深拷贝效率对比

对于浅拷贝和深拷贝&#xff0c;前面的文章已经说明了。 C#浅拷贝和深拷贝数据-CSDN博客 本篇说一下&#xff0c;深拷贝的效率问题&#xff0c;效率一直是程序追求的&#xff0c;效率越高肯定越好&#xff0c;有时候功能是实现了&#xff0c;但是运行以及处理数据的效率非常低…

Ubuntu 18.04配置NFS服务器以及配置时遇到NFS问题

1.安装相关软件 sudo apt-get install nfs-kernel-server sudo apt-get install nfs-common 2.配置共享目录 2.1修改exports文件 sudo vi /etc/exports在最后添加如下并保存退出 /home/xiaowu/nfs 192.168.31*(rw,sync,no_root_squash,no_subtree_check) /home/xiaowu/nfs…

【密码学】群的证明(习题)

0.前置知识 1.习题 记录一次密码学作业~群的判定 2.求解

〖大前端 - 基础入门三大核心之JS篇(57)〗- 继承

说明&#xff1a;该文属于 大前端全栈架构白宝书专栏&#xff0c;目前阶段免费&#xff0c;如需要项目实战或者是体系化资源&#xff0c;文末名片加V&#xff01;作者&#xff1a;哈哥撩编程&#xff0c;十余年工作经验, 从事过全栈研发、产品经理等工作&#xff0c;目前在公司…

骨传导耳机和气传导耳机有什么区别?谁更值得入手?

先说答案&#xff0c;骨传导耳机和气传导耳机的佩戴方式和传声方式不同&#xff0c;并且骨传导耳机相比于气传导耳机更值得入手。 一、骨传导耳机和气传导耳机有什么区别 1、佩戴方式不同 骨传导耳机采用一体式耳挂佩戴或耳夹式佩戴&#xff0c;气传导耳机采用分体式耳挂设计…

采样率8kHz~96kHz单端输入ADC芯片CJC1808

音频模/数转换器是一种用于将模拟音频信号转换成数字信号的设备&#xff0c;可以提供高质量的音频转换功能&#xff1b;被广泛应用于录音室、数字音频处理、混音、模拟音频处理等应用中&#xff1b;是当今数字音频处理中不可或缺的一部分。 ADC芯片可以提供宽频带的模拟输入&a…

SpringCloud微服务 【实用篇】| Docker镜像、容器、数据卷操作

目录 一&#xff1a;Docker基本操作 1. 镜像操作 镜像相关命令 2. 容器操作 容器相关命令 3. 数据卷&#xff08;容器数据管理&#xff09; 数据卷 操作数据卷 挂载数据卷 挂载的方式区别 前些天突然发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0…

如何利用CHAT写C程序?

问CHAT&#xff1a;用c语言编写在二维字符数组中查找某个字符串 CHAT回复&#xff1a;以下是一个简单的C程序&#xff0c;它将在二维字符数组中查找特定的字符串。 c #include <stdio.h> #include <string.h> void search(char arr[100][100], int r, char* str) …