13. 用Rust手把手編寫一個wmproxy(代理,內網穿透等), HTTP中的壓縮gzip,deflate,brotli演算法

来源:https://www.cnblogs.com/luojiawaf/archive/2023/10/17/17768820.html
-Advertisement-
Play Games

你知道HTTP中的壓縮演算法是如何工作的嗎, 他們的壓縮比又是多少, 能起到多少作用嗎? 他的限制又是多少嗎? 他存在的意義給我們帶來了什麼? ...


用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
    image.png
    該庫如庫名一般,只支持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())
    },
}

如果數據包非常的巨大的時候,可能需要將記憶體內容寫入緩存文件來緩解記憶體的壓力。

結語

壓縮為了可以更好的存儲,也可以更好的傳輸,是我們日常生活中必不可少的存在,雖然現在比以前帶寬更高,存儲比之前的更便宜,但是現在的數據更多,傳輸延時要求更少,所以高壓縮的能力依然非常受歡迎。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • package chap03; import java.io.IOException;import java.io.PrintWriter;import java.sql.Connection;import java.sql.DriverManager;import java.sql.ResultS ...
  • 一、審核功能實現的方式 1、普通 方案:經辦時入A表,審核後從A表讀取數據,然後操作目標B表; 優勢:思路簡單 劣勢:對後端功能實行高度的嵌入;審核功能數據操作不統一 2、彈框式 方案:前臺實現,操作時判斷是否需要許可權控制,如果需要,則彈出框,由審核人員進行審核,審核通過後,進行後續操作。 優勢:對 ...
  • C語音-數據類型 數據類型 中文名稱 空間大小(bite - 位元組) char 字元串數據類 1 short (int) 短整型 2 int 整形 4 long 長整形 4 long long 更長的整形 8 float 單精度浮點數 4 double 雙精度浮點數 8 include <> int ...
  • 最近這段時間收到了一些讀者的私信,問我某個技術要不要學,還有一些在國外的同學竟然對 Java 圖形化很感興趣,還想找這方面的工作。 比較忙,一直沒抽出時間去回答這類問題,剛好看到我關註的一位大佬回答過,這裡分享一下,希望對你能有幫助。 下麵是正文。 原文鏈接:https://www.zhihu.co ...
  • 網路上的文件傳輸功能也是很有必要實現一下的,網路傳輸文件的過程通常分為客戶端和伺服器端兩部分。客戶端可以選擇上傳或下載文件,將文件分塊並逐塊發送到伺服器,或者從伺服器分塊地接收文件。伺服器端接收來自客戶端的請求,根據請求類型執行對應的操作,並根據發送的文件名或其他標識來確定要傳輸的文件。在實現文件傳... ...
  • 0 概述 通常聲明一個數組時需要使用一個常量來指定數組的長度,數組所占用的記憶體是在編譯時就被分配。這種方式的聲明的優點是簡單,但是存在以下幾個缺點: 使用的元素數量超過數組聲明的長度,當前數組就不能存儲相應的數據; 如果數組的長度被聲明很大,實際使用的元素又比較少會導致記憶體空間的浪費; 程式開發中會 ...
  • 正文 上一篇文章我們講解了事務的Advisor是如何註冊進Spring容器的,也講解了Spring是如何將有配置事務的類配置上事務的,實際上也就是用了AOP那一套,也講解了Advisor,pointcut驗證流程,至此,事務的初始化工作都已經完成了,在之後的調用過程,如果代理類的方法被調用,都會調用 ...
  • 如果多個客戶端同時請求修改同一個kubernetes資源,那麼很有可能收到apiserver返回失敗,本篇就來分析並復現這個問題,然後再實戰client-go官方的解決手段 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...