forked from mirror/client_golang
Assure we exhaust bodies of all HTTP responses.
Signed-off-by: bwplotka <bwplotka@gmail.com>
This commit is contained in:
parent
4c41dfbcd5
commit
db2636b0a3
|
@ -24,6 +24,8 @@ import (
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/internal/errcapture"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DefaultRoundTripper is used if no RoundTripper is set in Config.
|
// DefaultRoundTripper is used if no RoundTripper is set in Config.
|
||||||
|
@ -118,39 +120,17 @@ func (c *httpClient) URL(ep string, args map[string]string) *url.URL {
|
||||||
return &u
|
return &u
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *httpClient) Do(ctx context.Context, req *http.Request) (*http.Response, []byte, error) {
|
func (c *httpClient) Do(ctx context.Context, req *http.Request) (_ *http.Response, _ []byte, err error) {
|
||||||
if ctx != nil {
|
if ctx != nil {
|
||||||
req = req.WithContext(ctx)
|
req = req.WithContext(ctx)
|
||||||
}
|
}
|
||||||
resp, err := c.client.Do(req)
|
resp, err := c.client.Do(req)
|
||||||
defer func() {
|
|
||||||
if resp != nil {
|
|
||||||
resp.Body.Close()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
defer errcapture.ExhaustClose(&err, resp.Body, "close response body")
|
||||||
|
|
||||||
var body []byte
|
var buf bytes.Buffer
|
||||||
done := make(chan struct{})
|
_, err = buf.ReadFrom(resp.Body)
|
||||||
go func() {
|
return resp, buf.Bytes(), err
|
||||||
var buf bytes.Buffer
|
|
||||||
_, err = buf.ReadFrom(resp.Body)
|
|
||||||
body = buf.Bytes()
|
|
||||||
close(done)
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
<-done
|
|
||||||
err = resp.Body.Close()
|
|
||||||
if err == nil {
|
|
||||||
err = ctx.Err()
|
|
||||||
}
|
|
||||||
case <-done:
|
|
||||||
}
|
|
||||||
|
|
||||||
return resp, body, err
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
// Copyright 2014 The Prometheus Authors
|
||||||
|
// 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 errcapture
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
)
|
||||||
|
|
||||||
|
type doFunc func() error
|
||||||
|
|
||||||
|
// Do runs function and on error return error by argument including the given error (usually
|
||||||
|
// from caller function).
|
||||||
|
func Do(err *error, doer doFunc, format string, a ...interface{}) {
|
||||||
|
derr := doer()
|
||||||
|
if err == nil || derr == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// For os closers, it's a common case to double close.
|
||||||
|
// From reliability purpose this is not a problem it may only indicate surprising execution path.
|
||||||
|
if errors.Is(derr, os.ErrClosed) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
errs := prometheus.MultiError{}
|
||||||
|
errs.Append(*err)
|
||||||
|
errs.Append(fmt.Errorf(format+": %w", append(a, derr)...))
|
||||||
|
*err = errs
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExhaustClose closes the io.ReadCloser with error capture but exhausts the reader before.
|
||||||
|
func ExhaustClose(err *error, r io.ReadCloser, format string, a ...interface{}) {
|
||||||
|
_, copyErr := io.Copy(ioutil.Discard, r)
|
||||||
|
|
||||||
|
Do(err, r.Close, format, a...)
|
||||||
|
if copyErr == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
errs := prometheus.MultiError{}
|
||||||
|
errs.Append(copyErr)
|
||||||
|
errs.Append(*err)
|
||||||
|
*err = errs
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
// Copyright 2014 The Prometheus Authors
|
||||||
|
// 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 errcapture
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testCloser struct {
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c testCloser) Close() error {
|
||||||
|
return c.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDo(t *testing.T) {
|
||||||
|
for _, tcase := range []struct {
|
||||||
|
err error
|
||||||
|
closer io.Closer
|
||||||
|
|
||||||
|
expectedErrStr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
err: nil,
|
||||||
|
closer: testCloser{err: nil},
|
||||||
|
expectedErrStr: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
err: errors.New("test"),
|
||||||
|
closer: testCloser{err: nil},
|
||||||
|
expectedErrStr: "test",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
err: nil,
|
||||||
|
closer: testCloser{err: errors.New("test")},
|
||||||
|
expectedErrStr: "1 error(s) occurred:\n* close: test",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
err: errors.New("test"),
|
||||||
|
closer: testCloser{err: errors.New("test")},
|
||||||
|
expectedErrStr: "2 error(s) occurred:\n* test\n* close: test",
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
if ok := t.Run("", func(t *testing.T) {
|
||||||
|
ret := tcase.err
|
||||||
|
Do(&ret, tcase.closer.Close, "close")
|
||||||
|
|
||||||
|
if tcase.expectedErrStr == "" {
|
||||||
|
if ret != nil {
|
||||||
|
t.Error("Expected error to be nil")
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if ret == nil {
|
||||||
|
t.Error("Expected error to be not nil")
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
|
||||||
|
if tcase.expectedErrStr != ret.Error() {
|
||||||
|
t.Errorf("%s != %s", tcase.expectedErrStr, ret.Error())
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}); !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -48,6 +48,7 @@ import (
|
||||||
"github.com/prometheus/common/expfmt"
|
"github.com/prometheus/common/expfmt"
|
||||||
"github.com/prometheus/common/model"
|
"github.com/prometheus/common/model"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/internal/errcapture"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -228,7 +229,7 @@ func (p *Pusher) Format(format expfmt.Format) *Pusher {
|
||||||
//
|
//
|
||||||
// Delete returns the first error encountered by any method call (including this
|
// Delete returns the first error encountered by any method call (including this
|
||||||
// one) in the lifetime of the Pusher.
|
// one) in the lifetime of the Pusher.
|
||||||
func (p *Pusher) Delete() error {
|
func (p *Pusher) Delete() (err error) {
|
||||||
if p.error != nil {
|
if p.error != nil {
|
||||||
return p.error
|
return p.error
|
||||||
}
|
}
|
||||||
|
@ -243,7 +244,8 @@ func (p *Pusher) Delete() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer errcapture.ExhaustClose(&err, resp.Body, "close response body")
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusAccepted {
|
if resp.StatusCode != http.StatusAccepted {
|
||||||
body, _ := io.ReadAll(resp.Body) // Ignore any further error as this is for an error message only.
|
body, _ := io.ReadAll(resp.Body) // Ignore any further error as this is for an error message only.
|
||||||
return fmt.Errorf("unexpected status code %d while deleting %s: %s", resp.StatusCode, p.fullURL(), body)
|
return fmt.Errorf("unexpected status code %d while deleting %s: %s", resp.StatusCode, p.fullURL(), body)
|
||||||
|
@ -294,7 +296,8 @@ func (p *Pusher) push(ctx context.Context, method string) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer errcapture.ExhaustClose(&err, resp.Body, "close response body")
|
||||||
|
|
||||||
// Depending on version and configuration of the PGW, StatusOK or StatusAccepted may be returned.
|
// Depending on version and configuration of the PGW, StatusOK or StatusAccepted may be returned.
|
||||||
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusAccepted {
|
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusAccepted {
|
||||||
body, _ := io.ReadAll(resp.Body) // Ignore any further error as this is for an error message only.
|
body, _ := io.ReadAll(resp.Body) // Ignore any further error as this is for an error message only.
|
||||||
|
|
|
@ -48,6 +48,7 @@ import (
|
||||||
dto "github.com/prometheus/client_model/go"
|
dto "github.com/prometheus/client_model/go"
|
||||||
"github.com/prometheus/common/expfmt"
|
"github.com/prometheus/common/expfmt"
|
||||||
|
|
||||||
|
"github.com/prometheus/client_golang/internal/errcapture"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/prometheus/client_golang/prometheus/internal"
|
"github.com/prometheus/client_golang/prometheus/internal"
|
||||||
)
|
)
|
||||||
|
@ -158,12 +159,12 @@ func GatherAndCount(g prometheus.Gatherer, metricNames ...string) (int, error) {
|
||||||
// ScrapeAndCompare calls a remote exporter's endpoint which is expected to return some metrics in
|
// ScrapeAndCompare calls a remote exporter's endpoint which is expected to return some metrics in
|
||||||
// plain text format. Then it compares it with the results that the `expected` would return.
|
// plain text format. Then it compares it with the results that the `expected` would return.
|
||||||
// If the `metricNames` is not empty it would filter the comparison only to the given metric names.
|
// If the `metricNames` is not empty it would filter the comparison only to the given metric names.
|
||||||
func ScrapeAndCompare(url string, expected io.Reader, metricNames ...string) error {
|
func ScrapeAndCompare(url string, expected io.Reader, metricNames ...string) (err error) {
|
||||||
resp, err := http.Get(url)
|
resp, err := http.Get(url)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("scraping metrics failed: %w", err)
|
return fmt.Errorf("scraping metrics failed: %w", err)
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer errcapture.ExhaustClose(&err, resp.Body, "close response body")
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusOK {
|
if resp.StatusCode != http.StatusOK {
|
||||||
return fmt.Errorf("the scraping target returned a status code other than 200: %d",
|
return fmt.Errorf("the scraping target returned a status code other than 200: %d",
|
||||||
|
|
Loading…
Reference in New Issue