{
  "$type": "site.standard.document",
  "bskyPostRef": {
    "cid": "bafyreighfm5f5t7qh3q46n53v2ijxqbk4ybns5gjxcogtzcapolbppyjz4",
    "uri": "at://did:plc:pi6woz4d47bkuws673w2il2r/app.bsky.feed.post/3mirgvdzezqo2"
  },
  "path": "/t/http-tower-hs-a-rust-tower-inspired-middleware-library-for-haskell/13892#post_1",
  "publishedAt": "2026-04-05T08:31:58.000Z",
  "site": "https://discourse.haskell.org",
  "tags": [
    "http-tower-hs",
    "github.com/jarlah/http-tower-hs"
  ],
  "textContent": "I’ve been building http-tower-hs, a composable HTTP client middleware library for Haskell, inspired by Rust’s Tower.\n\n## The problem\n\nHaskell 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.\n\n## The approach\n\nTwo core types:\n\n\n    newtype Service req res = Service\n      { runService :: req -> IO (Either ServiceError res) }\n\n    type Middleware req res = Service req res -> Service req res\n\n\nA `Service` is a function from request to response. `Middleware` wraps a service to add behavior. You compose them with the `|>` operator:\n\n\n    client <- newClient\n    let configured = client\n          |> withBearerAuth \"my-token\"\n          |> withRequestId\n          |> withRetry (exponentialBackoff 3 0.5 2.0)\n          |> withTimeout 5000\n          |> withCircuitBreaker config breaker\n          |> withValidateStatus (\\c -> c >= 200 && c < 300)\n          |> withTracing\n\n    result <- runRequest configured request\n    case result of\n      Left err   -> handleError err\n      Right resp -> handleSuccess resp\n\n\nAll errors are `Either ServiceError Response` — no exceptions escape the stack.\n\n## Middleware included (12 so far)\n\nMiddleware | Description\n---|---\n**Retry** | Constant or exponential backoff\n**Timeout** | Per-request millisecond timeouts\n**Logging** | Pluggable request/response logging\n**Circuit Breaker** | Three-state (Closed/Open/HalfOpen) via STM\n**OpenTelemetry Tracing** | Automatic spans with stable HTTP semantic conventions\n**Set Header** | Add headers, Bearer auth, User-Agent\n**Request ID** | UUID v4 correlation IDs\n**Follow Redirect** | Automatic 3xx following\n**Filter** | Predicate-based request filtering\n**Hedge** | Speculative retry via `async`/`race`\n**Validate** | Status code, Content-Type, header validation\n**Test Double** | Mock services, route-based mocks, request recorder\n\n## Design decisions\n\n  * **Simple function composition** over type-level lists for v1 — middleware is just `Service -> Service`\n  * **`Either` errors, not exceptions** — `ServiceError` covers HTTP errors, timeouts, retry exhaustion, circuit breaker open\n  * **OpenTelemetry via`hs-opentelemetry-api`** — no-ops when no TracerProvider is configured, zero overhead for users who don’t use tracing\n  * **Generic where possible** — `Filter`, `Hedge`, `Retry`, `Timeout` work with any `req`/`res`, not just HTTP\n\n\n\n## Testing\n\n69 tests including property-based tests (QuickCheck) and a Jaeger Docker integration test via `testcontainers-haskell` that verifies real OTLP span export end-to-end.\n\n## Status\n\nThis 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.\n\nI’d appreciate feedback on the API design, especially:\n\n  * Is `Service req res` the right core abstraction, or should it be more constrained?\n  * Should middleware ordering be enforced at the type level in a future version?\n  * Any middleware you’d want to see that’s missing?\n\n",
  "title": "Http-tower-hs — A Rust Tower-inspired middleware library for Haskell"
}