http-libs
↑↓ navigate select esc close
GitHub

Cache content-store contracts and offload

Metadata and HTTP freshness remain in HybridCache. Choose one separately packaged body store:

Package Backend Guide
DamianH.HttpHybridCacheHandler.ContentStore Provider-independent streaming contracts, no cloud SDK dependency This page
DamianH.HttpHybridCacheHandler.ContentStore.AzureBlob Official Azure.Storage.Blobs SDK Azure Blob
DamianH.HttpHybridCacheHandler.ContentStore.S3 Official AWSSDK.S3 SDK Amazon S3
DamianH.HttpHybridCacheHandler.ContentStore.GoogleCloudStorage Official Google.Cloud.Storage.V1 SDK Google Cloud Storage
DamianH.HttpHybridCacheHandler.ContentStore.FileSystem Native file streams, no cloud SDK Filesystem

Adapters depend on ContentStore, not on the handler or HybridCache. Configure credentials, endpoints, and retries through an injected SDK client. Registration never provisions cloud resources or modifies lifecycle policies. Stowage and FluentStorage are not dependencies.

Independent contracts

DamianH.HttpHybridCacheHandler.ContentStore provides provider-independent contracts for the handler and adapters. No cloud SDK or HybridCache dependency is required to implement a store.

The contracts and all five consuming HTTP cache packages ship net10.0, netstandard2.0, and net472 assets. Let NuGet restore the older-target async support packages and enable automatic binding redirects in Framework applications. See framework compatibility for tested runtimes, header representation, and the handler's upstream HybridCache support warning; the contracts themselves do not depend on HybridCache.

Install in the application or adapter project directory:

dotnet add package DamianH.HttpHybridCacheHandler.ContentStore

NuGet | Contract source

IHttpCacheContentStore provides complete streaming writes, independently owned read streams, and single-key removal. ILargeHttpCacheContentStore identifies the optional external body store. The handler retains responsibility for HTTP freshness, variant selection, and metadata invalidation. Storage retention is separate from HTTP freshness.

Recognized SDK write failures propagate as HttpCacheContentStoreException (an IOException) with the original provider exception attached. Cancellation and programming errors are not converted. This lets the handler log and abandon a failed fill without depending on cloud SDK exceptions or corrupting the origin response. Only missing reads return null; other read failures propagate.

Routing and streaming

LargeContentThreshold uses the original response-body length before internal compression. MaxCacheableContentSize still determines whether a body can be cached at all. Without an external store, the HybridCache-only path remains available. See option defaults.

With external storage enabled, cacheable origin bodies can stream to the caller while the handler stages a copy in bounded memory and temporary files. A completed body is uploaded before metadata is published. Early disposal, incomplete responses, or cancellation do not populate the cache. Admission limits must not truncate the origin response.

Use HttpCompletionOption.ResponseHeadersRead and consume/dispose the response stream to benefit from streaming. The default ResponseContentRead, ReadAsStringAsync, and ReadAsByteArrayAsync can still buffer the entire response in the caller. Cold streaming requests do not share a live response stream; simultaneous misses can make independent origin requests. Completing consumption can include cache upload latency.

Temporary staging

Temporary spool storage and persistent body retention are separate concerns. Configure handler staging independently of the body store:

services.AddHttpHybridCacheHandler(options =>
{
options.LargeContentThreshold = 1024 * 1024;
options.SpoolMemoryThreshold = 64 * 1024;
options.MaxSpoolDiskBytes = 1024L * 1024 * 1024;
options.MaxConcurrentDiskSpools = 32;
options.SpoolDirectory = Path.Combine(Path.GetTempPath(), "MyApp-cache-spool");
});

Defaults are 64 KiB staging memory per spool, a 1 GiB aggregate active disk budget, 32 concurrent disk spools, and the system temporary directory. Compression may need a second spool, counted against the same disk limits. Reservations are process-wide; use consistent limits across handlers. Exhaustion abandons caching while origin delivery continues. Limits exclude caller buffers, fixed transfer buffers, provider SDK buffers, and persistent body storage.

Use a trusted private staging parent. Provisioning it is an application/deployment responsibility, including when SpoolDirectory is null and uses Path.GetTempPath(). Verify access for the process account rather than assuming the default directory is private. On Windows, provision the parent with inheritable ACLs restricting access to trusted principals. The handler inherits those ACLs; it does not restrict or validate them. A shared/public parent is not made private by the handler. Unique names and leases manage lifetime, not access control.

Each spill owns a unique leased directory; cleanup releases completed spools and can reclaim abandoned leased directories without deleting a live owner's files. HTTP cache metadata remains the caller's HybridCache configuration responsibility.

On Linux and macOS, the netstandard2.0 and net472 builds create private staging directories through native POSIX mkdir (libc), with a UTF-8 path and owner-only mode 0700. The return code is checked: there is no weaker-permission fallback or narrowing of permissions after writing response data. This OS call does not inspect runtime internals. The net10.0 build uses managed Directory.CreateDirectory with UnixFileMode. Windows uses the managed API and the trusted parent's ACLs. Other operating systems are unsupported for older-target disk spooling. These are platform requirements, not claims of completed macOS or actual Framework 4.7.2 runtime testing.

Retention

Configure cloud lifecycle policies for cached objects and incomplete uploads, and age/size cleanup for filesystem storage. Retention is not HTTP freshness: deletion becomes a cache miss even when metadata is fresh. Revalidation does not necessarily refresh object creation time. Do not delete shared content-addressed bodies merely because one URL or variant was invalidated. Provider guides document their different incomplete-upload behavior and retention limitations.

Versioning, ownership, and migration

The handler, ContentStore, and each adapter have independent versions and release tags. ContentStore uses cache-contentstore-v. Release compatible ContentStore versions before consumers; SDK updates need not force a handler release.

The interfaces retain their namespace but now live in the ContentStore assembly. Implementations migrate from a materialized sequence write to a seekable, caller-owned input stream with an explicit stored length. The adapter must leave the input open and finish consuming it before returning. Returned read streams are owned by the response/caller. This is a breaking change from the initial pre-release Stowage implementation, not a binary-compatible replacement.

Build targets are pack-handler, pack-contentstore, pack-azureblob, pack-s3, pack-gcs, and pack-filesystem; pack-all creates the local bundle. The release workflow selects one package and publishes only its exact artifact. Consumers declare the compatible ContentStore dependency floor through HttpCacheContentStorePackageVersion (initially 0.1.0), rather than leaking another project's computed prerelease version.