Hey performance-minded developers! š
Are you still using HTTP/1.1 without realizing it? Let's unlock the modern protocols that can dramatically improve your app's network performance!
Quick Protocol Comparison
| Feature | HTTP/1.1 | HTTP/2 | HTTP/3 |
|---|---|---|---|
| Multiplexing | ā | ā | ā |
| Header Compression | ā | ā (HPACK) | ā (QPACK) |
| Connection | TCP | TCP | QUIC/UDP |
| Head-of-line Blocking | ā | Partial | ā |
| 0-RTT | ā | ā | ā |
š Fun Fact: HTTP/3 uses QUIC, a protocol developed by Google that runs over UDP. It can establish connections up to 3x faster than TCP+TLS because it combines the transport and security handshakes into one round-trip!
Enabling HTTP/2
HTTP/2 is supported since .NET Core 3.0. Enable it:
var handler = new SocketsHttpHandler
{
// Enable HTTP/2 explicitly
EnableMultipleHttp2Connections = true
};
var client = new HttpClient(handler);
With HttpClientFactory
services.AddHttpClient<IMyApiClient, MyApiClient>()
.ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler
{
EnableMultipleHttp2Connections = true
});
Force HTTP/2
By default, HttpClient negotiates the protocol via ALPN. To force HTTP/2:
var request = new HttpRequestMessage(HttpMethod.Get, "https://api.example.com/data")
{
Version = HttpVersion.Version20,
VersionPolicy = HttpVersionPolicy.RequestVersionExact
};
var response = await client.SendAsync(request);
Console.WriteLine($"Protocol: {response.Version}"); // 2.0
Client-Level Default
var client = new HttpClient()
{
DefaultRequestVersion = HttpVersion.Version20,
DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrLower
};
Enabling HTTP/3
HTTP/3 support came in .NET 6 (preview) and .NET 7 (stable).
// .NET 7+
var client = new HttpClient()
{
DefaultRequestVersion = HttpVersion.Version30,
DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrLower
};
Per-Request HTTP/3
var request = new HttpRequestMessage(HttpMethod.Get, "https://cloudflare.com")
{
Version = HttpVersion.Version30,
VersionPolicy = HttpVersionPolicy.RequestVersionOrHigher
};
var response = await client.SendAsync(request);
Console.WriteLine($"Protocol: HTTP/{response.Version}"); // Might be 3.0!
Check Server Support
public static async Task<string> GetBestProtocolAsync(HttpClient client, string url)
{
// Try HTTP/3 first
try
{
var request = new HttpRequestMessage(HttpMethod.Head, url)
{
Version = HttpVersion.Version30,
VersionPolicy = HttpVersionPolicy.RequestVersionExact
};
var response = await client.SendAsync(request);
return $"HTTP/{response.Version}";
}
catch (HttpRequestException)
{
// Fall back to HTTP/2
}
try
{
var request = new HttpRequestMessage(HttpMethod.Head, url)
{
Version = HttpVersion.Version20,
VersionPolicy = HttpVersionPolicy.RequestVersionExact
};
var response = await client.SendAsync(request);
return $"HTTP/{response.Version}";
}
catch
{
return "HTTP/1.1";
}
}
Why HTTP/2 is Faster
Multiplexing
HTTP/1.1 can only process one request per connection at a time. To get parallelism, browsers open 6+ connections per domain.
HTTP/2 multiplexes unlimited requests over a single connection:
// These all share ONE TCP connection with HTTP/2
var tasks = new[]
{
client.GetAsync("/api/users"),
client.GetAsync("/api/products"),
client.GetAsync("/api/orders"),
client.GetAsync("/api/settings"),
client.GetAsync("/api/notifications")
};
await Task.WhenAll(tasks);
// Much faster than sequential HTTP/1.1!
Header Compression
HTTP headers are verbose and repetitive. HTTP/2 uses HPACK compression:
HTTP/1.1 headers per request: ~800 bytes
HTTP/2 after compression: ~20-50 bytes (after first request)
That's 95% smaller headers!
š” Pro Tip: Multiple HTTP/2 Connections
Sometimes you want multiple HTTP/2 connections (different auth contexts, load balancing):
var handler = new SocketsHttpHandler
{
EnableMultipleHttp2Connections = true,
MaxConnectionsPerServer = 10 // Allow up to 10 HTTP/2 connections
};
Performance Tuning
Connection Pooling
var handler = new SocketsHttpHandler
{
// HTTP/2 settings
EnableMultipleHttp2Connections = true,
InitialHttp2StreamWindowSize = 65536 * 16, // 1MB window
// Connection lifetime
PooledConnectionLifetime = TimeSpan.FromMinutes(15),
PooledConnectionIdleTimeout = TimeSpan.FromMinutes(2),
// Keep connections warm
KeepAlivePingPolicy = HttpKeepAlivePingPolicy.WithActiveRequests,
KeepAlivePingDelay = TimeSpan.FromSeconds(60),
KeepAlivePingTimeout = TimeSpan.FromSeconds(30)
};
GRPC (Built on HTTP/2)
If you're using gRPC, you're already on HTTP/2:
var channel = GrpcChannel.ForAddress("https://api.example.com", new GrpcChannelOptions
{
HttpHandler = new SocketsHttpHandler
{
EnableMultipleHttp2Connections = true,
KeepAlivePingDelay = TimeSpan.FromSeconds(60),
KeepAlivePingTimeout = TimeSpan.FromSeconds(30)
}
});
Debugging Protocol Version
public class ProtocolLoggingHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
Console.WriteLine($"ā Request version: HTTP/{request.Version}");
var response = await base.SendAsync(request, cancellationToken);
Console.WriteLine($"ā Response version: HTTP/{response.Version}");
return response;
}
}
Real-World Impact
I ran a benchmark fetching 100 resources from an API:
| Protocol | Time | Connections |
|---|---|---|
| HTTP/1.1 | 4.2s | 6 |
| HTTP/2 | 1.1s | 1 |
| HTTP/3 | 0.9s | 1 |
HTTP/2 was 4x faster with 6x fewer connections!
When to Use What
| Protocol | Best For |
|---|---|
| HTTP/1.1 | Legacy systems, simple requests |
| HTTP/2 | Most modern APIs, microservices, gRPC |
| HTTP/3 | Mobile apps, high-latency networks, CDNs |
Wrapping Up
Upgrading to HTTP/2 is usually a one-liner change that can significantly improve performance ā especially for apps making many parallel requests to the same host.
HTTP/3 is the future, with even better performance on unreliable networks. Start testing it now!
Check your protocol version with response.Version and make sure you're not leaving performance on the table.
Happy speeding! š
Top comments (0)