Switch Go HTTP Proxy Routes Without Mutating Client.Transport
Use a synchronized RoundTripper wrapper to publish a new configured proxy transport without mutating a live http.Client field.
If an http.Client is already shared by concurrent requests, keep its Transport field stable. A wrapper can synchronize which fully configured transport each later round trip receives:
type routeSlot struct {
mu sync.RWMutex
current http.RoundTripper
}
func (s *routeSlot) RoundTrip(r *http.Request) (*http.Response, error) {
s.mu.RLock()
current := s.current
s.mu.RUnlock()
return current.RoundTrip(r)
}
func (s *routeSlot) Set(next http.RoundTripper) {
s.mu.Lock()
s.current = next
s.mu.Unlock()
}
Using net/http and sync, initialize it before publishing the client:
slot := &routeSlot{current: initialTransport}
client := &http.Client{Transport: slot}
slot.Set(nextTransport)
initialTransport and nextTransport must be non-nil, fully configured, concurrency-safe round trippers. Build each proxy transport before publishing it, and leave its configuration unchanged while requests use it.
The lock protects only the pointer snapshot, so a slow request does not hold the routing lock for its entire network exchange. A request already using the previous snapshot continues on that transport; the switch does not replay it or move its connection.
Retain and retire old transports through the application’s bounded lifecycle, allowing in-flight work to finish and closing idle pools when appropriate. A redirect can cause another RoundTrip after a switch. If an entire workflow requires one route, assign that route at the workflow boundary instead of changing this shared selector midway through it.