HTTP cache handler pipeline
Recommended setup
On .NET 10, prefer SocketsHttpHandler with automatic decompression for
explicit DNS refresh and connection-pool lifetime control:
.ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler
{
AutomaticDecompression = DecompressionMethods.All,
PooledConnectionLifetime = TimeSpan.FromMinutes(5),
PooledConnectionIdleTimeout = TimeSpan.FromMinutes(2)
})On .NET Framework 4.7.2, use HttpClientHandler:
services.AddHttpClient("MyClient")
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler
{
AutomaticDecompression =
DecompressionMethods.GZip | DecompressionMethods.Deflate
})
.AddHttpMessageHandler(sp => sp.GetRequiredService<HttpHybridCacheHandler>());This example also uses APIs available to .NET Standard 2.0 consumers. Choose the
primary handler for the application's actual runtime; .NET Standard is not a
runtime. Framework's HttpClientHandler does not expose
PooledConnectionLifetime, PooledConnectionIdleTimeout, or ConnectTimeout.
Do not copy those options or DecompressionMethods.All into Framework-targeted
code. See framework compatibility
for dependency support and header-representation differences.
AutomaticDecompression explained
There are two different compressions:
- Transport compression (server to client):
- Controlled by
AutomaticDecompressionon the primary HTTP handler. - Reduces network bandwidth.
- The cache handler receives decompressed content.
- Controlled by
- Cache storage compression:
- Controlled by
CompressionThresholdin the caching options. - Reduces cache storage size.
- Content is compressed before storage.
- Controlled by
Example flow (illustrative sizes, not benchmark results):
Server sends: gzipped 512 bytes
↓
SocketsHttpHandler: auto-decompresses → 2048 bytes
↓
HttpHybridCacheHandler: receives decompressed content
↓
Storage compression: compresses → 600 bytes
↓
Cache: stores 600 bytes (no Base64 overhead!)
The cache handler can inspect and validate response content; Cache-Control, ETag, and Last-Modified headers are readable for caching decisions. Storage compression is optional and configurable.
Handler ordering
The basic pipeline is:
HttpClient → [Outer Handlers] → HttpHybridCacheHandler → SocketsHttpHandler → Network
With Polly resilience
For a cache hit to bypass resilience, register caching before the resilience handler, so caching is outer and resilience is inner:
.AddHttpMessageHandler(sp => sp.GetRequiredService<HttpHybridCacheHandler>())
.AddStandardResilienceHandler(options =>
{
options.Retry.MaxRetryAttempts = 3;
options.CircuitBreaker.SamplingDuration = TimeSpan.FromSeconds(30);
});The request order is cache, then Polly, then SocketsHttpHandler. Cache hits use
the fast path without invoking Polly; cache misses with network failures can be
retried by Polly. This is the recommended production ordering for that behavior.
With authentication
.AddHttpMessageHandler(() => new AuthenticationHandler())
.AddHttpMessageHandler(sp => sp.GetRequiredService<HttpHybridCacheHandler>());To include authentication headers in request-key partitioning, configure
VaryHeaders in the AddHttpHybridCacheHandler options.
Authentication is applied before caching. Configured VaryHeaders partition the
request key, and response Vary is always enforced on cache hits.
Common mistakes
Not enabling automatic decompression:
new SocketsHttpHandler() // AutomaticDecompression defaults to NoneThe cache handler receives compressed content instead of the intended ready-to-use representation. Explicitly enable decompression:
new SocketsHttpHandler
{
AutomaticDecompression = DecompressionMethods.All
}Choosing a handler unavailable on the application target:
HttpClientHandler with GZip/Deflate decompression is the Framework-compatible
choice. SocketsHttpHandler is recommended for .NET 10 applications; the
connection-pooling and DecompressionMethods.All examples above are modern-only.
Putting caching inside Polly when hits should bypass Polly:
.AddStandardResilienceHandler() // Outer
.AddHttpMessageHandler(sp => sp.GetRequiredService<HttpHybridCacheHandler>()) // InnerInstead:
.AddHttpMessageHandler(sp => sp.GetRequiredService<HttpHybridCacheHandler>()) // Outer
.AddStandardResilienceHandler() // InnerGolden rule: HttpHybridCacheHandler should receive decompressed,
ready-to-use content.