// adapted from https://blog.golang.org/context httpDo func func doTimeout(cli *Client, req *http.Request, timeout time.Duration) (*http.Response, error) { // TODO: could pass in a context from further up the chain // and use that instead of context.Background() ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() // new way to cancel requests reqCancel := make(chan struct{}) req.Cancel = reqCancel type response struct { resp *http.Response err error } c := make(chan response, 1) go func() { var r response r.resp, r.err = cli.cli.Do(req) c <- r }() select { case <-ctx.Done(): // request ctx timed out. Cancel the request by closing req.Cancel channel: close(reqCancel) // wait for request to finish <-c return nil, ctx.Err() case r := <-c: // request successful return r.resp, r.err } }
func (t *transport) RoundTrip(ctx context.Context, req *http.Request, l log15.Logger) (*http.Response, error) { // http.Transport closes the request body on a failed dial, issue #875 req.Body = &fakeCloseReadCloser{req.Body} defer req.Body.(*fakeCloseReadCloser).RealClose() // hook up CloseNotify to cancel the request req.Cancel = ctx.Done() stickyBackend := t.getStickyBackend(req) backends := t.getOrderedBackends(stickyBackend, req) allTimeout := len(backends) > 0 for i, backend := range backends { req.URL.Host = backend res, err := httpTransport.RoundTrip(req) if err == nil { t.setStickyBackend(res, stickyBackend) return res, nil } if allTimeout && !strings.Contains(err.Error(), "timeout") { allTimeout = false } l.Error("retriable dial error", "backend", backend, "err", err, "attempt", i) } status, err := 503, errNoBackends if allTimeout { status, err = 504, errTimeout } l.Error("request failed", "status", status, "num_backends", len(backends)) return nil, err }
// makeReqCancel returns a closure that cancels the given http.Request // when called. func makeReqCancel(req *http.Request) func(http.RoundTripper) { c := make(chan struct{}) req.Cancel = c return func(http.RoundTripper) { close(c) } }
func (t *transport) RoundTrip(ctx context.Context, req *http.Request, l log15.Logger) (*http.Response, string, error) { // http.Transport closes the request body on a failed dial, issue #875 req.Body = &fakeCloseReadCloser{req.Body} defer req.Body.(*fakeCloseReadCloser).RealClose() // hook up CloseNotify to cancel the request req.Cancel = ctx.Done() rt := ctx.Value(ctxKeyRequestTracker).(RequestTracker) stickyBackend := t.getStickyBackend(req) backends := t.getOrderedBackends(stickyBackend) for i, backend := range backends { req.URL.Host = backend rt.TrackRequestStart(backend) res, err := httpTransport.RoundTrip(req) if err == nil { t.setStickyBackend(res, stickyBackend) return res, backend, nil } rt.TrackRequestDone(backend) if _, ok := err.(dialErr); !ok { l.Error("unretriable request error", "backend", backend, "err", err, "attempt", i) return nil, "", err } l.Error("retriable dial error", "backend", backend, "err", err, "attempt", i) } l.Error("request failed", "status", "503", "num_backends", len(backends)) return nil, "", errNoBackends }
func cancelable(client *http.Client, req *http.Request) func() { ch := make(chan struct{}) req.Cancel = ch return func() { close(ch) } }
func (t *Transport) tries(req *http.Request, try uint) (*http.Response, error) { startTime := time.Now() var timer *time.Timer rCancler := make(chan struct{}) req.Cancel = rCancler if t.RequestTimeout != 0 { timer = time.AfterFunc(t.RequestTimeout, func() { //t.CancelRequest(req) t.startOnce.Do(t.start) if bc, ok := req.Body.(*bodyCloser); ok { bc.timer.Stop() } close(rCancler) t.transport.CancelRequest(req) }) } res, err := t.transport.RoundTrip(req) headerTime := time.Now() if err != nil { if timer != nil { timer.Stop() } var stats *Stats if t.Stats != nil { stats = &Stats{ Request: req, Response: res, Error: err, } stats.Duration.Header = headerTime.Sub(startTime) stats.Retry.Count = try } if try < t.MaxTries && req.Method == "GET" && t.shouldRetryError(err) { if t.Stats != nil { stats.Retry.Pending = true t.Stats(stats) } return t.tries(req, try+1) } if t.Stats != nil { t.Stats(stats) } return nil, err } res.Body = &bodyCloser{ ReadCloser: res.Body, timer: timer, res: res, transport: t, startTime: startTime, headerTime: headerTime, } return res, nil }
func requestCanceler(tr CancelableTransport, req *http.Request) func() { ch := make(chan struct{}) req.Cancel = ch return func() { close(ch) } }
func RequestCanceler(rt http.RoundTripper, req *http.Request) func() { ch := make(chan struct{}) req.Cancel = ch return func() { close(ch) } }
func canceler(client transport.Sender, req *http.Request) func() { // TODO(djd): Respect any existing value of req.Cancel. ch := make(chan struct{}) req.Cancel = ch return func() { close(ch) } }
func (h *Hub) do(req *http.Request) (*http.Response, error) { cancel := make(chan struct{}) req.Cancel = cancel timer := time.AfterFunc(1*time.Second, func() { close(cancel) }) defer timer.Stop() res, err := http.DefaultClient.Do(req) if err != nil { return nil, err } if res.StatusCode != 200 { return nil, fmt.Errorf("powerview hub: %v", res.Status) } return res, nil }
// Wrapper around net.http.Client.Do(). // This allows a per-request timeout without setting the timeout on the Client object // directly. // The third parameter is the duration for the request itself. func doHttpWithTimeout(cli *http.Client, req *http.Request, timeout time.Duration) (resp *http.Response, err error) { if timeout.Seconds() == 0 { // No timeout resp, err = cli.Do(req) return } tmoch := make(chan struct{}) timer := time.AfterFunc(timeout, func() { tmoch <- struct{}{} }) req.Cancel = tmoch resp, err = cli.Do(req) timer.Stop() return }
// NewBodyWatcher creates a k8s body watcher for // a given http request func NewBodyWatcher(req *http.Request, client *http.Client) (Watch, error) { stop := make(chan struct{}) req.Cancel = stop res, err := client.Do(req) if err != nil { return nil, err } wr := &bodyWatcher{ results: make(chan Event), stop: stop, req: req, res: res, } go wr.stream() return wr, nil }
// doHeaderTimeout calls c.do(req) and returns its results, or // errHeaderTimeout if max elapses first. func (c *Client) doHeaderTimeout(req *http.Request, max time.Duration) (res *http.Response, err error) { type resErr struct { res *http.Response err error } if req.Cancel != nil { panic("use of Request.Cancel inside the buildlet package is reserved for doHeaderTimeout") } cancelc := make(chan struct{}) // closed to abort req.Cancel = cancelc resErrc := make(chan resErr, 1) go func() { res, err := c.do(req) resErrc <- resErr{res, err} }() timer := time.NewTimer(max) defer timer.Stop() cleanup := func() { close(cancelc) if re := <-resErrc; re.res != nil { re.res.Body.Close() } } select { case re := <-resErrc: return re.res, re.err case <-c.peerDead: go cleanup() return nil, c.deadErr case <-timer.C: go cleanup() return nil, errHeaderTimeout } }
// Do sends an HTTP request with the provided http.Client and returns an HTTP response. // If the client is nil, http.DefaultClient is used. // If the context is canceled or times out, ctx.Err() will be returned. func Do(ctx context.Context, client *http.Client, req *http.Request) (*http.Response, error) { if client == nil { client = http.DefaultClient } // TODO(djd): Respect any existing value of req.Cancel. cancel := make(chan struct{}) req.Cancel = cancel type responseAndError struct { resp *http.Response err error } result := make(chan responseAndError, 1) // Make local copies of test hooks closed over by goroutines below. // Prevents data races in tests. testHookDoReturned := testHookDoReturned testHookDidBodyClose := testHookDidBodyClose go func() { resp, err := client.Do(req) testHookDoReturned() result <- responseAndError{resp, err} }() var resp *http.Response select { case <-ctx.Done(): testHookContextDoneBeforeHeaders() close(cancel) // Clean up after the goroutine calling client.Do: go func() { if r := <-result; r.resp != nil { testHookDidBodyClose() r.resp.Body.Close() } }() return nil, ctx.Err() case r := <-result: var err error resp, err = r.resp, r.err if err != nil { return resp, err } } c := make(chan struct{}) go func() { select { case <-ctx.Done(): close(cancel) case <-c: // The response's Body is closed. } }() resp.Body = ¬ifyingReader{resp.Body, c} return resp, nil }