mirror of
https://github.com/gravitational/teleport.git
synced 2026-11-02 14:59:53 +08:00
The `http.Client.Transport` created in `newWebClient` wraps a chain of `http.RoundTripper` over an underlying `http.Transport`. Since we cannot guarantee that each `http.RoundTipper` has a `CloseIdleConnections` method the usage of `defer clt.CloseIdleConnections()` does not guarantee that the http connections created during Find/Ping/etc are closed. To prevent leaking connections implementations of `http.RoundTripper` have added a `CloseIdleConnections` method added that forwards the request on to the wrapped interface. The `otelhttp.Transport` does not implement this method either so care has been taken to wrap it in a `http.RoundTripper` which will call the root transport in `enforceCloseIdleConnections`. An upstream issue has been filed with otelhttp: https://github.com/open-telemetry/opentelemetry-go-contrib/issues/3543 to get the method added to their `Transport` implementation. This wasn't noticed prior to upgrading to go1.20 becuase prior to [this](https://github.com/golang/go/commit/4e7e7ae1406c70d9cc0809ec11105a55a60a0b70) commit the `ReadHeaderTimeout` set on the Proxy web api http.Server would cause the connection to appear idle and get terminated by the server. `TestWebClientClosesIdleConnections` was added to capture the leak in connections as reported in #22757 and prevent any regressions.
62 lines
1.8 KiB
Go
62 lines
1.8 KiB
Go
// Copyright 2022 Gravitational, Inc
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package breaker
|
|
|
|
import (
|
|
"net/http"
|
|
)
|
|
|
|
// RoundTripper wraps a http.RoundTripper with a CircuitBreaker
|
|
type RoundTripper struct {
|
|
tripper http.RoundTripper
|
|
cb *CircuitBreaker
|
|
}
|
|
|
|
// NewRoundTripper returns a RoundTripper
|
|
func NewRoundTripper(cb *CircuitBreaker, tripper http.RoundTripper) *RoundTripper {
|
|
return &RoundTripper{
|
|
tripper: tripper,
|
|
cb: cb,
|
|
}
|
|
}
|
|
|
|
// CloseIdleConnections ensures idle connections of the wrapped
|
|
// [http.RoundTripper] are closed.
|
|
func (t *RoundTripper) CloseIdleConnections() {
|
|
type closeIdler interface {
|
|
CloseIdleConnections()
|
|
}
|
|
if tr, ok := t.tripper.(closeIdler); ok {
|
|
tr.CloseIdleConnections()
|
|
}
|
|
}
|
|
|
|
// RoundTrip forwards the request on to the provided http.RoundTripper if
|
|
// the CircuitBreaker allows it
|
|
func (t *RoundTripper) RoundTrip(request *http.Request) (*http.Response, error) {
|
|
v, err := t.cb.Execute(func() (interface{}, error) {
|
|
//nolint:bodyclose // The interface{} conversion to *http.Response trips the linter even though this
|
|
// is merely a pass through function. Closing the body here would prevent the actual
|
|
// consumer to not be able to read it. Copying here to satisfy the linter seems wasteful.
|
|
return t.tripper.RoundTrip(request)
|
|
})
|
|
|
|
if v == nil {
|
|
return nil, err
|
|
}
|
|
|
|
return v.(*http.Response), err
|
|
}
|