Can you achieve Http/2 multiplexing with .Net Core HttpClient?
Asked Answered
C

1

18

Summary

The Http/2 protocol provides the ability to multiplex multiple requests over a single connection. This allows for more efficient use of connections - see https://http2.github.io/faq/#why-is-http2-multiplexed

I would expect to be able to use the .Net Core HttpClient to achieve this. My test (based on the below) however indicate that there is a 1:1 ratio of request to TCP connections are made.

Is multiplexing supported under .Net Core HttpClient? And if so, how is it achieved?

Work so far

I have a sample app (repo can be found here, with the following code;

using (var httpClient = new HttpClient())
{
   var request1 = new HttpRequestMessage(HttpMethod.Get, "https://www.google.com");
   request1.Version = new Version(2, 0);

   var request2 = new HttpRequestMessage(HttpMethod.Get, "https://www.google.com");
   request2.Version = new Version(2, 0);

   var task1 = httpClient.SendAsync(request1);
   var task2 = httpClient.SendAsync(request2);

   Task.WaitAll(task1, task2);

   var response1 = task1.Result;
   var response2 = task2.Result;

   Console.WriteLine($"Response 1 - Http Version: {response1.Version}, Http Status Code: {response1.StatusCode}");
   Console.WriteLine($"Response 2 - Http Version: {response2.Version}, Http Status Code: {response2.StatusCode}");
}

This code produces the following results (so I know Http/2 is being used);

Response 1 - Http Version: 2.0, Http Status Code: OK
Response 2 - Http Version: 2.0, Http Status Code: OK

I can see from Wireshark that 2 connections have been created - each having to go through TLS setup;

Wireshark Capture

If HttpClient was multiplexing the requests, I would expect see a single connection (1 port, 1 handshake, etc).

Cranny answered 15/12, 2017 at 10:54 Comment(1)
I have to add new WinHttpHandler() as a handler to get 2.0, do you have any idea why?Spread
C
8

The problem with my code is that request2 is being received by the HttpClient BEFORE it has had a chance to create a TCP connection for request1. As such, as far as HttpClient is concerned, there is no existing connection to multiplex on.

If however, I create an initial request (request0 in the below) and allow HttpClient to open the connection, then the subsequent requests (1 & 2) use that existing connection.

The code:

using (var httpClient = new HttpClient())
{
   // Setup first connection
   var request0 = new HttpRequestMessage(HttpMethod.Get, "https://www.google.com");
   request0.Version = new Version(2, 0);

   var task0 = httpClient.SendAsync(request0);
   var response0 = task0.Result;

   Console.WriteLine($"Response 0 - Http Version: {response0.Version}, Http Status Code: {response0.StatusCode}");

   // Now send the multiplexed requests
   var request1 = new HttpRequestMessage(HttpMethod.Get, "https://www.google.com");
   request1.Version = new Version(2, 0);

   var request2 = new HttpRequestMessage(HttpMethod.Get, "https://www.google.com");
   request2.Version = new Version(2, 0);

   var task1 = httpClient.SendAsync(request1);
   var task2 = httpClient.SendAsync(request2);

   Task.WaitAll(task1, task2);

   var response1 = task1.Result;
   var response2 = task2.Result;

   Console.WriteLine($"Response 1 - Http Version: {response1.Version}, Http Status Code: {response1.StatusCode}");
   Console.WriteLine($"Response 2 - Http Version: {response2.Version}, Http Status Code: {response2.StatusCode}");
}

And the Wireshark proof (only 1 port, 1 handshake):

Wireshark capture

Cranny answered 15/12, 2017 at 11:43 Comment(5)
Restricting the MaxConnectionsPerServer will also force multiplexing. Default value is 2147483647. var handler = new WinHttpHander(); handler.MaxConnectionsPerServer = 1; var httpClient = new HttpClient(handler);Cranny
Rocking dude, thanks for sharing this research. Just to make clear for others: the fixed code in this answer is identical after the line: "// Now send the multiplexed requests"Loralyn
The fact that you only see one connection does not mean it's multiplexing. HTTP connection have a keep-alive behavior where multiple requests can be made on the same connection (regardless of pipelining or multiplexing). Could that be what you're seeing? Or did you verify that in fact the requests or responses were actually being interleaved in the stream?Quiche
How will it handle the scenario when the server does not support HTTP 2 as you are explicitly specifying the use of version 2 on your HTTPClientRequestRecommit
@AndrewArnott, totally agree... I'm trying to prove multiplexing whole day and to do so we have to decrypt TLS in WireShark... sounds like it is possible to do with custom local server that also supports multiplexingUntitled

© 2022 - 2024 — McMap. All rights reserved.