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

news2024/11/24 22:49:09

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.coloradmin.cn/o/1321487.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

IDEA版SSM入门到实战(Maven+MyBatis+Spring+SpringMVC) -Spring的AOP前奏

第一章 AOP前奏 1.1 代理模式 代理模式&#xff1a;我们需要做一件事情&#xff0c;又不期望自己亲力亲为&#xff0c;此时&#xff0c;可以找一个代理【中介】 我们【目标对象】与中介【代理对象】不能相互转换&#xff0c;因为是“兄弟”关系 1.2 为什么需要代理【程序中…

MQTT中的遗嘱和证明(Last Will and Testament, LWT)

在MQTT中&#xff0c;遗嘱和证明&#xff08;Last Will and Testament, LWT&#xff09;是一个很有用的功能&#xff0c;允许客户端指定一条消息&#xff0c;当非预期的断开连接发生时&#xff0c;由代理&#xff08;broker&#xff09;代替它们自动发布出去。提供了一个可靠通…

Redis原理之网络模型笔记

目录 1. 阻塞IO 2. 非堵塞IO 3. IO多路复用 ​3.1 select 3.2 poll 3.3 epoll 4. 信号驱动IO 5. 异步IO 6. Redis是单线程还是多线程 Redis采用单线程模型&#xff0c;这意味着一个Redis服务器在任何时刻都只会处理一个请求。Redis的网络模型涉及到阻塞I/O&#xff08;Blo…

基于双目RGB图像和图像深度信息的三维室内场景建模matlab仿真

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1 双目视觉原理 4.2 深度信息获取 4.3 表面重建 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 matlab2022a 3.部分核心程序 .....................................…

快速处理EDI数据映射:知行EDI Profiler 操作指南

一个完整的EDI项目通常由建立传输通道&#xff0c;处理数据映射以及集成内部业务系统三部分组成。对用户而言&#xff0c;基于知行之桥EDI系统进行自主实施最大的挑战便是处理数据映射。EDI报文读不懂&#xff0c;映射关系太复杂……这些问题给企业造成困扰的同时也阻挡了自主实…

【Linux】进程状态、进程优先级和进程切换

> 作者简介&#xff1a;დ旧言~&#xff0c;目前大二&#xff0c;现在学习Java&#xff0c;c&#xff0c;c&#xff0c;Python等 > 座右铭&#xff1a;松树千年终是朽&#xff0c;槿花一日自为荣。 > 目标&#xff1a;了解冯诺依曼体系结构与操作系统&#xff0c;掌握…

RLC防孤岛负载测试:电力系统安全运行的重要保障

在电力系统中&#xff0c;孤岛效应是一个严重的问题&#xff0c;它可能导致电力系统的不稳定甚至崩溃。为了确保电力系统的安全运行&#xff0c;必须进行RLC防孤岛负载测试。RLC防孤岛负载测试是一种模拟电网故障后&#xff0c;对电力系统进行检测的方法&#xff0c;主要用于检…

css实现边框彩虹跑马灯效果

效果展示 代码实战 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevice-…

Ubuntu20.04 下编译安装 ffmpeg 和 ffplay

Ubuntu20.04 下编译安装 ffmpeg 和 ffplay 一、下载源码包二、安装依赖库三、编译四、添加环境变量五、验证是否成功六、问题 一、下载源码包 1.1 官方下载链接&#xff1a;http://ffmpeg.org/download.html 最新版本为6.1&#xff0c;点击 Download Source Code下载即可 &…

Apache Seatunnel本地源码构建编译运行调试

Apache Seatunnel本地源码构建编译运行调试 文章目录 1. 环境准备1.1 Java环境1.2 Maven1.3 IDEA1.4 Docker环境1.5 Mysql8.0.281.6 其它环境准备 2. 源码包下载3. idea项目配置3.1 项目导入3.2 maven配置3.3 项目JDK配置3.4 项目启动参数配置3.4.1 seatunnel项目启动参数配置3…

21.Servlet 技术

JavaWeb应用的概念 在Sun的Java Servlet规范中&#xff0c;对Java Web应用作了这样定义&#xff1a;“Java Web应用由一组Servlet、HTML页、类、以及其它可以被绑定的资源构成。它可以在各种供应商提供的实现Servlet规范的 Servlet容器 中运行。” Java Web应用中可以包含如下…

lua语法

lua语法 1.lua数据类型 lua 脚本输出乱码&#xff0c;将lua脚本改为UTF-8编码&#xff0c;并且需要DOS下修改代码页&#xff1a;CHCP 65001 即可。 基本语法 注释 print("script lua win")-- 单行注释--[[多行注释]]--标识符 类似于&#xff1a;java当中 变量、…

ASP.NET MVC+EntityFramework图片头像上传

1&#xff0c;先展示一下整体的效果 2&#xff0c;接下来展示用户添加以及上传头像代码、添加用户界面 前端代码如下&#xff1a; <div class"form-group">Html.LabelFor(model > model.img, "头像&#xff1a;", htmlAttributes: new { class &…

网络协议小记

一、TCP/IP协议 作为一个小萌新&#xff0c;当然我无法将tcp/ip协议的大部分江山和盘托出&#xff0c;但是其中很多面试可能问到的知识&#xff0c;我觉得有必要总结一下&#xff01; 首先&#xff0c;在学习tcp/ip协议之前&#xff0c;我们必须搞明白什么是tcp/ip协议。 1、…

IND-CCA DEM:AE AEAD

参考文献&#xff1a; [BN00] Bellare M, Namprempre C. Authenticated encryption: Relations among notions and analysis of the generic composition paradigm[C]//International Conference on the Theory and Application of Cryptology and Information Security. Berl…

网络安全之Linux环境配置及Linux基础知识讲解<三>

目录 一.下载安装Vmware二.下载安装Kali三.Linux目录结构四.Linux文件属性五.文件目录管理六.vim编辑器 一.下载安装Vmware Vmware官网&#xff1a;https://www.vmware.com 二.下载安装Kali Kali包含数百种工具&#xff0c;可用于各种信息安全任务&#xff0c;例如渗透测试、…

静态路由及动态路由

文章目录 静态路由及动态路由一、静态路由基础1. 静态路由配置2. 负载分担3. 路由备份4. 缺省路由5. 静态路由实操 二、RIP 动态路由协议1. RIP 协议概述2. RIP 协议版本对比2.1 有类路由及无类路由 3. RIP 路由协议原理4. RIP 计时器5. 度量值6. 收敛7. 示例 静态路由及动态路…

ADB:获取坐标

命令&#xff1a; adb shell getevent | grep -e "0035" -e "0036" adb shell getevent -l | grep -e "0035" -e "0036" 这一条正确&#xff0c;但是&#xff0c;grep给过滤了&#xff0c;导致没有输出 getevent -c 10 //输出10条信息…

【Apache-StreamPark】Flink 开发利器 StreamPark 的介绍、安装、使用

【Apache-StreamPark】Flink 开发利器 StreamPark 的介绍、安装、使用 1&#xff09;框架介绍与引入1.1.&#x1f680; 什么是 StreamPark1.2.&#x1f389; Features1.3.&#x1f3f3;‍&#x1f308; 组成部分1.4.引入 StreamPark 2&#xff09;安装部署2.1.环境要求2.2.Hado…

electron与cesium组件入门应用功能

electron与cesium组件入门应用功能 运行应用效果图&#xff1a; electron应用目录&#xff0c;需要包括三个文件: index.html main.js package.json (一)、创建一个新项目 目录名称&#xff1a;project_helloWolrd (二)、生成package.json文件 npm init --yes(三&#x…