.Net Core HttpClient處理響應壓縮詳細
前言:
在之前的文章ASP.NET Core中的響應壓縮的實現(xiàn)提到,服務端的主要工作就是根據(jù)Content-Encoding
頭信息判斷采用哪種方式壓縮并返回。之前在群里有人問道過,現(xiàn)在的網(wǎng)絡帶寬這么高了還有必要在服務端針對請求進行壓縮嗎?確實,如今分布式和負載均衡技術這么成熟,很多需要處理高并發(fā)大數(shù)據(jù)的場景都可以通過增加服務器節(jié)點來進行。但是,在資源受限的情況下,或者是還沒必要為了某一個點去增加新的服務器節(jié)點的時候,我們還是要采用一些程序本身的常規(guī)處理手段來進行處理。筆者個人認為響應壓縮的使用場景是這樣的,在帶寬壓力比較緊張的情況,且CPU資源比較充足的情況下,使用響應壓縮整體效果還是比較明顯的。
有壓縮就有解壓,而解壓的工作就是在請求客戶端處理的。比如瀏覽器,這是我們最常用的Http客戶端,許多瀏覽器都是默認在我們發(fā)出請求的時候(比如我們?yōu)g覽網(wǎng)頁的時候)在Request Head
中添加Content-Encoding
,然后根據(jù)響應信息處理相關解壓。這些都源于瀏覽器已經內置了關于請求壓縮和解壓的機制。類似的還有許多,比如常用的代理抓包工具Filder
也是內置這種機制的。只不過需要手動去處理,但實現(xiàn)方式都是一樣的。有時候我們在自己寫程序的過程中也需要使用這種機制,在傳統(tǒng)的.Net HttpWebRequest
類庫中,并沒有這種機制,后來版本中加入了HttpClient
,有自帶的機制可以處理這種操作,
一、使用方式
首先我們來看一下直接在HttpClient
中如何處理響應壓縮
//自定義HttpClientHandler實例 HttpClientHandler httpClientHandler = new HttpClientHandler { AutomaticDecompression = DecompressionMethods.GZip }; //使用傳遞自定義HttpClientHandler實例的構造函數(shù) using (HttpClient client = new HttpClient(httpClientHandler)) { var response = await client.GetAsync($"http://MyDemo/Home/GetPerson?userId={userId}"); }
這個操作還是非常簡單的,我們操作的并不是HttpClient
的屬性而是HttpClientHandler
中的屬性,我們在之前的文章[.NET Core HttpClient
源碼探究]中曾探討過,HttpClient
的本質其實就是HttpMessageHandler
,而HttpClient
真正使用到的是HttpMessageHandler
最重要的一個子類HttpClientHandler
,所有的請求操作都是通過HttpMessageHandler
進行的。我們可以看到AutomaticDecompression
接受的是DecompressionMethods
枚舉,既然是枚舉就說明包含了不止一個值,接下來我們查看DecompressionMethods
中的源碼
[Flags] public enum DecompressionMethods { // 使用所有壓縮解壓縮算法。 All = -1, // 不使用解壓 None = 0x0, // 使用gzip解壓算法 GZip = 0x1, // 使用deflate解壓算法 Deflate = 0x2, // 使用Brotli解壓算法 Brotli = 0x4 }
該枚舉默認都是針對常用輸出解壓算法,接下來我們看一下在HttpClientFactory
中如何處理響應壓縮。在之前的文章[.NET Core HttpClientFactory+Consul
實現(xiàn)服務發(fā)現(xiàn)]中我們曾探討過HttpClientFactory
的大致工作方式默認PrimaryHandler
傳遞的就是HttpClientHandler實例,而且在我們注冊HttpClientFactory
的時候是可以通過ConfigurePrimaryHttpMessageHandler
自定義PrimaryHandler
的默認值,接下來我們具體代碼實現(xiàn)
services.AddHttpClient("mydemo", c => { c.BaseAddress = new Uri("http://MyDemo/"); }).ConfigurePrimaryHttpMessageHandler(provider=> new HttpClientHandler { AutomaticDecompression = DecompressionMethods.GZip });
其實在注冊HttpClientFactory
的時候還可以使用自定義的HttpClient
,具體的使用方式是這樣的
services.AddHttpClient("mydemo", c => { c.BaseAddress = new Uri("http://MyDemo/"); }).ConfigureHttpClient(provider => new HttpClient(new HttpClientHandler { AutomaticDecompression = DecompressionMethods.GZip }));
HttpClient
確實幫我們做了好多事情,只需要簡單的配置一下就開啟了針對響應壓縮的處理。這更勾起了我們對HttpClient
的探討,接下來我們就通過源碼的方式查看它是如何發(fā)起可響應壓縮請求,并解壓響應結果的。
二、源碼探究
通過上面的使用方式我們得知,無論使用哪種形式,最終都是針對HttpClientHandler
做配置操作,接下來我們查看HttpClientHandler
類中AutomaticDecompression
屬性的代碼
public DecompressionMethods AutomaticDecompression { get => _underlyingHandler.AutomaticDecompression; set => _underlyingHandler.AutomaticDecompression = value; }
它本身的值操作來自_underlyingHandler
這個對象,也就是說讀取和設置都是在操作_underlyingHandler.AutomaticDecompression
,我們查找到_underlyingHandler
對象的聲明位置
private readonly SocketsHttpHandler _underlyingHandler;
這里說明一下,HttpClient
的實質工作類是HttpClientHandler
,而HttpClientHandler
真正發(fā)起請求是依靠的SocketsHttpHandler
這個類,也就是說SocketsHttpHandler
是最原始發(fā)起請求的類。HttpClientHandler
本質還是通過SocketsHttpHandler
發(fā)起的Http請求,接下來我們就查看SocketsHttpHandler
類是如何處理AutomaticDecompression
這個屬性的
public DecompressionMethods AutomaticDecompression { get => _settings._automaticDecompression; set { CheckDisposedOrStarted(); _settings._automaticDecompression = value; } }
這里的_settings
不再是具體的功能類,而是用于初始化或者保存SocketsHttpHandler
的部分屬性值的配置類
private readonly HttpConnectionSettings _settings = new HttpConnectionSettings();
這里我們不在分析SocketsHttpHandler
出處理響應壓縮之外的其他代碼,所以具體就不再看這些了,直接查找_settings._automaticDecompression
屬性引用的地方,最終找到了這段代碼
if (settings._automaticDecompression != DecompressionMethods.None) { handler = new DecompressionHandler(settings._automaticDecompression, handler); }
這里就比較清晰了,真正處理請求響應壓縮相關的都是在DecompressionHandler
中。正如我們之前所說的,HttpClient
真正的工作方式就是一些實現(xiàn)自HttpMessageHandler
的子類在工作,它把不同功能的實現(xiàn)模塊都封裝成了具體的Handler
中。當你需要使用哪個模塊的功能,直接使用對應的Handler
操作類去發(fā)送處理請求即可。這種設計思路在ASP.NET Core
中體現(xiàn)的也是淋漓盡致,ASP.NET Core
采用的是構建不同終結點去處理和輸出請求。通過這些我們可以得知DecompressionHandler
才是今天的主題,接下來我們就來查看DecompressionHandler
類的源碼,我們先來看最核心的SendAsync
方法,這個方法是發(fā)送請求的執(zhí)行方法
internal override async ValueTask<HttpResponseMessage> SendAsync(HttpRequestMessage request, bool async, CancellationToken cancellationToken) { //判斷是否是GZIP壓縮請求,如果是則添加請求頭Accept-Encoding頭為gzip if (GZipEnabled && !request.Headers.AcceptEncoding.Contains(s_gzipHeaderValue)) { request.Headers.AcceptEncoding.Add(s_gzipHeaderValue); } //判斷是否是Deflate壓縮請求,如果是則添加請求頭Accept-Encoding頭為deflate if (DeflateEnabled && !request.Headers.AcceptEncoding.Contains(s_deflateHeaderValue)) { request.Headers.AcceptEncoding.Add(s_deflateHeaderValue); } //判斷是否是Brotli壓縮請求,如果是則添加請求頭Accept-Encoding頭為brotli if (BrotliEnabled && !request.Headers.AcceptEncoding.Contains(s_brotliHeaderValue)) { request.Headers.AcceptEncoding.Add(s_brotliHeaderValue); } //發(fā)送請求 HttpResponseMessage response = await _innerHandler.SendAsync(request, async, cancellationToken).ConfigureAwait(false); Debug.Assert(response.Content != null); //獲取返回的Content-Encoding輸出頭信息 ICollection<string> contentEncodings = response.Content.Headers.ContentEncoding; if (contentEncodings.Count > 0) { string? last = null; //獲取最后一個值 foreach (string encoding in contentEncodings) { last = encoding; } //根據(jù)響應頭判斷服務端采用的是否為gzip壓縮 if (GZipEnabled && last == Gzip) { //使用gzip解壓算法解壓返回內容,并從新賦值到response.Content response.Content = new GZipDecompressedContent(response.Content); } //根據(jù)響應頭判斷服務端采用的是否為deflate壓縮 else if (DeflateEnabled && last == Deflate) { //使用deflate解壓算法解壓返回內容,并從新賦值到response.Content response.Content = new DeflateDecompressedContent(response.Content); } //根據(jù)響應頭判斷服務端采用的是否為brotli壓縮 else if (BrotliEnabled && last == Brotli) { //使用brotli解壓算法解壓返回內容,并從新賦值到response.Content response.Content = new BrotliDecompressedContent(response.Content); } } return response; }
通過上面的邏輯我們可以看到GZipEnabled
、DeflateEnabled
、BrotliEnabled
三個bool類型的變量,中三個變量決定了采用哪種請求壓縮方式,主要實現(xiàn)方式是
internal bool GZipEnabled => (_decompressionMethods & DecompressionMethods.GZip) != 0; internal bool DeflateEnabled => (_decompressionMethods & DecompressionMethods.Deflate) != 0; internal bool BrotliEnabled => (_decompressionMethods & DecompressionMethods.Brotli) != 0;
主要就是根據(jù)我們配置的DecompressionMethods
枚舉值判斷想獲取哪種方式的壓縮結果,解壓的實現(xiàn)邏輯都封裝在GZipDecompressedContent
、DeflateDecompressedContent
、BrotliDecompressedContent
中,我們看一下他們的具體的代碼
private sealed class GZipDecompressedContent : DecompressedContent { public GZipDecompressedContent(HttpContent originalContent) : base(originalContent) { } //使用GZipStream類對返回的流進行解壓 protected override Stream GetDecompressedStream(Stream originalStream) => new GZipStream(originalStream, CompressionMode.Decompress); } private sealed class DeflateDecompressedContent : DecompressedContent { public DeflateDecompressedContent(HttpContent originalContent) : base(originalContent) { } //使用DeflateStream類對返回的流進行解壓 protected override Stream GetDecompressedStream(Stream originalStream) => new DeflateStream(originalStream, CompressionMode.Decompress); } private sealed class BrotliDecompressedContent : DecompressedContent { public BrotliDecompressedContent(HttpContent originalContent) : base(originalContent) { } //使用BrotliStream類對返回的流進行解壓 protected override Stream GetDecompressedStream(Stream originalStream) => new BrotliStream(originalStream, CompressionMode.Decompress); } }
其主要的工作方式就是使用對應壓縮算法的解壓方法得到原始信息。簡單總結一下,HttpClient
關于壓縮相關的處理機制是,首先根據(jù)你配置的DecompressionMethods
判斷你想使用那種壓縮算法。然后匹配到對應的壓縮算法后添加Accept-Encoding
請求頭為你期望的壓縮算法。最后根據(jù)響應結果獲取Content-Encoding
輸出頭信息,判斷服務端采用的是哪種壓縮算法,并采用對應的解壓方法解壓獲取原始數(shù)據(jù)。
總結:
通過本次探討HttpClient
關于響應壓縮的處理我們可以了解到,HttpClient
無論從設計上還是實現(xiàn)方式上都有非常高的靈活性和擴展性,這也是為什么到了.Net Core
上官方只推薦使用HttpClient
一種Http請求方式。由于使用比較簡單,實現(xiàn)方式比較清晰,這里就不過多拗述。主要是是想告訴大家HttpClient
默認可以直接處理響應壓縮,而不是和之前我們使用HttpWebRequest
的時候還需要手動編碼的方式去實現(xiàn)。
- ASP.NET Core 文件響應壓縮的常見使用誤區(qū)
- ASP.NET Core中的響應壓縮的實現(xiàn)
- asp.net core為IHttpClientFactory添加動態(tài)命名配置
- 如何在ASP.NET Core中使用HttpClientFactory
- 在ASP.NET Core中用HttpClient發(fā)送POST, PUT和DELETE請求
- .NET CORE HttpClient的使用方法
- .NET Core使用HttpClient進行表單提交時遇到的問題
- .Net Core下HTTP請求IHttpClientFactory示例詳解
- Asp.Net Core2.1前后使用HttpClient的兩種方式
- ASP.NET Core針對一個使用HttpClient對象的類編寫單元測試詳解
- .NET Core中HttpClient的正確打開方式
- .NET Core中使用HttpClient的正確姿勢
- .NET Core 2.1中HttpClientFactory的最佳實踐記錄
相關文章
ASP.NET?Core使用功能開關控制路由訪問操作(續(xù))
這篇文章主要介紹了ASP.NET?Core使用功能開關控制路由訪問操作的(續(xù)),上一篇文章我們已經介紹過一部份該相關內容,??在本文,我們可以判斷當前路由地址是否為調試地址,讓評估返回真,需要的小伙伴可以參考一下2022-02-02