How Middleware Works
The middleware pipeline follows a simple pattern:The HttpMiddleware Interface
All middleware implements a single interface:The BaseMiddleware Abstract Class
For most middleware, you do not need to implement the fullhandle() method. The BaseMiddleware class provides a template with overridable hooks:
| Method | Purpose |
|---|---|
beforeRequest($request) | Modify the request before sending. Return the (possibly modified) request. |
afterRequest($request, $response) | Inspect or modify the response after receiving it. Return the response. |
shouldDecorateResponse($request, $response) | Return true to wrap the response through toResponse(). Defaults to true. |
toResponse($request, $response) | Return a decorated response (e.g., with a transformed stream). |
shouldExecute($request) | Return false to skip this middleware entirely for a given request. |
Registering Middleware
On an Existing Client
TheHttpClient is immutable. withMiddleware() returns a new client with the middleware appended:
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:Retry-After header when present. The jitter options are:
none— exact exponential backofffull— random delay between 0 and the calculated backoffequal— half the backoff plus a random portion of the other half
CircuitBreakerMiddleware
Prevents repeated calls to a failing service by tracking failures per host:- Closed — requests flow normally; failures are counted.
- Open — after
failureThresholdfailures, the circuit opens and all requests throwCircuitBreakerOpenExceptionforopenForSecseconds. - Half-open — after the timeout, a limited number of probe requests are allowed. If
successThresholdprobes succeed, the circuit closes. If any fail, it reopens.
IdempotencyMiddleware
Attaches a unique idempotency key to requests, which prevents duplicate processing when retries occur: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: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, useBaseResponseDecorator to wrap the stream with a transformation function:
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:Middleware Order
The order you register middleware determines the execution flow. Middleware registered first is the outermost layer:- Request flow: Logging -> Retry -> Auth -> Driver
- Response flow: Driver -> Auth -> Retry -> Logging
Middleware Stack API
TheMiddlewareStack class provides fine-grained control over the middleware collection:
See Also
- Streaming Responses — EventSourceMiddleware for SSE parsing.
- Custom Clients — create drivers that middleware wraps around.