+ var cancel context.CancelFunc
+ if c.Timeout > 0 {
+ ctx, cancel = context.WithDeadline(ctx, time.Now().Add(c.Timeout))
+ req = req.WithContext(ctx)
+ } else {
+ cancel = context.CancelFunc(func() {})
+ }
+
+ c.requestLimiter.Acquire(ctx)
+ if ctx.Err() != nil {
+ c.requestLimiter.Release()
+ return nil, ctx.Err()
+ }
+
+ // Attach Release() to cancel func, see cancelOnClose below.
+ cancelOrig := cancel
+ cancel = func() {
+ c.requestLimiter.Release()
+ cancelOrig()
+ }
+
+ resp, err := c.httpClient().Do(req)
+ if c.requestLimiter.Report(resp, err) {
+ c.last503.Store(time.Now())
+ }
+ if err == nil {
+ // We need to call cancel() eventually, but we can't
+ // use "defer cancel()" because the context has to
+ // stay alive until the caller has finished reading
+ // the response body.
+ resp.Body = cancelOnClose{ReadCloser: resp.Body, cancel: cancel}
+ } else {
+ cancel()
+ }
+ return resp, err
+}
+
+// Last503 returns the time of the most recent HTTP 503 (Service
+// Unavailable) response. Zero time indicates never.
+func (c *Client) Last503() time.Time {
+ t, _ := c.last503.Load().(time.Time)
+ return t
+}
+
+// cancelOnClose calls a provided CancelFunc when its wrapped
+// ReadCloser's Close() method is called.
+type cancelOnClose struct {
+ io.ReadCloser
+ cancel context.CancelFunc
+}
+
+func (coc cancelOnClose) Close() error {
+ err := coc.ReadCloser.Close()
+ coc.cancel()
+ return err
+}
+
+func isRedirectStatus(code int) bool {
+ switch code {
+ case http.StatusMovedPermanently, http.StatusFound, http.StatusSeeOther, http.StatusTemporaryRedirect, http.StatusPermanentRedirect:
+ return true
+ default:
+ return false
+ }