Если http.Client уже используется параллельными запросами, держите его поле Transport стабильным. Обёртка может синхронизировать, какой полностью настроенный транспорт получит каждый следующий round trip:

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()
}

С net/http и sync инициализируйте её до публикации клиента:

slot := &routeSlot{current: initialTransport}
client := &http.Client{Transport: slot}

slot.Set(nextTransport)

initialTransport и nextTransport должны быть не-nil, полностью настроенными и безопасными при параллелизме. Собирайте каждый прокси-транспорт до его публикации и не меняйте его конфигурацию, пока запросы его используют.

Блокировка защищает только снимок указателя, поэтому медленный запрос не удерживает блокировку маршрутизации всё время сетевого обмена. Запрос, который уже использует предыдущий снимок, продолжает на этом транспорте; переключение не перезапускает его и не переносит его соединение.

Сохраняйте и выводите старые транспорты в рамках ограниченного жизненного цикла приложения, давая текущей работе завершиться и закрывая простаивающие пулы, когда это уместно. Перенаправление может вызвать ещё один RoundTrip после переключения. Если всему рабочему процессу нужен один маршрут, назначьте его на границе рабочего процесса, а не меняйте этот общий селектор на полпути.

Похожие статьи