Pebble is a small, opinionated wrapper around net/http with the things you find yourself reimplementing on every team: retries, timeouts, tracing, and structured errors. About 600 lines of code and one dependency.
$ go get github.com/example/pebble@latest
Exponential backoff with jitter, configurable per-request. Only retries on the response codes that make sense to retry on.
Separate dial, TLS handshake, and request timeouts. No more http.Get hanging on a misconfigured load balancer.
OpenTelemetry spans for every request, with the relevant attributes. Optional, off by default.
Errors that tell you what failed and where. No more wrapping io.EOF in three layers of "unexpected error reading response."
Per-host connection limits, idle timeouts, and graceful shutdown. The things you remember after one outage.
Configuration is explicit. Defaults are documented. There are no global state changes. Read the code, it's short.
The smallest useful program with Pebble:
package main
import (
"context"
"fmt"
"log"
"github.com/example/pebble"
)
func main() {
client := pebble.New(pebble.Options{
Timeout: pebble.DefaultTimeout,
MaxRetries: 3,
})
resp, err := client.Get(context.Background(), "https://api.example.com/v1/health")
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
fmt.Println("Status:", resp.StatusCode)
}
That's it. Sensible behaviour out of the box, with knobs available when you need them.