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

前幾天把我的部落格網站啟用 HTTP/2 通訊協定版本，結果意外發現我有個用 [WebClient](https://learn.microsoft.com/zh-tw/dotnet/api/system.net.webclient?view=netframework-4.8&WT.mc_id=DT-MVP-4015686) 抓取網頁的程式壞掉了。其實我一開始並沒有發現是 HTTP/2 造成的問題，鬼打牆了一段時間才意識到可能是 HTTP 版本差異造成的問題。這篇文章我就來分享幾種不同的 [HttpClient](https://learn.microsoft.com/zh-tw/dotnet/api/system.net.http.httpclient) 程式寫法，讓你用 HTTP/2 通訊協定版本抓回遠端 Web 伺服器上的網頁。

![HTTP/2 with HttpClient](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/be9ec0e8dada1da81375-212971649-9de7b74f-9fb9-4152-beea-8566681c743d.jpg)

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

有個 [HTTP2.Pro](https://http2.pro/) 網站提供一個工具，你在網站上輸入一個網址，他就會告訴你該網站是支援 HTTP/1.1 或 HTTP/2 版本。

![HTTP2.Pro](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/c6a46d4503017f2980d5-212925072-463783f5-13a8-4f4b-bba7-0a948a1b77cb.png)

同時，該網站也提供了一個 [Client HTTP/2 Support API](https://http2.pro/doc/api) 端點，幫助你測試「用戶端」是否是使用 HTTP/2 版本來建立連線，我今天就會用這個端點來進行測試！

```
https://http2.pro/api/v1
```

我先用 cURL 工具來檢測一下：

```bash
curl --http2 https://http2.pro/api/v1
```

結果發現 Windows 10 內建的 `curl.exe` 即便有支援 `--http2` 參數，但竟然是不支援 HTTP/2 版本的。我若改用 WSL (Ubuntu 20.04.4 LTS) 底下的 `/usr/bin/curl` 卻可以得到預期的結果！👍

![image](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/75835688f592a93dbb76-212925676-29e15c2b-e6cd-49e5-bbf0-d294de6c11c8.png)

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

1.  WebClient
    
    基本上，在 .NET Framework 的 [WebClient](https://learn.microsoft.com/zh-tw/dotnet/api/system.net.webclient?view=netframework-4.8&WT.mc_id=DT-MVP-4015686) 是完全不支援發出 HTTP/2 連線的。
    
    ```csharp
    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 預設也是不支援的：
    
    ```csharp
    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](https://www.nuget.org/packages/System.Net.Http.WinHttpHandler)
    
    要透過 HttpClient 發出 HTTP/2 版本的連線，必須搭配 [System.Net.Http.WinHttpHandler](https://www.nuget.org/packages/System.Net.Http.WinHttpHandler) 的幫助之下達成這個目的！👍
    
    你要先繼承 `System.Net.Http` 命名空間下的 `WinHttpHandler` 類別，並覆寫 `SendAsync` 方法：
    
    ```csharp
    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 的連線：
    
    ```csharp
    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` 類別，可以直接指定版本。但其實程式碼也沒有比較短，完整範例如下：

```csharp
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"}
```

### 相關連結

-   [HTTP2.Pro - Check server & client HTTP/2, ALPN, and NPN support online.](https://http2.pro/)
-   [NuGet Gallery | System.Net.Http.WinHttpHandler 7.0.0](https://www.nuget.org/packages/System.Net.Http.WinHttpHandler)
-   [WebClient 類別 (System.Net) | Microsoft Learn](https://learn.microsoft.com/zh-tw/dotnet/api/system.net.webclient?view=netframework-4.8&WT.mc_id=DT-MVP-4015686)
-   [WinHttpHandler is only supported on .NET Framework and .NET Core runtimes on Windows in v2 function #3052](https://github.com/Azure/azure-functions-host/issues/3052)
-   [Support #HTTP2 in your C# Client code | Network Programming in .NET](https://blog.dotnetframework.org/2019/04/05/support-http2-in-your-c-client-code/)
-   [c# - How to make the .net HttpClient use http 2.0? - Stack Overflow](https://stackoverflow.com/q/32685151/910074)
-   [What's new in .NET Framework - .NET Framework | Microsoft Learn](https://learn.microsoft.com/en-us/dotnet/framework/whats-new/?redirectedfrom=MSDN&WT.mc_id=DT-MVP-4015686#:~:text=to%20true.-,HTTP/2%20Support,-\(Windows%2010\))
