Search code examples
httpasp.net-core.net-coredotnet-httpclienthttp2

Use HTTP 2 with HttpClient in .Net


I'm trying to request data over HTTP 2.0. I'm using the HttpClient from .Net Core 2.2. I'm on Windows 10 but will run on Linux in production. The problem is that the version on the response seems to always be "1.1". What I am doing wrong?

using (var client = new HttpClient())
{
    using (var request = new HttpRequestMessage(new HttpMethod("GET"),
    "https://duckduckgo.com/"
    ))
    {
        request.Version = new Version(2, 0);
        var response = await client.SendAsync(request);

        Console.WriteLine(response.Version);
    }
}

Solution

  • Update - .NET Core 3.0

    .NET Core 3.0 now supports HTTP/2. The following code will print 2.0 :

    var client = new HttpClient();
    
    var req = new HttpRequestMessage(HttpMethod.Get, "https://http2.akamai.com/demo"){
                 Version = new Version(2, 0) 
                 };
    
    var x = await client.SendAsync(req);
    var version = x.Version;
    
    Console.WriteLine(version);
    

    Original Answer

    You can't use HTTP/2 with HttpClient in .NET Core 2.1 or 2.2, even if you explicitly set the version in the request. You'll have to explicitly configure .NET Core to use the old HttpClientHandler instance that came with .NET Core 2.0, either by setting an App Context switch with :

    AppContext.SetSwitch("System.Net.Http.UseSocketsHttpHandler", false);
    

    Or by setting the DOTNET_SYSTEM_NET_HTTP_USESOCKETSHTTPHANDLER environment variable to 0 or false.

    The discussion in this Github issue shows that HTTP/2 support is planned for .NET Core 3.0. The 3.0 Preview 1 released at Microsoft Connect 2018 doesn't support HTTP/2 yet.

    The HttpClientHandler used up to .NET Core 2.0 supported HTTP/2. The following code will return 2.0 in a project that targets Core 2.0 :

    var client = new HttpClient();
    
    var req = new HttpRequestMessage(HttpMethod.Get, "https://http2.akamai.com/demo")
    {
        Version = new Version(2, 0)
    };
    
    var x = await client.SendAsync(req);
    var version = x.Version;
    
    Console.WriteLine(version);
    

    Just make sure you thoroughly clean your project if you change the target runtime - delete bin, obj and all target files, or you may end up running with the wrong runtime as I did.

    In 2.1 a new, far faster SocketsHttpClientHandler was added as a default. The new handler doesn't support HTTP/2 yet. The same code will return 1.1 as the protocol version.

    If the app context switch is set before creating the HttpClient though, HTTP/2 is used. This code will return 2.0. Interestingly, there's no need to specify the HTTP version. When HTTP/2 is available, the actual protocol version is negotiated. Both the Akamai URL and https://www.google.com will use HTTP/2 even though the version wasn't specified:

    AppContext.SetSwitch("System.Net.Http.UseSocketsHttpHandler", false);
    var client = new HttpClient();
    
    var req = new HttpRequestMessage(HttpMethod.Get, "https://http2.akamai.com/demo");
    var x = await client.SendAsync(req);
    
    var version = x.Version;
    

    The switch and environment variable are explained in the official announcement for .NET Core 2.1 SDK Preview 2:

    Sockets Performance and SocketsHttpHandler

    We made major improvements to sockets in .NET Core 2.1. Sockets are the basis of both outgoing and incoming networking communication. The higher-level networking APIs in .NET Core 2.1, including HttpClient and Kestrel, are now based on .NET sockets. In earlier versions, these higher-level APIs were based on native networking implementations.

    ...

    You can use one of the following mechanisms to configure a process to use the older HttpClientHandler:

    From code, use the AppContext class:

    AppContext.SetSwitch("System.Net.Http.UseSocketsHttpHandler", false);
    

    The AppContext switch can also be set by config file.

    The same can be achieved via the environment variable DOTNET_SYSTEM_NET_HTTP_USESOCKETSHTTPHANDLER. To opt out, set the value to either false or 0.