8. 用Rust手把手編寫一個wmproxy(代理,內網穿透等), HTTP改造篇之HPACK原理

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

用Rust手把手編寫一個wmproxy(代理,內網穿透等), HTTP改造篇之HPACK原理 項目 ++wmproxy++ gite: https://gitee.com/tickbh/wmproxy github: https://github.com/tickbh/wmproxy HTTP/2的 ...


用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的頭部壓縮協議已基本瞭解完成,下一章將進行具體示例的分析。


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

-Advertisement-
Play Games
更多相關文章
  • Sleuth 簡介 隨著業務的發展,系統規模變得越來越大,微服務拆分越來越細,各微服務間的調用關係也越來越複雜。客戶端請求在後端系統中會經過多個不同的微服務調用來協同產生最後的請求結果,幾平每一個請求都會形成一個複雜的分散式服務調用鏈路,在每條鏈路中任何一個依賴服務出現延遲超時或者錯誤都有可能引起整 ...
  • go語言-Go環境搭建 下載 https://golang.org/dl/ 切換root許可權 su root 進入用戶列表 cd /usr/local/ 解壓縮 tar -zxvf go1.13.linux-amd64.tar.gz 設置go環境變數 vi /etc/profile export G ...
  • 基於java智能物流管理系統設計與實現,可適用於校園物流管理系統,物流配送系統,快遞物流管理,物流追蹤系統,物流系統,物流運輸系統,javaweb物流系統,springboot物流管理系統,javaweb智能物流系統等等; ...
  • 來源:www.cnblogs.com/tjstep/p/15256463.html mybatis作為一個輕量級的ORM框架,應用廣泛,其上手使用也比較簡單;一個成熟的框架,必然有精巧的設計,值得學習。 在使用mybatis框架時,在sql語句中獲取傳入的參數有如下兩種方式: ${paramName ...
  • 今天主要說說如何通過自定義註解的方式,在 Spring Boot 中來實現 AOP 切麵統一列印出入參日誌。小伙伴們可以收藏一波。 廢話不多說,進入正題! 一、先看看切麵日誌輸出效果 在看看實現方法之前,我們先看下切麵日誌輸出效果咋樣: 從上圖中可以看到,每個對於每個請求,開始與結束一目瞭然,並且打 ...
  • 1 SPI簡介 1.1 SPI(Service Provider Interface) 本質:將介面實現類的全限定名配置在文件中,並由服務載入器讀取配置文件,載入實現類。這樣可以在運行時,動態為介面替換實現類。 java SPI:用來設計給服務提供商做插件使用的。基於策略模式來實現動態載入的機制。我 ...
  • Go 1.21中新增的 slices包中提供了很多與切片相關的函數,適用於任意類型的切片。 本文內容來自官方文檔 BinarySearch 函數簽名如下: func BinarySearch[S ~[]E, E cmp.Ordered](x S, target E) (int, bool) Bina ...
  • 哈嘍大家好,我是鹹魚 好久不見甚是想念,2023 年最後一次法定節假日已經結束了,不知道各位小伙伴是不是跟鹹魚一樣今天就開始“搬磚”了呢? 我們知道元組(tuple)是 Python 的內置數據類型,tuple 是一個不可變的值序列 tuple 的元素可以是任何類型,一般用在存儲異構數據(例如資料庫 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...