Compare commits
5 Commits
3aa7536328
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| b6350185d9 | |||
| 85cdc5e2c9 | |||
| 25beb2f5c2 | |||
| 16ff427c93 | |||
| 138d4b6c6d |
48
.cursorrules
Normal file
48
.cursorrules
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
You are working on `git.codelab.vc/pkg/httpx`, a Go 1.24 HTTP client/server library with zero external dependencies.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
- Client middleware: `func(http.RoundTripper) http.RoundTripper` — compose with `middleware.Chain`
|
||||||
|
- Server middleware: `func(http.Handler) http.Handler` — compose with `server.Chain`
|
||||||
|
- All configuration uses functional options pattern (`WithXxx` functions)
|
||||||
|
- Chain order for client: Logging → User MW → Retry → Circuit Breaker → Balancer → Transport
|
||||||
|
|
||||||
|
## Package structure
|
||||||
|
|
||||||
|
- `httpx` (root) — Client, request builders (NewJSONRequest, NewFormRequest), error types
|
||||||
|
- `middleware/` — client-side middleware (Logging, Recovery, Auth, Headers, RequestID)
|
||||||
|
- `retry/` — retry middleware with exponential backoff and Retry-After support
|
||||||
|
- `circuitbreaker/` — per-host circuit breaker (sync.Map of host → Breaker)
|
||||||
|
- `balancer/` — load balancing with health checking (RoundRobin, Weighted, Failover)
|
||||||
|
- `server/` — Server, Router, server middleware (RequestID, Recovery, Logging, CORS, RateLimit, MaxBodySize, Timeout), response helpers (WriteJSON, WriteError)
|
||||||
|
- `internal/requestid/` — shared context key (avoids circular import between server and middleware)
|
||||||
|
- `internal/clock/` — deterministic time for tests
|
||||||
|
|
||||||
|
## Code conventions
|
||||||
|
|
||||||
|
- Zero external dependencies — stdlib only, do not add imports outside the module
|
||||||
|
- Functional options: `type Option func(*options)` with `With<Name>` constructors
|
||||||
|
- Test with stdlib only: `testing`, `httptest`, `net/http`. No testify/gomock
|
||||||
|
- Client test helper: `mockTransport(fn)` wrapping `middleware.RoundTripperFunc`
|
||||||
|
- Server test helper: `httptest.NewRecorder`, `httptest.NewRequest`, `waitForAddr(t, srv)`
|
||||||
|
- Thread safety with `sync.Mutex`, `sync.Map`, or `atomic`
|
||||||
|
- Use `internal/clock` for time-dependent tests, not `time.Now()` directly
|
||||||
|
- Sentinel errors in sub-packages, re-exported as aliases in root package
|
||||||
|
|
||||||
|
## When writing new code
|
||||||
|
|
||||||
|
- Client middleware → file in `middleware/`, return `middleware.Middleware`
|
||||||
|
- Server middleware → file in `server/middleware_<name>.go`, return `server.Middleware`
|
||||||
|
- New option → add field to options struct, create `With<Name>` func, apply in constructor
|
||||||
|
- Do NOT import `server` from `middleware` or vice versa (use `internal/requestid` for shared context)
|
||||||
|
- Client.Close() must be called when using WithEndpoints() (stops health checker goroutine)
|
||||||
|
- Request bodies must have GetBody set for retry — use NewJSONRequest/NewFormRequest
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go build ./... # compile
|
||||||
|
go test ./... # test
|
||||||
|
go test -race ./... # test with race detector
|
||||||
|
go vet ./... # static analysis
|
||||||
|
```
|
||||||
39
.gitea/workflows/publish.yml
Normal file
39
.gitea/workflows/publish.yml
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
name: Publish
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags: ["v*"]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: "1.24"
|
||||||
|
|
||||||
|
- name: Vet
|
||||||
|
run: go vet ./...
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: go test -race -count=1 ./...
|
||||||
|
|
||||||
|
- name: Publish to Gitea Package Registry
|
||||||
|
run: |
|
||||||
|
VERSION=${GITHUB_REF#refs/tags/}
|
||||||
|
MODULE=$(go list -m)
|
||||||
|
|
||||||
|
# Create module zip with required prefix: module@version/
|
||||||
|
git archive --format=zip --prefix="${MODULE}@${VERSION}/" HEAD -o module.zip
|
||||||
|
|
||||||
|
# Gitea Go Package Registry API
|
||||||
|
curl -s -f \
|
||||||
|
-X PUT \
|
||||||
|
-H "Authorization: token ${{ secrets.PUBLISH_TOKEN }}" \
|
||||||
|
-H "Content-Type: application/zip" \
|
||||||
|
--data-binary @module.zip \
|
||||||
|
"${{ github.server_url }}/api/packages/pkg/go/upload?module=${MODULE}&version=${VERSION}"
|
||||||
|
env:
|
||||||
|
PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }}
|
||||||
50
.github/copilot-instructions.md
vendored
Normal file
50
.github/copilot-instructions.md
vendored
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# Copilot instructions — httpx
|
||||||
|
|
||||||
|
## Project
|
||||||
|
|
||||||
|
`git.codelab.vc/pkg/httpx` is a Go 1.24 HTTP client and server library with zero external dependencies.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Client (`httpx` root package)
|
||||||
|
- Middleware type: `func(http.RoundTripper) http.RoundTripper`
|
||||||
|
- Chain assembly (outermost → innermost): Logging → User MW → Retry → Circuit Breaker → Balancer → Transport
|
||||||
|
- Retry wraps CB+Balancer so each attempt can hit a different endpoint
|
||||||
|
- Circuit breaker is per-host (sync.Map of host → Breaker)
|
||||||
|
- Client.Close() required when using WithEndpoints() — stops health checker goroutine
|
||||||
|
|
||||||
|
### Server (`server/` package)
|
||||||
|
- Middleware type: `func(http.Handler) http.Handler`
|
||||||
|
- Router wraps http.ServeMux with groups, prefix routing, Mount for sub-handlers
|
||||||
|
- Defaults() preset: RequestID → Recovery → Logging + production timeouts
|
||||||
|
- Available middleware: RequestID, Recovery, Logging, CORS, RateLimit, MaxBodySize, Timeout
|
||||||
|
- WriteJSON/WriteError for JSON responses
|
||||||
|
|
||||||
|
### Sub-packages
|
||||||
|
- `middleware/` — client-side middleware (Logging, Recovery, Auth, Headers, RequestID)
|
||||||
|
- `retry/` — retry with exponential backoff and Retry-After
|
||||||
|
- `circuitbreaker/` — per-host circuit breaker
|
||||||
|
- `balancer/` — load balancing with health checking
|
||||||
|
- `internal/requestid/` — shared context key between server and middleware
|
||||||
|
- `internal/clock/` — deterministic time for tests
|
||||||
|
|
||||||
|
## Conventions
|
||||||
|
|
||||||
|
- All configuration uses functional options (`WithXxx` functions)
|
||||||
|
- Zero external dependencies — do not add requires to go.mod
|
||||||
|
- Tests use stdlib only (testing, httptest) — no testify or gomock
|
||||||
|
- Thread safety with sync.Mutex, sync.Map, or atomic
|
||||||
|
- Client test mock: `mockTransport(fn)` using `middleware.RoundTripperFunc`
|
||||||
|
- Server test helpers: `httptest.NewRecorder`, `httptest.NewRequest`
|
||||||
|
- Do NOT import server from middleware or vice versa — use internal/requestid for shared context
|
||||||
|
- Sentinel errors in sub-packages, re-exported in root package
|
||||||
|
- Use internal/clock for time-dependent tests
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go build ./... # compile
|
||||||
|
go test ./... # test
|
||||||
|
go test -race ./... # test with race detector
|
||||||
|
go vet ./... # static analysis
|
||||||
|
```
|
||||||
139
AGENTS.md
Normal file
139
AGENTS.md
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
# AGENTS.md — httpx
|
||||||
|
|
||||||
|
Universal guide for AI coding agents working with this codebase.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
`git.codelab.vc/pkg/httpx` is a Go HTTP toolkit with **zero external dependencies** (Go 1.24, stdlib only). It provides:
|
||||||
|
- A composable HTTP **client** with retry, circuit breaking, load balancing
|
||||||
|
- A production-ready HTTP **server** with routing, middleware, graceful shutdown
|
||||||
|
|
||||||
|
## Package map
|
||||||
|
|
||||||
|
```
|
||||||
|
httpx/ Root — Client, request builders, error types
|
||||||
|
├── middleware/ Client-side middleware (RoundTripper wrappers)
|
||||||
|
├── retry/ Retry middleware with backoff
|
||||||
|
├── circuitbreaker/ Per-host circuit breaker
|
||||||
|
├── balancer/ Client-side load balancing + health checking
|
||||||
|
├── server/ Server, Router, server-side middleware, response helpers
|
||||||
|
└── internal/
|
||||||
|
├── requestid/ Shared context key (avoids circular imports)
|
||||||
|
└── clock/ Deterministic time for testing
|
||||||
|
```
|
||||||
|
|
||||||
|
## Middleware chain architecture
|
||||||
|
|
||||||
|
### Client middleware: `func(http.RoundTripper) http.RoundTripper`
|
||||||
|
|
||||||
|
```
|
||||||
|
Request flow (outermost → innermost):
|
||||||
|
|
||||||
|
Logging
|
||||||
|
└→ User Middlewares
|
||||||
|
└→ Retry
|
||||||
|
└→ Circuit Breaker
|
||||||
|
└→ Balancer
|
||||||
|
└→ Base Transport (http.DefaultTransport)
|
||||||
|
```
|
||||||
|
|
||||||
|
Retry wraps CB+Balancer so each attempt can hit a different endpoint.
|
||||||
|
|
||||||
|
### Server middleware: `func(http.Handler) http.Handler`
|
||||||
|
|
||||||
|
```
|
||||||
|
Chain(A, B, C)(handler) == A(B(C(handler)))
|
||||||
|
A is outermost (sees request first, response last)
|
||||||
|
```
|
||||||
|
|
||||||
|
Defaults() preset: `RequestID → Recovery → Logging`
|
||||||
|
|
||||||
|
## Common tasks
|
||||||
|
|
||||||
|
### Add a client middleware
|
||||||
|
|
||||||
|
1. Create file in `middleware/` (or inline)
|
||||||
|
2. Return `middleware.Middleware` (`func(http.RoundTripper) http.RoundTripper`)
|
||||||
|
3. Use `middleware.RoundTripperFunc` for the inner adapter
|
||||||
|
4. Test with `middleware.RoundTripperFunc` as mock transport
|
||||||
|
|
||||||
|
```go
|
||||||
|
func MyMiddleware() middleware.Middleware {
|
||||||
|
return func(next http.RoundTripper) http.RoundTripper {
|
||||||
|
return middleware.RoundTripperFunc(func(req *http.Request) (*http.Response, error) {
|
||||||
|
// before
|
||||||
|
resp, err := next.RoundTrip(req)
|
||||||
|
// after
|
||||||
|
return resp, err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Add a server middleware
|
||||||
|
|
||||||
|
1. Create file in `server/` named `middleware_<name>.go`
|
||||||
|
2. Return `server.Middleware` (`func(http.Handler) http.Handler`)
|
||||||
|
3. Use `server.statusWriter` if you need to capture the response status
|
||||||
|
4. Test with `httptest.NewRecorder` + `httptest.NewRequest`
|
||||||
|
|
||||||
|
```go
|
||||||
|
func MyMiddleware() Middleware {
|
||||||
|
return func(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// before
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
// after
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Add a route
|
||||||
|
|
||||||
|
```go
|
||||||
|
r := server.NewRouter()
|
||||||
|
r.HandleFunc("GET /users/{id}", getUser)
|
||||||
|
r.HandleFunc("POST /users", createUser)
|
||||||
|
|
||||||
|
// Group with prefix + middleware
|
||||||
|
api := r.Group("/api/v1", authMiddleware)
|
||||||
|
api.HandleFunc("GET /items", listItems)
|
||||||
|
|
||||||
|
// Mount sub-handler
|
||||||
|
r.Mount("/health", server.HealthHandler())
|
||||||
|
```
|
||||||
|
|
||||||
|
### Add a functional option
|
||||||
|
|
||||||
|
1. Add field to the options struct (`clientOptions` or `serverOptions`)
|
||||||
|
2. Create `With<Name>` function returning `Option`
|
||||||
|
3. Apply the field in the constructor (`New`)
|
||||||
|
|
||||||
|
## Gotchas
|
||||||
|
|
||||||
|
- **Middleware order matters**: Retry wraps CB+Balancer intentionally — each retry attempt can hit a different endpoint and a different circuit breaker
|
||||||
|
- **Circular imports via `internal/`**: Both `server` and `middleware` packages need request ID context. The shared key lives in `internal/requestid` — do NOT import `server` from `middleware` or vice versa
|
||||||
|
- **Client.Close() is required** when using `WithEndpoints()` — the balancer starts a background health checker goroutine that must be stopped
|
||||||
|
- **GetBody for retries**: Request bodies must be replayable. Use `NewJSONRequest`/`NewFormRequest` (they set `GetBody`) or set it manually
|
||||||
|
- **statusWriter.Unwrap()**: Server middleware must not type-assert `http.ResponseWriter` directly — use `http.ResponseController` which calls `Unwrap()` to find `http.Flusher`, `http.Hijacker`, etc.
|
||||||
|
- **No external deps**: This is a zero-dependency library. Do not add any `require` to `go.mod`
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go build ./... # compile
|
||||||
|
go test ./... # all tests
|
||||||
|
go test -race ./... # tests with race detector
|
||||||
|
go test -v -run TestName ./package/ # single test
|
||||||
|
go vet ./... # static analysis
|
||||||
|
```
|
||||||
|
|
||||||
|
## Conventions
|
||||||
|
|
||||||
|
- **Functional options** for all configuration (client and server)
|
||||||
|
- **stdlib only** testing — no testify, no gomock
|
||||||
|
- **Thread safety** — use `sync.Mutex`, `sync.Map`, or `atomic` where needed
|
||||||
|
- **`internal/clock`** — use for deterministic time in tests (never `time.Now()` directly in testable code)
|
||||||
|
- **Test helpers**: `mockTransport(fn)` wrapping `middleware.RoundTripperFunc` (client), `httptest.NewRecorder`/`httptest.NewRequest` (server), `waitForAddr(t, srv)` for server integration tests
|
||||||
|
- **Sentinel errors** live in sub-packages, root package re-exports as aliases
|
||||||
@@ -50,3 +50,7 @@ go vet ./... # static analysis
|
|||||||
- No external test frameworks — stdlib only
|
- No external test frameworks — stdlib only
|
||||||
- Thread safety required (`sync.Mutex`/`atomic`)
|
- Thread safety required (`sync.Mutex`/`atomic`)
|
||||||
- `internal/clock` for deterministic time testing
|
- `internal/clock` for deterministic time testing
|
||||||
|
|
||||||
|
## See also
|
||||||
|
|
||||||
|
- `AGENTS.md` — universal AI agent guide with common tasks, gotchas, and ASCII diagrams
|
||||||
|
|||||||
34
balancer/doc.go
Normal file
34
balancer/doc.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
// Package balancer provides client-side load balancing as HTTP middleware.
|
||||||
|
//
|
||||||
|
// It distributes requests across multiple backend endpoints using pluggable
|
||||||
|
// strategies (round-robin, weighted, failover) with optional health checking.
|
||||||
|
//
|
||||||
|
// # Usage
|
||||||
|
//
|
||||||
|
// mw, closer := balancer.Transport(
|
||||||
|
// []balancer.Endpoint{
|
||||||
|
// {URL: "http://backend1:8080"},
|
||||||
|
// {URL: "http://backend2:8080"},
|
||||||
|
// },
|
||||||
|
// balancer.WithStrategy(balancer.RoundRobin()),
|
||||||
|
// balancer.WithHealthCheck(5 * time.Second),
|
||||||
|
// )
|
||||||
|
// defer closer.Close()
|
||||||
|
// transport := mw(http.DefaultTransport)
|
||||||
|
//
|
||||||
|
// # Strategies
|
||||||
|
//
|
||||||
|
// - RoundRobin — cycles through healthy endpoints
|
||||||
|
// - Weighted — distributes based on endpoint Weight field
|
||||||
|
// - Failover — prefers primary, falls back to secondaries
|
||||||
|
//
|
||||||
|
// # Health checking
|
||||||
|
//
|
||||||
|
// When enabled, a background goroutine periodically probes each endpoint.
|
||||||
|
// The returned Closer must be closed to stop the health checker goroutine.
|
||||||
|
// In httpx.Client, this is handled by Client.Close().
|
||||||
|
//
|
||||||
|
// # Sentinel errors
|
||||||
|
//
|
||||||
|
// ErrNoHealthy is returned when no healthy endpoints are available.
|
||||||
|
package balancer
|
||||||
27
circuitbreaker/doc.go
Normal file
27
circuitbreaker/doc.go
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// Package circuitbreaker provides a per-host circuit breaker as HTTP middleware.
|
||||||
|
//
|
||||||
|
// The circuit breaker monitors request failures and temporarily blocks requests
|
||||||
|
// to unhealthy hosts, allowing them time to recover before retrying.
|
||||||
|
//
|
||||||
|
// # State machine
|
||||||
|
//
|
||||||
|
// - Closed — normal operation, requests pass through
|
||||||
|
// - Open — too many failures, requests are rejected with ErrCircuitOpen
|
||||||
|
// - HalfOpen — after a cooldown period, one probe request is allowed through
|
||||||
|
//
|
||||||
|
// # Usage
|
||||||
|
//
|
||||||
|
// mw := circuitbreaker.Transport(
|
||||||
|
// circuitbreaker.WithThreshold(5),
|
||||||
|
// circuitbreaker.WithTimeout(30 * time.Second),
|
||||||
|
// )
|
||||||
|
// transport := mw(http.DefaultTransport)
|
||||||
|
//
|
||||||
|
// The circuit breaker is per-host: each unique request host gets its own
|
||||||
|
// independent breaker state machine stored in a sync.Map.
|
||||||
|
//
|
||||||
|
// # Sentinel errors
|
||||||
|
//
|
||||||
|
// ErrCircuitOpen is returned when a request is rejected because the circuit
|
||||||
|
// is in the Open state.
|
||||||
|
package circuitbreaker
|
||||||
39
doc.go
Normal file
39
doc.go
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
// Package httpx provides a high-level HTTP client with composable middleware
|
||||||
|
// for retry, circuit breaking, load balancing, structured logging, and more.
|
||||||
|
//
|
||||||
|
// The client is configured via functional options and assembled as a middleware
|
||||||
|
// chain around a standard http.RoundTripper:
|
||||||
|
//
|
||||||
|
// Logging → User Middlewares → Retry → Circuit Breaker → Balancer → Transport
|
||||||
|
//
|
||||||
|
// # Quick start
|
||||||
|
//
|
||||||
|
// client := httpx.New(
|
||||||
|
// httpx.WithBaseURL("https://api.example.com"),
|
||||||
|
// httpx.WithTimeout(10 * time.Second),
|
||||||
|
// httpx.WithRetry(),
|
||||||
|
// httpx.WithCircuitBreaker(),
|
||||||
|
// )
|
||||||
|
// defer client.Close()
|
||||||
|
//
|
||||||
|
// resp, err := client.Get(ctx, "/users/1")
|
||||||
|
//
|
||||||
|
// # Request builders
|
||||||
|
//
|
||||||
|
// NewJSONRequest and NewFormRequest create requests with appropriate
|
||||||
|
// Content-Type headers and GetBody set for retry compatibility.
|
||||||
|
//
|
||||||
|
// # Error handling
|
||||||
|
//
|
||||||
|
// Failed requests return *httpx.Error with structured fields (Op, URL,
|
||||||
|
// StatusCode). Sentinel errors ErrRetryExhausted, ErrCircuitOpen, and
|
||||||
|
// ErrNoHealthy can be checked with errors.Is.
|
||||||
|
//
|
||||||
|
// # Sub-packages
|
||||||
|
//
|
||||||
|
// - middleware — client-side middleware (logging, auth, headers, recovery, request ID)
|
||||||
|
// - retry — configurable retry with backoff and Retry-After support
|
||||||
|
// - circuitbreaker — per-host circuit breaker (closed → open → half-open)
|
||||||
|
// - balancer — client-side load balancing with health checking
|
||||||
|
// - server — production HTTP server with router, middleware, and graceful shutdown
|
||||||
|
package httpx
|
||||||
28
middleware/doc.go
Normal file
28
middleware/doc.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
// Package middleware provides client-side HTTP middleware for use with
|
||||||
|
// httpx.Client or any http.RoundTripper-based transport chain.
|
||||||
|
//
|
||||||
|
// Each middleware is a function of type func(http.RoundTripper) http.RoundTripper.
|
||||||
|
// Compose them with Chain:
|
||||||
|
//
|
||||||
|
// chain := middleware.Chain(
|
||||||
|
// middleware.Logging(logger),
|
||||||
|
// middleware.Recovery(),
|
||||||
|
// middleware.UserAgent("my-service/1.0"),
|
||||||
|
// )
|
||||||
|
// transport := chain(http.DefaultTransport)
|
||||||
|
//
|
||||||
|
// # Available middleware
|
||||||
|
//
|
||||||
|
// - Logging — structured request/response logging via slog
|
||||||
|
// - Recovery — panic recovery, converts panics to errors
|
||||||
|
// - DefaultHeaders — adds default headers to outgoing requests
|
||||||
|
// - UserAgent — sets User-Agent header
|
||||||
|
// - BearerAuth — dynamic Bearer token authentication
|
||||||
|
// - BasicAuth — HTTP Basic authentication
|
||||||
|
// - RequestID — propagates request ID from context to X-Request-Id header
|
||||||
|
//
|
||||||
|
// # RoundTripperFunc
|
||||||
|
//
|
||||||
|
// RoundTripperFunc adapts plain functions to http.RoundTripper, similar to
|
||||||
|
// http.HandlerFunc. Useful for testing and inline middleware.
|
||||||
|
package middleware
|
||||||
31
retry/doc.go
Normal file
31
retry/doc.go
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
// Package retry provides configurable HTTP request retry as client middleware.
|
||||||
|
//
|
||||||
|
// The retry middleware wraps an http.RoundTripper and automatically retries
|
||||||
|
// failed requests based on a configurable policy, with exponential backoff
|
||||||
|
// and optional jitter.
|
||||||
|
//
|
||||||
|
// # Usage
|
||||||
|
//
|
||||||
|
// mw := retry.Transport(
|
||||||
|
// retry.WithMaxAttempts(3),
|
||||||
|
// retry.WithBackoff(retry.ExponentialBackoff(100*time.Millisecond, 5*time.Second)),
|
||||||
|
// )
|
||||||
|
// transport := mw(http.DefaultTransport)
|
||||||
|
//
|
||||||
|
// # Retry-After
|
||||||
|
//
|
||||||
|
// The retry middleware respects the Retry-After response header. If a server
|
||||||
|
// returns 429 or 503 with Retry-After, the delay from the header overrides
|
||||||
|
// the backoff strategy.
|
||||||
|
//
|
||||||
|
// # Request bodies
|
||||||
|
//
|
||||||
|
// For requests with bodies to be retried, the request must have GetBody set.
|
||||||
|
// Use httpx.NewJSONRequest or httpx.NewFormRequest which set GetBody
|
||||||
|
// automatically.
|
||||||
|
//
|
||||||
|
// # Sentinel errors
|
||||||
|
//
|
||||||
|
// ErrRetryExhausted is returned when all attempts fail. The original error
|
||||||
|
// is wrapped and accessible via errors.Unwrap.
|
||||||
|
package retry
|
||||||
38
server/doc.go
Normal file
38
server/doc.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
// Package server provides a production-ready HTTP server with graceful
|
||||||
|
// shutdown, middleware composition, routing, and JSON response helpers.
|
||||||
|
//
|
||||||
|
// # Server
|
||||||
|
//
|
||||||
|
// Server wraps http.Server with net.Listener, signal-based graceful shutdown
|
||||||
|
// (SIGINT/SIGTERM), and lifecycle hooks. It is configured via functional options:
|
||||||
|
//
|
||||||
|
// srv := server.New(handler,
|
||||||
|
// server.WithAddr(":8080"),
|
||||||
|
// server.Defaults(logger),
|
||||||
|
// )
|
||||||
|
// srv.ListenAndServe()
|
||||||
|
//
|
||||||
|
// # Router
|
||||||
|
//
|
||||||
|
// Router wraps http.ServeMux with middleware groups, prefix-based route groups,
|
||||||
|
// and sub-handler mounting. It supports Go 1.22+ method-based patterns:
|
||||||
|
//
|
||||||
|
// r := server.NewRouter()
|
||||||
|
// r.HandleFunc("GET /users/{id}", getUser)
|
||||||
|
//
|
||||||
|
// api := r.Group("/api/v1", authMiddleware)
|
||||||
|
// api.HandleFunc("GET /items", listItems)
|
||||||
|
//
|
||||||
|
// # Middleware
|
||||||
|
//
|
||||||
|
// Server middleware follows the func(http.Handler) http.Handler pattern.
|
||||||
|
// Available middleware: RequestID, Recovery, Logging, CORS, RateLimit,
|
||||||
|
// MaxBodySize, Timeout. Use Chain to compose them:
|
||||||
|
//
|
||||||
|
// chain := server.Chain(server.RequestID(), server.Recovery(logger), server.Logging(logger))
|
||||||
|
//
|
||||||
|
// # Response helpers
|
||||||
|
//
|
||||||
|
// WriteJSON and WriteError provide JSON response writing with proper
|
||||||
|
// Content-Type headers.
|
||||||
|
package server
|
||||||
Reference in New Issue
Block a user