{"id":47719258,"url":"https://github.com/jhonsferg/relay","last_synced_at":"2026-04-09T20:19:28.550Z","repository":{"id":347942428,"uuid":"1195801425","full_name":"jhonsferg/relay","owner":"jhonsferg","description":"A resilient, extensible HTTP client for Go with middleware, circuit breakers, rate limiting, caching, and OpenTelemetry support","archived":false,"fork":false,"pushed_at":"2026-04-03T02:20:36.000Z","size":671,"stargazers_count":2,"open_issues_count":61,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-04-03T05:18:34.308Z","etag":null,"topics":["caching","circuit-breaker","go","golang","http","http-client","middleware","observability","opentelemetry","rate-limiting","resilience","retry"],"latest_commit_sha":null,"homepage":"https://pkg.go.dev/github.com/jhonsferg/relay","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jhonsferg.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-30T04:33:54.000Z","updated_at":"2026-04-03T02:12:50.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/jhonsferg/relay","commit_stats":null,"previous_names":["jhonsferg/relay"],"tags_count":57,"template":false,"template_full_name":null,"purl":"pkg:github/jhonsferg/relay","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jhonsferg%2Frelay","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jhonsferg%2Frelay/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jhonsferg%2Frelay/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jhonsferg%2Frelay/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jhonsferg","download_url":"https://codeload.github.com/jhonsferg/relay/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jhonsferg%2Frelay/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31508282,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-07T03:10:19.677Z","status":"ssl_error","status_checked_at":"2026-04-07T03:10:13.982Z","response_time":105,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["caching","circuit-breaker","go","golang","http","http-client","middleware","observability","opentelemetry","rate-limiting","resilience","retry"],"created_at":"2026-04-02T19:14:29.895Z","updated_at":"2026-04-09T20:19:28.544Z","avatar_url":"https://github.com/jhonsferg.png","language":"Go","readme":"\u003cdiv align=\"center\"\u003e\n\n# Relay\n\n**A production-grade, declarative HTTP client for Go.**\n\n[![Go Version](https://img.shields.io/badge/Go-1.24%2B-00ADD8?style=for-the-badge\u0026logo=go)](https://pkg.go.dev/github.com/jhonsferg/relay)\n[![CI](https://img.shields.io/github/actions/workflow/status/jhonsferg/relay/ci.yml?style=for-the-badge\u0026logo=github\u0026label=CI)](https://github.com/jhonsferg/relay/actions/workflows/ci.yml)\n[![Tests](https://img.shields.io/badge/tests-6%20OS%2FGo%20combos-0099ff?style=for-the-badge\u0026logo=github)](https://github.com/jhonsferg/relay/actions/workflows/ci.yml)\n[![Codecov](https://img.shields.io/badge/coverage-tracked-41B883?style=for-the-badge\u0026logo=codecov)](https://codecov.io/gh/jhonsferg/relay)\n[![CodeQL](https://img.shields.io/github/actions/workflow/status/jhonsferg/relay/codeql.yml?style=for-the-badge\u0026logo=github\u0026label=CodeQL)](https://github.com/jhonsferg/relay/actions/workflows/codeql.yml)\n[![Trivy](https://img.shields.io/badge/vulnerability%20scan-Trivy-1f77b4?style=for-the-badge\u0026logo=github)](https://github.com/jhonsferg/relay/actions/workflows/trivy.yml)\n[![Release](https://img.shields.io/github/v/release/jhonsferg/relay?style=for-the-badge\u0026logo=github\u0026color=orange)](https://github.com/jhonsferg/relay/releases/latest)\n[![pkg.go.dev](https://img.shields.io/badge/pkg.go.dev-reference-007D9C?style=for-the-badge\u0026logo=go)](https://pkg.go.dev/github.com/jhonsferg/relay)\n[![Go Report Card](https://img.shields.io/badge/go%20report-A%2B-brightgreen?style=for-the-badge)](https://goreportcard.com/report/github.com/jhonsferg/relay)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=for-the-badge)](LICENSE)\n\n---\n\n**[Documentation](https://jhonsferg.github.io/relay) · [pkg.go.dev](https://pkg.go.dev/github.com/jhonsferg/relay) · [Quick Start](#quick-start) · [Extensions](#extensions) · [Tools](#tools) · [Changelog](CHANGELOG.md)**\n\n\u003c/div\u003e\n\n## Overview\n\nRelay brings the ergonomics of Python's *requests* and the resilience of *Resilience4j* to Go. It provides a fluent, batteries-included API for building resilient HTTP clients: retries, circuit breaking, rate limiting, deduplication, adaptive timeouts, load balancing, and full observability  -  all composable via options.\n\nThe core module has **zero external dependencies**. Every integration (Redis, OTel, Prometheus, gRPC, slog, chaos, VCR, etc.) lives in its own optional extension module so you only pull in what you need.\n\n```bash\ngo get github.com/jhonsferg/relay\n```\n\nRequires Go 1.24 or later. WASM (`js/wasm`) targets are supported.\n\n---\n\n## Quick Start\n\n```go\npackage main\n\nimport (\n    \"context\"\n    \"fmt\"\n    \"log\"\n    \"time\"\n\n    \"github.com/jhonsferg/relay\"\n)\n\ntype Repo struct {\n    ID   int    `json:\"id\"`\n    Name string `json:\"full_name\"`\n}\n\nfunc main() {\n    client := relay.New(\n        relay.WithBaseURL(\"https://api.github.com\"),\n        relay.WithRetry(relay.RetryConfig{MaxAttempts: 3}),\n        relay.WithTimeout(10*time.Second),\n    )\n\n    var repo Repo\n    _, err := relay.Get[Repo](context.Background(), client, \"/repos/golang/go\", \u0026repo)\n    if err != nil {\n        log.Fatal(err)\n    }\n    fmt.Println(repo.Name)\n}\n```\n\n---\n\n## Features\n\n### Core\n\n| Feature | Description |\n|---------|-------------|\n| **Fluent request builder** | Chain `.GET()`, `.POST()`, `.Header()`, `.Query()`, `.Body()` |\n| **Retry \u0026 backoff** | Exponential backoff with jitter, configurable retry conditions |\n| **Circuit breaker** | Automatic open/half-open/closed state machine |\n| **Rate limiting** | Token bucket and sliding window algorithms |\n| **Request deduplication** | In-flight singleflight to collapse concurrent identical requests |\n| **Retry budget** | Sliding window budget to prevent retry storms |\n| **Client-side load balancer** | Round-robin and random strategies across backends |\n| **Adaptive timeout** | Percentile-based timeout derived from observed latency |\n| **Bulkhead isolation** | Concurrency limits per client or endpoint group |\n| **Request hedging** | Parallel speculative requests, use first response |\n| **Streaming** | `io.Reader` and channel-based streaming for large payloads |\n| **Hooks** | `OnBeforeRequest` / `OnAfterResponse` for auth, logging, metrics |\n| **Generic decode** | `relay.Get[T]`, `relay.Post[T]` with zero-alloc JSON decoding |\n| **Error classification** | Distinguish transient / permanent / rate-limited errors |\n| **ETag \u0026 idempotency** | Built-in idempotency key generation and ETag support |\n| **TLS \u0026 certificates** | Dynamic cert reloading, mTLS, custom CA bundles |\n\n### Auth \u0026 Credentials\n\n| Feature | Description |\n|---------|-------------|\n| **Bearer / Basic auth** | `WithBearerToken`, `WithBasicAuth` options |\n| **HMAC-SHA256 signing** | `HMACRequestSigner` sets `X-Timestamp` + `X-Signature` automatically |\n| **Multi-signer** | `NewMultiSigner` chains multiple signers in order |\n| **Credential rotation** | `RotatingTokenProvider` refreshes tokens before expiry with a configurable threshold |\n| **Custom signer** | Implement the `RequestSigner` interface for any auth scheme |\n| **mTLS** | Mutual TLS with client certificates |\n\n### Transport\n\n| Feature | Description |\n|---------|-------------|\n| **Unix domain socket** | `WithUnixSocket`  -  connect to local services via socket path (Linux/macOS) |\n| **DNS SRV discovery** | `WithSRVDiscovery`  -  resolve service endpoints via DNS SRV records with TTL caching |\n| **HTTP/2 push promises** | `WithHTTP2PushHandler`  -  handle server push promises and cache pushed responses |\n| **WASM/js** | Builds on `js/wasm`; `WithUnixSocket` is a no-op on that target for portability |\n\n### Compression\n\n| Feature | Description |\n|---------|-------------|\n| **Gzip / Zstd** | `WithCompression(relay.Gzip)` or `WithCompression(relay.Zstd)` for response decompression |\n| **Request compression** | `WithRequestCompression` compresses outgoing request bodies |\n| **Dictionary Zstd** | `ext/compress`  -  `ZstdDictionaryCompressor` for pre-shared dictionary compression |\n\n### Observability\n\n| Feature | Description |\n|---------|-------------|\n| **HAR export** | `HARRecorder` captures all traffic in HAR format; `HARRecorder.All()` returns a Go 1.23 `iter.Seq[HAREntry]` iterator |\n| **OpenTelemetry** | `ext/otel`  -  unified tracing + metrics via `WithOtel(tracer, meter)` |\n| **Prometheus** | `ext/prometheus`  -  Prometheus metrics exporter |\n\n### Validation\n\n| Feature | Description |\n|---------|-------------|\n| **Response schema validation** | `WithSchemaValidator`  -  validate decoded responses against struct tags or a JSON Schema |\n| **Struct validator** | `NewStructValidator`  -  validates required fields and rules via struct tags |\n| **JSON Schema validator** | `NewJSONSchemaValidator`  -  validates against an inline JSON Schema definition |\n\n\u003e Full feature documentation: **[jhonsferg.github.io/relay](https://jhonsferg.github.io/relay)**\n\n---\n\n## Extensions\n\nEach extension is a standalone Go module  -  add only what you use:\n\n| Module | Import path | Description |\n|--------|-------------|-------------|\n| `ext/compress` | `github.com/jhonsferg/relay/ext/compress` | Dictionary-based Zstd compression (`ZstdDictionaryCompressor`) |\n| `ext/oidc` | `github.com/jhonsferg/relay/ext/oidc` | OIDC/JWT bearer token provider |\n| `ext/otel` | `github.com/jhonsferg/relay/ext/otel` | OpenTelemetry tracing + metrics in one option (`WithOtel`) |\n| `ext/tracing` | `github.com/jhonsferg/relay/ext/tracing` | OpenTelemetry distributed tracing (standalone) |\n| `ext/metrics` | `github.com/jhonsferg/relay/ext/metrics` | OpenTelemetry metrics (standalone) |\n| `ext/prometheus` | `github.com/jhonsferg/relay/ext/prometheus` | Prometheus metrics exporter |\n| `ext/slog` | `github.com/jhonsferg/relay/ext/slog` | Structured logging via `log/slog` |\n| `ext/zap` | `github.com/jhonsferg/relay/ext/zap` | Zap logging integration |\n| `ext/chaos` | `github.com/jhonsferg/relay/ext/chaos` | Fault injection for resilience testing |\n| `ext/vcr` | `github.com/jhonsferg/relay/ext/vcr` | HTTP cassette recording and playback |\n| `ext/mock` | `github.com/jhonsferg/relay/ext/mock` | Mock transport for unit tests |\n| `ext/oauth` | `github.com/jhonsferg/relay/ext/oauth` | OAuth2 token management |\n| `ext/sigv4` | `github.com/jhonsferg/relay/ext/sigv4` | AWS SigV4 request signing |\n| `ext/openapi` | `github.com/jhonsferg/relay/ext/openapi` | OpenAPI request/response validation |\n| `ext/redis` | `github.com/jhonsferg/relay/ext/redis` | Redis-backed cache and rate limiting |\n| `ext/http3` | `github.com/jhonsferg/relay/ext/http3` | HTTP/3 QUIC transport |\n| `ext/websocket` | `github.com/jhonsferg/relay/ext/websocket` | WebSocket upgrade |\n| `ext/grpc` | `github.com/jhonsferg/relay/ext/grpc` | gRPC bridge transport |\n| `ext/graphql` | `github.com/jhonsferg/relay/ext/graphql` | GraphQL query support |\n| `ext/sentry` | `github.com/jhonsferg/relay/ext/sentry` | Sentry error reporting |\n| `ext/brotli` | `github.com/jhonsferg/relay/ext/brotli` | Brotli compression support |\n| `ext/breaker/gobreaker` | `github.com/jhonsferg/relay/ext/breaker/gobreaker` | Circuit breaker backed by `gobreaker` |\n| `ext/cache/lru` | `github.com/jhonsferg/relay/ext/cache/lru` | In-process LRU response cache |\n| `ext/cache/twolevel` | `github.com/jhonsferg/relay/ext/cache/twolevel` | Two-level (L1+L2) response cache |\n| `ext/ratelimit/distributed` | `github.com/jhonsferg/relay/ext/ratelimit/distributed` | Distributed rate limiting |\n| `ext/memcached` | `github.com/jhonsferg/relay/ext/memcached` | Memcached-backed cache |\n| `ext/jitterbug` | `github.com/jhonsferg/relay/ext/jitterbug` | Pluggable jitter strategies for retry backoff |\n\n\u003e Extension documentation: **[jhonsferg.github.io/relay/extensions](https://jhonsferg.github.io/relay/extensions/index/)**\n\n---\n\n## Tools\n\n### relay-gen  -  OpenAPI client generator\n\n`relay-gen` reads an OpenAPI 3.x spec and generates a type-safe Go client using relay:\n\n```bash\ngo install github.com/jhonsferg/relay/cmd/relay-gen@latest\n\nrelay-gen -spec openapi.json -pkg acme -out ./acme/client.go\n```\n\nThe generated client exposes one method per operation with strongly-typed request/response structs and full relay middleware support.\n\n### relay-probe  -  health check CLI\n\n```bash\ngo install github.com/jhonsferg/relay/cmd/relay-probe@latest\nrelay-probe https://api.example.com/health\n```\n\n### relay-bench  -  micro-benchmarking harness\n\n```bash\ngo install github.com/jhonsferg/relay/cmd/relay-bench@latest\nrelay-bench -url https://api.example.com/ping -n 1000\n```\n\n---\n\n## Unix Socket Transport\n\nConnect to services exposed via Unix domain sockets (Linux/macOS):\n\n```go\nclient := relay.New(\n    relay.WithBaseURL(\"http://localhost\"),\n    relay.WithUnixSocket(\"/var/run/myapp.sock\"),\n)\nresp, err := client.Execute(relay.NewRequest().GET(\"/status\"))\n```\n\n\u003e **Note:** On `js/wasm` targets `WithUnixSocket` is accepted but silently ignored, keeping call sites portable.\n\n---\n\n## DNS SRV Discovery\n\nResolve backends dynamically from DNS SRV records:\n\n```go\nresolver := relay.NewSRVResolver(\"myservice\", \"tcp\", \"example.com\", \"https\",\n    relay.WithSRVTTL(30*time.Second),\n    relay.WithSRVBalancer(relay.SRVRoundRobin),\n)\nclient := relay.New(relay.WithSRVDiscovery(resolver))\n```\n\n---\n\n## HTTP/2 Push Promises\n\nHandle server-pushed resources and cache them for subsequent requests:\n\n```go\ncache := relay.NewPushedResponseCache()\nclient := relay.New(\n    relay.WithBaseURL(\"https://api.example.com\"),\n    relay.WithHTTP2PushHandler(cache),\n)\n```\n\n---\n\n## HAR Recording\n\nCapture all traffic in [HAR](https://w3c.github.io/web-performance/specs/HAR/Overview.html) format for debugging or test fixtures:\n\n```go\nrec := relay.NewHARRecorder()\nclient := relay.New(relay.WithMiddleware(rec.Middleware()))\n\n// iterate with a Go 1.23 range-over-func loop\nfor entry := range rec.All() {\n    fmt.Println(entry.Request.URL, entry.Response.Status)\n}\n\ndata, _ := rec.Export()\nos.WriteFile(\"traffic.har\", data, 0o644)\n```\n\n---\n\n## Request Authentication\n\n**HMAC-SHA256 signing**  -  sets `X-Timestamp` and `X-Signature` headers automatically:\n\n```go\nclient := relay.New(\n    relay.WithRequestSigner(\u0026relay.HMACRequestSigner{Key: []byte(secret)}),\n)\n```\n\n**Rotating token provider**  -  refreshes credentials before expiry:\n\n```go\nprovider := relay.NewRotatingTokenProvider(fetchTokenFunc, 5*time.Minute)\nclient := relay.New(relay.WithCredentialProvider(provider))\n```\n\n**Multiple signers**  -  chain signers in order with `NewMultiSigner`:\n\n```go\nclient := relay.New(\n    relay.WithRequestSigner(relay.NewMultiSigner(\n        \u0026relay.HMACRequestSigner{Key: signingKey},\n        relay.RequestSignerFunc(func(r *http.Request) error {\n            r.Header.Set(\"X-Tenant\", tenantID)\n            return nil\n        }),\n    )),\n)\n```\n\n---\n\n## Response Schema Validation\n\nValidate decoded responses against struct tags or a JSON Schema:\n\n```go\n// struct-tag validation\nvalidator := relay.NewStructValidator(MyResponse{})\nclient := relay.New(relay.WithSchemaValidator(validator))\n\n// JSON Schema validation\nvalidator, err := relay.NewJSONSchemaValidator(`{\"type\":\"object\",\"required\":[\"id\"]}`)\nclient := relay.New(relay.WithSchemaValidator(validator))\n```\n\n---\n\n## CI/CD\n\nRelay's CI pipeline runs across 6 OS/Go version combinations and includes:\n\n- **Unit \u0026 integration tests**  -  `ci.yml`\n- **Benchmark regression detection**  -  `benchstat.yml` compares PR benchmarks against the base branch using [benchstat](https://pkg.go.dev/golang.org/x/perf/cmd/benchstat) and fails the build if a statistically significant slowdown is detected\n- **CodeQL static analysis**  -  `codeql.yml`\n- **Vulnerability scanning**  -  Trivy (`trivy.yml`)\n\n---\n\n## Testing\n\n```go\nimport \"github.com/jhonsferg/relay/testutil\"\n\nsrv := testutil.NewMockServer()\ndefer srv.Close()\n\nsrv.Enqueue(testutil.Response{Status: 200, Body: `{\"id\":1}`})\n\nclient := relay.New(relay.WithBaseURL(srv.URL))\nresp, err := client.Execute(relay.NewRequest().GET(\"/items/1\"))\n```\n\n\u003e Testing guide: **[jhonsferg.github.io/relay/guides/testing](https://jhonsferg.github.io/relay/guides/testing/)**\n\n---\n\n## Documentation\n\nThe full documentation is at **[jhonsferg.github.io/relay](https://jhonsferg.github.io/relay)**:\n\n- [Getting Started](https://jhonsferg.github.io/relay/quickstart/)\n- [All Features](https://jhonsferg.github.io/relay/features/)\n- [Extension Modules](https://jhonsferg.github.io/relay/extensions/)\n- [API Reference](https://pkg.go.dev/github.com/jhonsferg/relay) on pkg.go.dev\n- 📊 **[Live Benchmark Dashboard](https://jhonsferg.github.io/relay/benchmarks/)**\n\n---\n\n## License\n\nMIT - see [LICENSE](LICENSE).\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjhonsferg%2Frelay","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjhonsferg%2Frelay","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjhonsferg%2Frelay/lists"}