Files
teleport/api/breaker/round_tripper.go
T
rosstimothy fa3bb9ac59 Ensure that the webclient closes connections (#22832)
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.
2023-03-10 15:21:16 +00:00

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
}