Skip to content

Commit be3176f

Browse files
committed
use a default timeout on requests that don't specify one
This behavior was inadvertently lost in e282fb3, but is restored here, while still allowing for per-request timeouts to be specified.
1 parent e7151c3 commit be3176f

File tree

2 files changed

+66
-6
lines changed

2 files changed

+66
-6
lines changed

client/client.go

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"net/url"
1111
"reflect"
1212
"strings"
13+
"time"
1314
"unicode/utf8"
1415

1516
"github.com/tidepool-org/platform/errors"
@@ -35,6 +36,9 @@ type Client struct {
3536
address string
3637
userAgent string
3738
errorResponseParser ErrorResponseParser
39+
40+
// DefaultRequestTimeout applies to requests whose context doesn't include a timeout.
41+
DefaultRequestTimeout time.Duration
3842
}
3943

4044
func New(cfg *Config) (*Client, error) {
@@ -49,12 +53,15 @@ func NewWithErrorParser(cfg *Config, errorResponseParser ErrorResponseParser) (*
4953
}
5054

5155
return &Client{
52-
address: cfg.Address,
53-
userAgent: cfg.UserAgent,
54-
errorResponseParser: errorResponseParser,
56+
address: cfg.Address,
57+
userAgent: cfg.UserAgent,
58+
errorResponseParser: errorResponseParser,
59+
DefaultRequestTimeout: DefaultRequestTimeout,
5560
}, nil
5661
}
5762

63+
const DefaultRequestTimeout = time.Minute
64+
5865
func (c *Client) ConstructURL(paths ...string) string {
5966
segments := []string{}
6067
for _, path := range paths {
@@ -92,6 +99,14 @@ func (c *Client) RequestStreamWithHTTPClient(ctx context.Context, method string,
9299
return nil, err
93100
}
94101

102+
reqCtx := req.Context()
103+
if _, ok := reqCtx.Deadline(); !ok {
104+
toCtx, cancel := context.WithTimeout(reqCtx, c.DefaultRequestTimeout)
105+
defer cancel()
106+
req = req.WithContext(toCtx)
107+
ctx = toCtx
108+
}
109+
95110
res, err := httpClient.Do(req)
96111
if err != nil {
97112
return nil, errors.Wrapf(err, "unable to perform request to %s %s", method, url)
@@ -152,13 +167,11 @@ func (c *Client) createRequest(ctx context.Context, method string, url string, m
152167
}
153168
}
154169

155-
req, err := http.NewRequest(method, url, body)
170+
req, err := http.NewRequestWithContext(ctx, method, url, body)
156171
if err != nil {
157172
return nil, errors.Wrapf(err, "unable to create request to %s %s", method, url)
158173
}
159174

160-
req = req.WithContext(ctx)
161-
162175
for _, mutator := range mutators {
163176
if err = mutator.MutateRequest(req); err != nil {
164177
return nil, errors.Wrapf(err, "unable to mutate request to %s %s", method, url)

client/client_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"net/http"
1010
"net/url"
1111
"strings"
12+
"time"
1213

1314
. "github.com/onsi/ginkgo/v2"
1415
. "github.com/onsi/gomega"
@@ -350,6 +351,40 @@ var _ = Describe("Client", func() {
350351
Expect(reader).To(BeNil())
351352
})
352353

354+
Context("request timeouts", func() {
355+
It("aren't overwritten by the default", func() {
356+
deadline := time.Now().Add(time.Second)
357+
toCtx, cancel := context.WithDeadline(ctx, deadline)
358+
defer cancel()
359+
server.AppendHandlers(RespondWith(http.StatusNoContent, nil))
360+
361+
rc, err := clnt.RequestStreamWithHTTPClient(toCtx, method, url, nil, nil, nil, httpClient)
362+
Expect(err).To(Succeed())
363+
if rc != nil {
364+
defer rc.Close()
365+
}
366+
t, found := toCtx.Deadline()
367+
Expect(found).To(Equal(true))
368+
Expect(t).To(Equal(deadline))
369+
})
370+
371+
It("uses the default", func() {
372+
ctx, cancel := context.WithCancel(ctx)
373+
defer cancel()
374+
server.AppendHandlers(waitThenReturn(ctx, 10*time.Second))
375+
shortTimeout := 10 * time.Millisecond
376+
clnt.DefaultRequestTimeout = shortTimeout
377+
378+
start := time.Now()
379+
rc, err := clnt.RequestStreamWithHTTPClient(ctx, method, url, nil, nil, nil, httpClient)
380+
Expect(err).To(MatchError(ContainSubstring("context deadline exceeded")))
381+
if rc != nil {
382+
defer rc.Close()
383+
}
384+
Expect(time.Since(start) < 2*shortTimeout).To(Equal(true))
385+
})
386+
})
387+
353388
Context("with a successful response and no request body, but inspector returns error", func() {
354389
var responseErr error
355390
var errorInspector *requestTest.ResponseInspector
@@ -1312,3 +1347,15 @@ var _ = Describe("Client", func() {
13121347
})
13131348
})
13141349
})
1350+
1351+
func waitThenReturn(ctx context.Context, dur time.Duration) http.HandlerFunc {
1352+
return func(w http.ResponseWriter, r *http.Request) {
1353+
select {
1354+
case <-time.After(dur): // wait a while...
1355+
RespondWith(http.StatusInternalServerError, nil)
1356+
case <-ctx.Done(): // ...unless the test is ended
1357+
RespondWith(http.StatusNoContent, nil)
1358+
}
1359+
}
1360+
1361+
}

0 commit comments

Comments
 (0)