Documentation
¶
Overview ¶
Package hostrate provides an http.RoundTripper that rate-limits outbound HTTP requests on a per-host basis (or by any custom key), so a client stays polite to each remote server independently rather than sharing one global budget across all hosts.
Each distinct key gets its own golang.org/x/time/rate.Limiter, created lazily on first use. The default key is the request's host, so traffic to api.a.com is limited separately from api.b.com.
Basic use:
client := hostrate.NewClient(2, 5) // 2 req/s, burst 5, per host
resp, err := client.Get("https://example.com/feed.xml")
Composing with a custom base transport (for example, tuned connection pooling) and keying:
t := hostrate.New(myTransport, rate.Limit(2), 5,
hostrate.WithKeyFunc(hostrate.KeyByHostPort),
hostrate.WithIdleTimeout(10*time.Minute), // bound memory for unbounded hosts
)
client := &http.Client{Transport: t}
A Transport blocks each request until its key's limiter permits the call or the request's context is done, so request deadlines and cancellation are honored while waiting.
Example ¶
Example shows the convenience constructor: an http.Client that allows at most 2 requests per second to each host, bursting up to 5.
package main
import (
"fmt"
"io"
"log"
"net/http"
"net/http/httptest"
"github.com/richardwooding/hostrate"
)
func main() {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
fmt.Fprint(w, "hello")
}))
defer srv.Close()
client := hostrate.NewClient(2, 5)
resp, err := client.Get(srv.URL)
if err != nil {
log.Fatal(err)
}
defer func() { _ = resp.Body.Close() }()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
Output: hello
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func KeyByHost ¶
KeyByHost keys requests by their lowercased host without port. It is the default KeyFunc.
func KeyByHostPort ¶
KeyByHostPort keys requests by their lowercased host including port, so the same hostname on different ports is limited independently.
func NewClient ¶
NewClient is a convenience constructor returning an http.Client whose Transport rate-limits to rps requests per second per host (with the given burst), wrapping http.DefaultTransport. For a custom base transport — for example, tuned connection pooling — build a Transport with New instead.
Types ¶
type KeyFunc ¶
KeyFunc derives the rate-limiting key for a request. Requests that map to the same key share a single token-bucket limiter.
type Option ¶
type Option func(*Transport)
Option configures a Transport passed to New.
func WithIdleTimeout ¶
WithIdleTimeout enables eviction of per-key limiters that have not been used for at least d. This bounds memory when the set of keys is effectively unbounded (for example, a crawler reaching arbitrarily many hosts).
The default is zero, which disables eviction and retains one limiter per key for the Transport's lifetime — appropriate when the set of hosts is small and known. A non-positive d disables eviction.
func WithKeyFunc ¶
WithKeyFunc sets the function used to derive each request's limiter key. The default is KeyByHost. A nil KeyFunc is ignored.
type Transport ¶
type Transport struct {
// contains filtered or unexported fields
}
Transport is an http.RoundTripper that applies a per-key token-bucket rate limit before delegating to a base RoundTripper. A separate limiter is created lazily for each distinct key (by default, each target host).
The zero value is not usable; construct a Transport with New. A Transport is safe for concurrent use by multiple goroutines.
func New ¶
New returns a Transport that limits requests sharing a key to limit requests per second with the given burst, delegating to base. If base is nil, http.DefaultTransport is used.
limit is a golang.org/x/time/rate.Limit: use rate.Limit(n) for n requests per second, rate.Every(d) for one request per d, or rate.Inf to disable limiting. burst is the maximum number of requests allowed to proceed at once before throttling begins.
Example ¶
ExampleNew shows composing the Transport over a custom base transport, with a custom key and idle eviction to bound memory when reaching many hosts.
package main
import (
"fmt"
"net/http"
"time"
"golang.org/x/time/rate"
"github.com/richardwooding/hostrate"
)
func main() {
base := &http.Transport{MaxConnsPerHost: 10}
transport := hostrate.New(base, rate.Limit(2), 5,
hostrate.WithKeyFunc(hostrate.KeyByHostPort),
hostrate.WithIdleTimeout(10*time.Minute),
)
client := &http.Client{Transport: transport}
_ = client // use client for outbound requests
fmt.Println("configured")
}
Output: configured
func (*Transport) Len ¶
Len reports the number of per-key limiters currently tracked. It is primarily useful for observability and tests.
func (*Transport) RoundTrip ¶
RoundTrip implements http.RoundTripper. It blocks until the limiter for the request's key permits the call or the request's context is done, then delegates to the base RoundTripper. If the context is canceled or its deadline is exceeded while waiting, RoundTrip returns that error and does not call the base RoundTripper.