go 51 lines · 8 steps

A retrying HTTP transport in Go

A custom http.RoundTripper that injects auth headers and retries failed requests with exponential backoff.

Explained by highlit
1package httpx
2 
3import (
4 "net/http"
5 "time"
6)
7 
8type AuthRetryTransport struct {
9 Base http.RoundTripper
10 Token string
11 MaxRetries int
12}
13 
14func (t *AuthRetryTransport) base() http.RoundTripper {
15 if t.Base != nil {
16 return t.Base
17 }
18 return http.DefaultTransport
19}
20 
21func (t *AuthRetryTransport) RoundTrip(req *http.Request) (*http.Response, error) {
22 clone := req.Clone(req.Context())
23 if t.Token != "" {
24 clone.Header.Set("Authorization", "Bearer "+t.Token)
25 }
26 clone.Header.Set("User-Agent", "acme-client/1.0")
27 
28 var resp *http.Response
29 var err error
30 backoff := 200 * time.Millisecond
31 
32 for attempt := 0; attempt <= t.MaxRetries; attempt++ {
33 resp, err = t.base().RoundTrip(clone)
34 if err == nil && resp.StatusCode < 500 {
35 return resp, nil
36 }
37 if resp != nil {
38 resp.Body.Close()
39 }
40 if attempt == t.MaxRetries {
41 break
42 }
43 select {
44 case <-time.After(backoff):
45 backoff *= 2
46 case <-req.Context().Done():
47 return nil, req.Context().Err()
48 }
49 }
50 return resp, err
51}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Implementing http.RoundTripper lets you wrap request behavior transparently around any client.
  2. 2Cloning the request before mutating it keeps retries safe and avoids sharing state with the caller.
  3. 3Honoring the request context during backoff means slow retries cancel cleanly instead of blocking.

Related explainers

Share this explainer

Here's the card — post it anywhere.

A retrying HTTP transport in Go — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code