Skip to main content
Middleware is the primary extension mechanism for the HTTP client. It lets you add behaviors — logging, retries, circuit breaking, authentication, response transformation — without modifying drivers or request code. Each middleware sits in a pipeline: requests pass through in order on the way out, and responses pass through in reverse order on the way back.

How Middleware Works

The middleware pipeline follows a simple pattern:
Each middleware receives the request and a reference to the next handler in the chain. It can modify the request, call the next handler, inspect or modify the response, or short-circuit the chain entirely by returning a response without calling next.

The HttpMiddleware Interface

All middleware implements a single interface:
Here is a complete example that adds a header to every request:

The BaseMiddleware Abstract Class

For most middleware, you do not need to implement the full handle() method. The BaseMiddleware class provides a template with overridable hooks:
The available hooks are:

Registering Middleware

On an Existing Client

The HttpClient is immutable. withMiddleware() returns a new client with the middleware appended:
The second argument is an optional name, which lets you remove the middleware later:

Via the Builder

The builder collects middleware before creating the client:

Built-in Middleware

The package ships with several production-ready middleware components.

RetryMiddleware

Automatically retries failed requests with exponential backoff and jitter:
The retry middleware only operates on synchronous (non-streamed) requests. It respects the Retry-After header when present. The jitter options are:
  • none — exact exponential backoff
  • full — random delay between 0 and the calculated backoff
  • equal — half the backoff plus a random portion of the other half

CircuitBreakerMiddleware

Prevents repeated calls to a failing service by tracking failures per host:
The circuit breaker follows the standard state machine:
  • Closed — requests flow normally; failures are counted.
  • Open — after failureThreshold failures, the circuit opens and all requests throw CircuitBreakerOpenException for openForSec seconds.
  • Half-open — after the timeout, a limited number of probe requests are allowed. If successThreshold probes succeed, the circuit closes. If any fail, it reopens.
State is stored in APCu when available, with an in-memory fallback for environments without it.

IdempotencyMiddleware

Attaches a unique idempotency key to requests, which prevents duplicate processing when retries occur:
The middleware only attaches keys to the specified HTTP methods and hosts. If the request already has an idempotency key header, it is left unchanged.

EventSourceMiddleware

Parses server-sent event streams into clean payloads. See Streaming Responses for usage details.

RecordReplayMiddleware

Records HTTP interactions to disk and replays them later, which is invaluable for testing and development:
When fallbackToRealRequests is true, unrecorded requests are sent to the real server. When false, a RecordingNotFoundException is thrown. Record/replay matching is intentionally narrow in 2.0.0: recordings are keyed by request method, full URL, and body. Request headers and request options are not part of the identity contract. For streamed responses, recording mode buffers the full upstream stream before returning a replayable streamed response. That keeps replay deterministic, but it means recording mode is not a transparent progressive-streaming path.

Response Decoration

For middleware that needs to transform streamed responses, use BaseResponseDecorator to wrap the stream with a transformation function:
This creates a new HttpResponse with a TransformStream that applies your function to each chunk. The original response is not modified.

Writing Custom Middleware

Here is a practical example of authentication middleware:
And a logging middleware that records request duration:

Middleware Order

The order you register middleware determines the execution flow. Middleware registered first is the outermost layer:
In this setup:
  • Request flow: Logging -> Retry -> Auth -> Driver
  • Response flow: Driver -> Auth -> Retry -> Logging
The retry middleware wraps the auth middleware, so retried requests get fresh auth headers. The logging middleware sees all attempts, including retries.

Middleware Stack API

The MiddlewareStack class provides fine-grained control over the middleware collection:
You can replace the entire stack on a client:

See Also