Use HTTP 2 with HttpClient in .Net
Asked Answered
G

3

21

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);
    }
}
Gebelein answered 13/12, 2018 at 14:28 Comment(0)
S
25

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.

Stent answered 14/12, 2018 at 8:59 Comment(4)
Thank you for the answer. I got it to work based on what you said! On Windows 10. Do you also know if there is a way to get it to work on Ubuntu? I got this error : System.TypeInitializationException: The type initializer for 'System.Net.Http.CurlHandler' threw an exception. --> System.TypeInitializationException: The type initializer for 'Http' threw an exception. --> System.TypeInitializationException: The type initializer for 'HttpInitializer' threw an exception. --> System.DllNotFoundException: Unable to load shared library 'System.Net.Http.Native' or one of its dependenciesGebelein
@RobertM as the announcement and the bugs explain, if you want to use the old HttpClientHandler you need to install the old prerequisites like libcurl and openssl. The old HttpClientHandler uses different libraries on each OS. A quick&dirty solution could be to install 2.0 first.Stent
Thanks a lot! Indeed going with 2.0 would be minimum amount of headache approach until 3.0 hopefully.Gebelein
If you do not need HTTP/1 compatibility/fallback and don't care about some missing features, you might be able to try my native HTTP/2 implementation in C#, which should work on .NET core 2.0: github.com/Matthias247/http2dotnetHoming
C
5

The current suggested pattern is to register HttpClient dependencies in the ConfigureServices method: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-requests.

Here is an example of registering a default HttpClient using a typed client and configuring it to use HTTP/2:

public void ConfigureServices(IServiceCollection services)
{
...
    services.AddHttpClient<ExampleService>()
        .ConfigureHttpClient((client) =>
        {
            client.DefaultRequestVersion = new Version(2, 0);
        });
...
}
Chattanooga answered 15/7, 2020 at 8:34 Comment(2)
Works for all HttpClient methods except SendAsync learn.microsoft.com/en-us/dotnet/api/…Engulf
@GunnarMárÓttarsson thanks a lot, I didn't notice this detail!Ratiocination
M
1

You want to use the http/2 protocol but you did not set the protocol property. Please do so. Did you check if it is possible on the operating system you use? .NET Core does not support this for Mac OS yet for example. Also make sure you setup the needed properties as described here using the options variable when calling ConfigureKestrel. Logically, you can also try this alternative way to set the http version.

By doing a curl call, you can check if your http/2 call is possible in your situation.

In addition, there are issues with the original HttpClient in .NET Core (which you use). Please use the HttpClientFactory.

Marna answered 13/12, 2018 at 15:4 Comment(8)
There's no "original" HttpClient. HttpClientFactory creates HttpClient instances. What changed in 2.1 was that HttpClient started using the new SocketClientHandler by default.Stent
The documentation from Microsoft refers to "original and well-known HttpClient class". I mean exactly the same and I always try to be consistent with the Microsoft documentation so people know what I am talking about. You do have a point because there is not really a new one, just a new way of creating one with a the new handler you mentioned. learn.microsoft.com/en-us/dotnet/standard/…Marna
That documentation refers to the .NET 4.x HttpClient, not to .NET Core. HttpClientFactory creates .NET Core HttpClient instances. You can't use the old HttpClient class in .NET CoreStent
The "original and well-known HttpClient class" is the exact quote.Marna
It is an additional remark, not something which will solve the problem. That is why I said "In addition". There are "Issues with the original HttpClient class" according to the Microsoft documentation which is something to be aware of. I never said there is a new type of HttpClient, I quoted the Microsoft docs. That's all. If you want to discuss this, please talk to Microsoft, not to me. If Microsoft explicitly mentions "Issues with the original HttpClient class", this should be something to aware of. These are their words, not mine.Marna
I appreciate the time invested in the answer, but it still outputs "1.1". I'm not sure how to set the HttpRequest.Protocol Property, are you sure is this related to the HttpClient? I've checked if http/2 is possible with curl and the browser and it is for "duckduckgo.com". You are right about the Kestrel options, but as you can see I'm doing a request to an existing server on the web (DuckDuckGo). I'm on Windows 10, up to date. I've attached the version on the HttpRequestMessage request message already.Gebelein
@RobertM that's because you can't use HTTP/2 in .NET Core 2.2, even when you specify the versionStent
@PanagiotisKanavos, why not? What's missing from .Net Core 2.2 to support HTTP/2?Glanville

© 2022 - 2024 — McMap. All rights reserved.