http.Client がすでに並行リクエストで共有されている場合は、その Transport フィールドを安定させたままにします。ラッパーは、以降の各ラウンドトリップがどの完全に設定されたトランスポートを受け取るかを同期できます:

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 を引き起こすことがあります。ワークフロー全体が 1 つの経路を必要とする場合は、この共有セレクタを途中で変更するのではなく、ワークフローの境界でその経路を割り当ててください。

関連記事