davianspace_http_resilience 1.0.4
davianspace_http_resilience: ^1.0.4 copied to clipboard
Production-grade HTTP resilience for Dart & Flutter. Retry, circuit breaker, timeout, bulkhead, hedging, fallback, structured observability, and header-redacted logging.
Changelog #
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
1.0.4 — 2026-03-03 #
Fixed #
- FIX-01: Cancel losing hedge attempts —
HedgingHandlernow creates per-attemptCancellationTokeninstances and cancels all losing attempts when a winner is found, freeing network resources immediately. - FIX-02: Drain abandoned streaming responses in hedging — Non-winning
and abandoned streaming
HttpResponsebodies are now drained to release underlying TCP connections. - FIX-03: Drain discarded streaming responses during retry —
RetryHandlernow drains previous streaming responses before overwritinglastResponse, and drains the final response before throwingRetryExhaustedException. - FIX-04: Parse RFC 9110 HTTP-date Retry-After header —
RetryHandlernow parses both numeric seconds and IMF-fixdate format (Retry-Afterheader) per RFC 9110 §10.2.3. - FIX-05: Eliminate DateTime drift in
CircuitOpenException.retryAfter—retryAfteris now computed from the monotonicStopwatchremaining time rather than wall-clock_openedAt, avoiding NTP/DST drift. - FIX-06: Wrap streaming errors in TerminalHandler — Raw
http.ClientExceptionerrors emitted by streaming response bodies are now wrapped inHttpResilienceExceptionfor uniform error handling. - FIX-07: Forward swallowed
onFallbackerrors to event hub —FallbackHandlernow emits aFallbackCallbackErrorEventto theResilienceEventHubwhen theonFallbackcallback throws, instead of silently swallowing the error. AddedeventHubfield toFallbackPolicy. - FIX-10: Guard ExponentialBackoff against int overflow — For attempts
with exponent > 52,
ExponentialBackoff.delayFor()now returnsmaxDelaydirectly instead of crashing withUnsupportedErrorondouble.infinity. - FIX-11: Handle
Mapbody inResilientHttpClient._send()— Passing aMap<String, dynamic>body topost()/put()/patch()now auto-encodes it as JSON withContent-Type: application/json. Unsupported body types now throwArgumentErrorinstead of silently dropping data.
Improved #
- FIX-08: Copy-on-write optimization in
ResilienceEventHub.emit()— Listener snapshots are now cached and invalidated only on add/remove, reducing GC pressure on hot-path event emission. Dynamic dispatch in_safeInvokereplaced withFunction.applyto satisfyavoid_dynamic_callslint. - FIX-09: Ring buffer for sliding window in
CircuitBreakerState— ReplacedList.removeAt(0)(O(n)) with a fixed-size ring buffer (O(1)) for the sliding-window failure counter.
Added #
FallbackCallbackErrorEvent— new event type emitted whenFallbackPolicy.onFallbackthrows.FallbackPolicy.eventHub— optionalResilienceEventHubfor fallback lifecycle event forwarding.
Documentation #
- FIX-12: Added "When to use" guidance to
HttpClientBuilder,FluentHttpClientBuilder,BulkheadHandler, andBulkheadIsolationHandlerdoc comments. - FIX-13:
NoOpPipelineannotated as testing-only with expanded doc comment. - FIX-14:
TimeoutHandlerdoc comment now documents the "abandoned in-flight request" limitation and suggestsCancellationTokenor socket-level timeouts as alternatives. - Enhanced README with comprehensive migration guide, expanded usage examples, and improved formatting.
- Extended
example/example.dartwithMapbody posting, transport-agnostic policy engine, and error-handling patterns.
1.0.3 — 2026-02-25 #
Added #
davianspace_dependencyinjectionintegration —davianspace_dependencyinjection ^1.0.3is now a runtime dependency. Two extension methods are added toServiceCollection:addHttpClientFactory([configure])— registers a singletonHttpClientFactory, optionally applying defaults and named-client setup. Uses try-add semantics.addTypedHttpClient<TClient>(create, {clientName, configure})— registersTClientas a transient service backed by a named client on the sharedHttpClientFactory. The factory is auto-registered if not already present. Configuration is applied viaonContainerBuiltso the full container is assembled before any client is resolved.
davianspace_loggingintegration —loggingdependency replaced bydavianspace_logging ^1.0.3.LoggingHandlernow acceptsdavianspace_logging.Logger(injected or defaults toNullLogger).
Changed #
- Removed
metadependency —@immutableand@internalannotations dropped;final classdeclarations in Dart 3 already enforce immutability without the annotation. LoggingHandlerdefault logger changed fromlogging.Logger('davianspace.http')toNullLogger('davianspace.http')— no-op until aLoggeris injected.
1.0.2 — 2026-02-25 #
Added #
- Rate limiter integration — companion package
davianspace_http_ratelimitv1.0.0 published. AddswithRateLimit(RateLimitPolicy)as a fluent extension onHttpClientBuilder, supporting six algorithms: Token Bucket, Fixed Window, Sliding Window (counter), Sliding Window Log, Leaky Bucket, and Concurrency Limiter. Server-side per-key admission control (ServerRateLimiter) is also included. Importpackage:davianspace_http_ratelimit/davianspace_http_ratelimit.dartalongside this package to unlock the integration. - Example 10 —
example/example.dartgains a rate limiter integration example demonstratingTokenBucketRateLimiter,FixedWindowRateLimiter, and the recommended pipeline placement ofwithRateLimit.
Fixed #
TerminalHandlerfalse-positive analyzer warnings — removed the redundant@internalannotation (and unusedpackage:metaimport) fromTerminalHandler. The class is already hidden from public consumers viahide TerminalHandlerin the main barrel; the annotation caused spuriousinvalid_use_of_internal_memberwarnings for in-package callers inHttpPipeline,HttpPipelineBuilder, and the test helper.doc/architecture.md—CircuitBreakerRegistryAPI references corrected:isHealthyandsnapshotare read-only getters (not parameterised methods); the snippet now correctly showsregistry.isHealthy(bool),registry.snapshot(Map<String, CircuitState>), and per-circuit access viaregistry['name'].README.md— Health Checks & Monitoring code example corrected to match the realCircuitBreakerRegistrygetter API.pubspec.yamldescription improved to mention all seven resilience policies within pub.dev's 180-character limit.
1.0.1 — 2026-02-24 #
Added #
Resilience hardening
ResiliencePolicy.dispose()— virtual lifecycle method on the base class; enables deterministic cleanup of circuit-breaker timers, semaphore slots, and other stateful resources.PolicyWrap.dispose()— recursively disposes all child policies in a composed pipeline.HttpClientBuilder.build()— theonDisposecallback now automatically disposesPolicyHandlerpolicies when the client is disposed.CircuitBreakerRegistryhealth-check API —isHealthy,snapshot,contains,[]operator, andcircuitNamesfor production monitoring.RetryResiliencePolicy.onRetrycallback — fires on each retry attempt with the attempt number and exception for external telemetry integration.RetryCallbacktypedef —void Function(int attempt, Object? exception).
Configuration-driven hedging & fallback
HedgingConfig— typed config model for speculative request hedging (hedgeAfterMs,maxHedgedAttempts), parsed from JSON.FallbackConfig— typed config model for fallback trigger status codes, parsed from JSON.ResilienceConfigLoader— new_parseHedging()and_parseFallback()section parsers; full JSON support for all seven policy sections.ResilienceConfigBinder.buildHedging()— bindsHedgingConfig→HedgingPolicy.ResilienceConfigBinder.buildFallbackPolicy()— bindsFallbackConfig→FallbackPolicywith status-code-based predicate; the fallback action is always supplied programmatically.
Per-request streaming
HttpRequest.streamingKey— well-known metadata constant ('resilience.streaming') enabling per-request override of the pipeline's default streaming mode.TerminalHandler.send()— checkscontext.request.metadataforHttpRequest.streamingKeybefore falling back to the handler-levelstreamingModedefault.
Observability & security
LoggingHandlerheader redaction — newredactedHeadersandlogHeadersconstructor parameters; default redacted set includesauthorization,proxy-authorization,cookie,set-cookie, andx-api-key.
Changed #
CircuitBreakerStatenow usesStopwatchfor break-duration measurement, eliminating clock-skew sensitivity._openedAtand_lastTransitionAtuseDateTime.now().toUtc()for consistent serialisation.BulkheadResiliencePolicy._AsyncSemaphore— internal rewrite with_SemaphoreEntrywrapper and cancellation flag to prevent slot leakage under high-contention / timeout scenarios.BulkheadPolicy._queue— changed fromList<_QueueEntry>toQueue<_QueueEntry>for O(1) dequeue operations.CancellationToken.onCancelled— theFuture<void>is now memoised; repeated accesses return the same instance.Backoffstrategies — shared_defaultRandominstance replaces per-callmath.Random()allocations across all backoff implementations.HttpStatusException.body— capped at 64 KB; bodies exceeding the limit are truncated with a… [truncated N bytes]suffix to prevent unbounded memory consumption in diagnostic logging.HttpResponseclass documentation — clarified as "effectively immutable" with a note about internal_streamConsumedtracking state.HttpContext.startedAt— documentation clarifies local-time-zone semantics and recommendselapsedfor monotonic measurement.CircuitBreakerResiliencePolicy— enhanced class-level documentation covering lifecycle anddispose()contract.
Fixed #
BulkheadResiliencePolicy— fixed semaphore slot leak when queued requests were cancelled or timed out before acquiring a permit.RetryResiliencePolicy— fixedTypeErrorwhenretryForever: truecausedtotalAttemptsto benull; now defaults to-1.RetryResiliencePolicy— removed invalid[attempt]/[exception]doc-comment references that triggeredcomment_referenceslint info.
1.0.0 — 2026-01-15 #
First stable release. All public APIs are covered by semantic-versioning guarantees.
Added #
Resilience policy engine (ResiliencePolicy)
- Abstract
ResiliencePolicybase with genericexecute<T>()contract. RetryResiliencePolicy— stateless retry with constant, linear, exponential, and decorrelated-jitter back-off strategies.CircuitBreakerResiliencePolicy— Closed / Open / Half-Open state machine with configurable failure threshold, sampling window, and open duration.TimeoutResiliencePolicy— cancels actions after a configurable deadline.BulkheadResiliencePolicy— bounded-concurrency semaphore with observableactiveCountandqueuedCountmetrics.BulkheadIsolationResiliencePolicy—BulkheadIsolationSemaphore-backed isolation policy with queue-timeout and rejection callbacks.FallbackResiliencePolicy— response/exception predicate + async fallback action.PolicyWrap— combines an ordered list ofResiliencePolicyinstances.ResiliencePipelineBuilder— fluent DSL to constructPolicyWrapchains.Policy— static factory class:Policy.retry,Policy.circuitBreaker,Policy.timeout,Policy.bulkhead,Policy.bulkheadIsolation,Policy.fallback,Policy.wrap.PolicyRegistry— named-policy store for sharing instances across the app.RetryContext— per-attempt context carrying the attempt index and previous outcome, available to retry predicates.- Backoff strategies:
ConstantBackoff,LinearBackoff,ExponentialBackoff,DecorrelatedJitterBackoff,AddedJitterBackoff. OutcomeClassifier— pluggable result/exception →PolicyOutcomemapping.
Observability (ResilienceEventHub)
ResilienceEventHub— broadcast stream for policy lifecycle events.- Event types:
RetryAttemptEvent,CircuitStateChangedEvent,BulkheadRejectedEvent,TimeoutEvent,FallbackActivatedEvent. withEventHub()extension on all resilience policies and handlers.
Hedging handler
HedgingPolicy— configurable speculative request hedging for idempotent operations to reduce tail latency.HedgingHandler— pipeline handler that launches concurrent hedge requests after a configurable delay.
Fallback handler
FallbackPolicy— configurable fallback action for the handler pipeline, triggered by exception or status-code predicates.FallbackHandler— pipeline handler that executes a fallback on downstream failure.
Fluent builder DSL (FluentHttpClientBuilder)
HttpClientFactoryFluentExtensionadds.withResiliencePipeline()and.using()toHttpClientBuilder.FluentHttpClientBuilder— immutable step-builder returning a new instance per decoration.
JSON configuration layer
ResilienceConfig— typed configuration model for all policy parameters.ResilienceConfigLoader— resolves config from one or moreResilienceConfigSourceimplementations.InMemoryConfigSource— in-process config source backed by aMap.JsonStringConfigSource— parses raw JSON strings intoResilienceConfig.ResilienceConfigBinder— binds a loaded config toResiliencePolicyinstances usingPolicyRegistry.PolicyRegistryConfigExtension— extension onPolicyRegistryfor one-line binding:registry.loadFromConfig(config).
Bulkhead isolation handler
BulkheadIsolationHandler— handler-layer counterpart toBulkheadIsolationResiliencePolicy.- Exposes live
activeCount,queuedCount, andsemaphoremetrics.
Streaming support
TerminalHandlersupportsstreamingModefor unbuffered response bodies viaHttpResponse.bodyStream.
Changed #
HttpStatusExceptionnow extendsHttpResilienceException(previously implementedExceptiondirectly), enabling unified catch withon HttpResilienceException.TerminalHandlerandHttpPipelineBuilderhave been annotated@internal: they remain exported for internal use but produce a lint warning if referenced outside the package. UseHttpClientFactory/HttpClientBuilder.addHandler()instead.
Fixed #
- Stack traces are fully preserved via
Error.throwWithStackTracethroughout retry and pipeline code. BulkheadRejectedExceptioncarries bothmaxConcurrencyandmaxQueueDepthfields for accurate diagnostic messages.