The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

設定 .NET 的 HttpClient 使用 HTTP/2 通訊協定發出 HTTP 要求

前幾天把我的部落格網站啟用 HTTP/2 通訊協定版本,結果意外發現我有個用 WebClient 抓取網頁的程式壞掉了。其實我一開始並沒有發現是 HTTP/2 造成的問題,鬼打牆了一段時間才意識到可能是 HTTP 版本差異造成的問題。這篇文章我就來分享幾種不同的 HttpClient 程式寫法,讓你用 HTTP/2 通訊協定版本抓回遠端 Web 伺服器上的網頁。

HTTP/2 with HttpClient

要怎樣知道你的 C# 程式發出的 HTTP 要求是用什麼版本?

有個 HTTP2.Pro 網站提供一個工具,你在網站上輸入一個網址,他就會告訴你該網站是支援 HTTP/1.1 或 HTTP/2 版本。

HTTP2.Pro

同時,該網站也提供了一個 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 卻可以得到預期的結果!👍

image

在 .NET Framework 平台如何發出 HTTP/2 連線

  1. 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":""}
    
  2. 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 喔,其他條件要用接下來的作法!

  3. 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"}

相關連結

留言評論