前幾天把我的部落格網站啟用 HTTP/2 通訊協定版本,結果意外發現我有個用 WebClient 抓取網頁的程式壞掉了。其實我一開始並沒有發現是 HTTP/2 造成的問題,鬼打牆了一段時間才意識到可能是 HTTP 版本差異造成的問題。這篇文章我就來分享幾種不同的 HttpClient 程式寫法,讓你用 HTTP/2 通訊協定版本抓回遠端 Web 伺服器上的網頁。
要怎樣知道你的 C# 程式發出的 HTTP 要求是用什麼版本?
有個 HTTP2.Pro 網站提供一個工具,你在網站上輸入一個網址,他就會告訴你該網站是支援 HTTP/1.1 或 HTTP/2 版本。
同時,該網站也提供了一個 Client HTTP/2 Support API 端點,幫助你測試「用戶端」是否是使用 HTTP/2 版本來建立連線,我今天就會用這個端點來進行測試!
https://http2.pro/api/v1
我先用 cURL 工具來檢測一下:
curl --http2 https://http2.pro/api/v1
結果發現 Windows 10 內建的 curl.exe
即便有支援 --http2
參數,但竟然是不支援 HTTP/2 版本的。我若改用 WSL (Ubuntu 20.04.4 LTS) 底下的 /usr/bin/curl
卻可以得到預期的結果!👍
在 .NET Framework 平台如何發出 HTTP/2 連線
-
WebClient
基本上,在 .NET Framework 的 WebClient 是完全不支援發出 HTTP/2 連線的。
WebClient client = new WebClient();
var html = client.DownloadString("https://http2.pro/api/v1");
html.Dump("WebClient with HTTP/1.1");
這段程式的結果如下:
{"http2":0,"protocol":"HTTP\/1.1","push":0,"user_agent":""}
-
HttpClient
其實 HttpClient 預設也是不支援的:
using (var client = new HttpClient())
{
// 舊版的 .NET Framework 必須加上這一行
ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
// 你可以在發出 HTTP 要求時,明確指定採用 TLS 1.2 版本進行加密連線
var handler = new HttpClientHandler();
handler.SslProtocols = SslProtocols.Tls12;
var httpClient = new HttpClient(handler);
httpClient.DefaultRequestHeaders.Add("User-Agent", "HttpClient/1.0");
var response = await httpClient.GetAsync("https://http2.pro/api/v1");
var content = await response.Content.ReadAsStringAsync();
content.Dump("HttpClient with HTTP/1.1 by default");
}
這段程式的結果如下:
{"http2":0,"protocol":"HTTP\/1.1","push":0,"user_agent":"HttpClient\/1.0"}
從 .NET Framework 4.6 開始,只有 ASP.NET + IIS + 啟用 HTTP/2 的條件下,以及在 Windows 10 Universal Windows Platform (UWP) 應用程式下,HttpClient 才預設支援 HTTP/2 喔,其他條件要用接下來的作法!
-
HttpClient + System.Net.Http.WinHttpHandler
要透過 HttpClient 發出 HTTP/2 版本的連線,必須搭配 System.Net.Http.WinHttpHandler 的幫助之下達成這個目的!👍
你要先繼承 System.Net.Http
命名空間下的 WinHttpHandler
類別,並覆寫 SendAsync
方法:
public class Http2CustomHandler : WinHttpHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, System.Threading.CancellationToken cancellationToken)
{
request.Version = new Version("2.0");
return base.SendAsync(request, cancellationToken);
}
}
然後你才能用以下程式碼發出 HTTP/2 的連線:
var url = "https://http2.pro/api/v1";
var handler = new Http2CustomHandler()
{
SslProtocols = SslProtocols.Tls12,
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
};
var httpClient = new HttpClient(handler);
httpClient.DefaultRequestHeaders.Add("User-Agent", "HttpClient/1.0");
var resTask = httpClient.GetAsync(new Uri(url));
var response = resTask.Result;
var strTask = response.Content.ReadAsStringAsync();
var strResponse = strTask.Result;
strResponse.Dump("HttpClient with HTTP/2 through WinHttpHandler");
這段程式的結果如下:
{"http2":1,"protocol":"HTTP\/2.0","push":0,"user_agent":"HttpClient\/1.0"}
這個技巧也可以用在 .NET Core 1.0 ~ .NET Core 2.2 版本的 HttpClient 之中,但只能用在 Windows 平台。
在 .NET Core 3.0 之後如何發出 HTTP/2 連線
從 .NET Core 3.0 開始,包含 .NET 5 / 6 / 7 基本上都已經內建支援 HTTP/2 協定,寫法上比 .NET Framework 簡單一點,不用額外定義一個 WinHttpHandler
類別,可以直接指定版本。但其實程式碼也沒有比較短,完整範例如下:
using (var client = new HttpClient())
{
// ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls;
var handler = new HttpClientHandler()
{
SslProtocols = SslProtocols.Tls12,
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate
};
var httpClient = new HttpClient(handler);
var req = new HttpRequestMessage(HttpMethod.Get, "https://http2.pro/api/v1")
{
Version = new Version(2, 0)
};
req.Headers.Add("User-Agent", "HttpClient/1.0");
var response = await client.SendAsync(req);
var content = await response.Content.ReadAsStringAsync();
content.Dump("HttpClient with HTTP/2 after .NET Core 3");
}
這段程式的結果如下:
{"http2":1,"protocol":"HTTP\/2.0","push":0,"user_agent":"HttpClient\/1.0"}
相關連結