Yes, HttpClient
in C# fully supports HTTP/2 starting with .NET Core 2.1. HTTP/2 provides significant performance benefits including multiplexing, header compression, and binary framing. However, both client and server must support HTTP/2 for it to work effectively.
Requirements
- .NET Core 2.1 or later (including .NET 5, 6, 7, 8+)
- HTTPS connection (HTTP/2 requires TLS in most implementations)
- Server support for HTTP/2 protocol
Basic HTTP/2 Usage
Method 1: Explicit Version Setting
using System;
using System.Net.Http;
using System.Threading.Tasks;
class Program
{
static async Task Main()
{
using var client = new HttpClient();
var request = new HttpRequestMessage()
{
RequestUri = new Uri("https://httpbin.org/get"),
Method = HttpMethod.Get,
Version = HttpVersion.Version20 // Force HTTP/2
};
try
{
var response = await client.SendAsync(request);
Console.WriteLine($"Protocol: HTTP/{response.Version}");
Console.WriteLine($"Status: {response.StatusCode}");
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Response: {content}");
}
catch (HttpRequestException ex)
{
Console.WriteLine($"Request failed: {ex.Message}");
}
}
}
Method 2: Configure Default Version
using var client = new HttpClient();
client.DefaultRequestVersion = HttpVersion.Version20;
client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionExact;
// All requests will now use HTTP/2
var response = await client.GetAsync("https://httpbin.org/get");
Console.WriteLine($"Protocol: HTTP/{response.Version}");
Version Policies
Control how HttpClient handles HTTP version negotiation:
using var client = new HttpClient();
// Always use exact version, no fallback
client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionExact;
// Use requested version or higher
client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrHigher;
// Use requested version or lower (allows fallback)
client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrLower;
Multiple Concurrent Requests
HTTP/2's multiplexing allows multiple requests over a single connection:
using var client = new HttpClient();
client.DefaultRequestVersion = HttpVersion.Version20;
var tasks = new List<Task<HttpResponseMessage>>();
// Send 10 concurrent requests - HTTP/2 will multiplex them
for (int i = 0; i < 10; i++)
{
var task = client.GetAsync($"https://httpbin.org/delay/{i}");
tasks.Add(task);
}
var responses = await Task.WhenAll(tasks);
foreach (var response in responses)
{
Console.WriteLine($"Status: {response.StatusCode}, Protocol: HTTP/{response.Version}");
response.Dispose();
}
Checking HTTP/2 Support
Verify if a server supports HTTP/2:
public static async Task<bool> SupportsHttp2(string url)
{
using var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Head, url)
{
Version = HttpVersion.Version20,
VersionPolicy = HttpVersionPolicy.RequestVersionExact
};
try
{
var response = await client.SendAsync(request);
return response.Version.Major == 2;
}
catch
{
return false;
}
}
// Usage
bool supportsHttp2 = await SupportsHttp2("https://google.com");
Console.WriteLine($"Server supports HTTP/2: {supportsHttp2}");
Server Configuration (ASP.NET Core)
Kestrel Configuration
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.ConfigureKestrel(options =>
{
options.ListenAnyIP(5001, listenOptions =>
{
listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
listenOptions.UseHttps();
});
// HTTP/2 only (no HTTP/1.1 fallback)
options.ListenAnyIP(5002, listenOptions =>
{
listenOptions.Protocols = HttpProtocols.Http2;
listenOptions.UseHttps();
});
});
});
appsettings.json Configuration
{
"Kestrel": {
"EndpointDefaults": {
"Protocols": "Http1AndHttp2"
},
"Endpoints": {
"Https": {
"Url": "https://localhost:5001",
"Protocols": "Http1AndHttp2"
},
"Http2Only": {
"Url": "https://localhost:5002",
"Protocols": "Http2"
}
}
}
}
Performance Considerations
Connection Pooling
HTTP/2 uses fewer connections due to multiplexing:
// Configure connection limits for HTTP/2
var handler = new SocketsHttpHandler()
{
PooledConnectionLifetime = TimeSpan.FromMinutes(2),
MaxConnectionsPerServer = 2 // Fewer connections needed with HTTP/2
};
using var client = new HttpClient(handler);
client.DefaultRequestVersion = HttpVersion.Version20;
Stream Limits
Be aware of HTTP/2 stream limits:
// Monitor concurrent streams
var semaphore = new SemaphoreSlim(100, 100); // Limit concurrent requests
var tasks = urls.Select(async url =>
{
await semaphore.WaitAsync();
try
{
return await client.GetAsync(url);
}
finally
{
semaphore.Release();
}
});
Common Issues and Solutions
1. HTTPS Requirement
// HTTP/2 typically requires HTTPS
var handler = new HttpClientHandler()
{
ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true
};
using var client = new HttpClient(handler);
2. Version Fallback Handling
public static async Task<HttpResponseMessage> GetWithFallback(string url)
{
using var client = new HttpClient();
// Try HTTP/2 first
var request = new HttpRequestMessage(HttpMethod.Get, url)
{
Version = HttpVersion.Version20,
VersionPolicy = HttpVersionPolicy.RequestVersionOrLower
};
var response = await client.SendAsync(request);
if (response.Version.Major < 2)
{
Console.WriteLine("Fell back to HTTP/1.1");
}
return response;
}
Limitations
- Server Push: Not supported by HttpClient
- Trailers: Limited support for HTTP/2 trailers
- Frame Types: Only standard HTTP/2 frames are supported
- ALPN: Automatic, but limited configuration options
Best Practices
- Reuse HttpClient instances to benefit from connection pooling
- Use async/await to maximize HTTP/2 multiplexing benefits
- Set appropriate timeouts for long-lived connections
- Monitor connection health in production environments
- Test fallback scenarios when HTTP/2 is unavailable
HTTP/2 support in HttpClient provides significant performance improvements for modern web applications, especially when making multiple concurrent requests to the same server.