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
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Implementing http.RoundTripper lets you wrap request behavior transparently around any client.
- 2Cloning the request before mutating it keeps retries safe and avoids sharing state with the caller.
- 3Honoring the request context during backoff means slow retries cancel cleanly instead of blocking.
Related explainers
go
package config import ( "fmt"
Loading typed config from environment variables in Go
configuration
environment-variables
type-parsing
Beginner
8 steps
go
package middleware import ( "crypto/subtle"
Building an API-key middleware in Gin
middleware
authentication
dependency-injection
Intermediate
5 steps
go
package api func RegisterRoutes(r *gin.Engine, deps *Dependencies) { r.GET("/health", func(c *gin.Context) {
Layering route groups and middleware in Gin
routing
middleware
authentication
Intermediate
8 steps
go
package payments import ( "context"
Fetching a charge over HTTP in Go
context-timeout
http-client
json-decoding
Intermediate
6 steps
javascript
const express = require('express'); const compression = require('compression'); const zlib = require('zlib');
Tuning Express response compression
compression
middleware
http
Intermediate
9 steps
go
package scheduler import ( "context"
A context-aware heartbeat ticker in Go
concurrency
channels
context
Intermediate
5 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/a-retrying-http-transport-in-go-explained-go-c42a/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.