External Publication
Visit Post

Http-tower-hs — A Rust Tower-inspired middleware library for Haskell

Haskell Community [Unofficial] April 5, 2026
Source

I’ve been building http-tower-hs, a composable HTTP client middleware library for Haskell, inspired by Rust’s Tower.

The problem

Haskell has solid HTTP clients (http-client, http-client-tls), but no middleware composition story. Every project ends up hand-rolling retry logic, timeout handling, logging, and circuit breakers around raw HTTP calls. Mercury’s recent blog post “A Couple Million Lines of Haskell” described this exact gap — they write all their own HTTP client bindings because there’s no way to inject cross-cutting concerns like tracing, retries, or circuit breakers.

The approach

Two core types:

newtype Service req res = Service
  { runService :: req -> IO (Either ServiceError res) }

type Middleware req res = Service req res -> Service req res

A Service is a function from request to response. Middleware wraps a service to add behavior. You compose them with the |> operator:

client <- newClient
let configured = client
      |> withBearerAuth "my-token"
      |> withRequestId
      |> withRetry (exponentialBackoff 3 0.5 2.0)
      |> withTimeout 5000
      |> withCircuitBreaker config breaker
      |> withValidateStatus (\c -> c >= 200 && c < 300)
      |> withTracing

result <- runRequest configured request
case result of
  Left err   -> handleError err
  Right resp -> handleSuccess resp

All errors are Either ServiceError Response — no exceptions escape the stack.

Middleware included (12 so far)

Middleware Description
Retry Constant or exponential backoff
Timeout Per-request millisecond timeouts
Logging Pluggable request/response logging
Circuit Breaker Three-state (Closed/Open/HalfOpen) via STM
OpenTelemetry Tracing Automatic spans with stable HTTP semantic conventions
Set Header Add headers, Bearer auth, User-Agent
Request ID UUID v4 correlation IDs
Follow Redirect Automatic 3xx following
Filter Predicate-based request filtering
Hedge Speculative retry via async/race
Validate Status code, Content-Type, header validation
Test Double Mock services, route-based mocks, request recorder

Design decisions

  • Simple function composition over type-level lists for v1 — middleware is just Service -> Service
  • Either errors, not exceptionsServiceError covers HTTP errors, timeouts, retry exhaustion, circuit breaker open
  • OpenTelemetry viahs-opentelemetry-api — no-ops when no TracerProvider is configured, zero overhead for users who don’t use tracing
  • Generic where possibleFilter, Hedge, Retry, Timeout work with any req/res, not just HTTP

Testing

69 tests including property-based tests (QuickCheck) and a Jaeger Docker integration test via testcontainers-haskell that verifies real OTLP span export end-to-end.

Status

This is an early release (0.1.0.0). I’m working on getting it published on Hackage. The source is at github.com/jarlah/http-tower-hs.

I’d appreciate feedback on the API design, especially:

  • Is Service req res the right core abstraction, or should it be more constrained?
  • Should middleware ordering be enforced at the type level in a future version?
  • Any middleware you’d want to see that’s missing?

Discussion in the ATmosphere

Loading comments...