// DoRaw executes the request but does not process the response body. func (r *Request) DoRaw() ([]byte, error) { start := time.Now() defer func() { metrics.RequestLatency.WithLabelValues(r.verb, r.finalURLTemplate()).Observe(metrics.SinceInMicroseconds(start)) }() var result Result err := r.request(func(req *http.Request, resp *http.Response) { result.body, result.err = ioutil.ReadAll(resp.Body) }) if err != nil { return nil, err } return result.body, result.err }
// Do formats and executes the request. Returns a Result object for easy response // processing. // // Error type: // * If the request can't be constructed, or an error happened earlier while building its // arguments: *RequestConstructionError // * If the server responds with a status: *errors.StatusError or *errors.UnexpectedObjectError // * http.Client.Do errors are returned directly. func (r *Request) Do() Result { start := time.Now() defer func() { metrics.RequestLatency.WithLabelValues(r.verb, r.finalURLTemplate()).Observe(metrics.SinceInMicroseconds(start)) }() var result Result err := r.request(func(req *http.Request, resp *http.Response) { result = r.transformResponse(resp, req) }) if err != nil { return Result{err: err} } return result }
// request connects to the server and invokes the provided function when a server response is // received. It handles retry behavior and up front validation of requests. It will invoke // fn at most once. It will return an error if a problem occurred prior to connecting to the // server - the provided function is responsible for handling server errors. func (r *Request) request(fn func(*http.Request, *http.Response)) error { //Metrics for total request latency start := time.Now() defer func() { metrics.RequestLatency.WithLabelValues(r.verb, r.finalURLTemplate()).Observe(metrics.SinceInMicroseconds(start)) }() if r.err != nil { glog.V(4).Infof("Error in request: %v", r.err) return r.err } // TODO: added to catch programmer errors (invoking operations with an object with an empty namespace) if (r.verb == "GET" || r.verb == "PUT" || r.verb == "DELETE") && r.namespaceSet && len(r.resourceName) > 0 && len(r.namespace) == 0 { return fmt.Errorf("an empty namespace may not be set when a resource name is provided") } if (r.verb == "POST") && r.namespaceSet && len(r.namespace) == 0 { return fmt.Errorf("an empty namespace may not be set during creation") } client := r.client if client == nil { client = http.DefaultClient } // Right now we make about ten retry attempts if we get a Retry-After response. // TODO: Change to a timeout based approach. maxRetries := 10 retries := 0 for { url := r.URL().String() req, err := http.NewRequest(r.verb, url, r.body) if err != nil { return err } req.Header = r.headers r.backoffMgr.Sleep(r.backoffMgr.CalculateBackoff(r.URL())) resp, err := client.Do(req) updateURLMetrics(r, resp, err) if err != nil { r.backoffMgr.UpdateBackoff(r.URL(), err, 0) } else { r.backoffMgr.UpdateBackoff(r.URL(), err, resp.StatusCode) } if err != nil { return err } done := func() bool { // ensure the response body is closed before we reconnect, so that we reuse the same // TCP connection defer resp.Body.Close() retries++ if seconds, wait := checkWait(resp); wait && retries < maxRetries { if seeker, ok := r.body.(io.Seeker); ok && r.body != nil { _, err := seeker.Seek(0, 0) if err != nil { glog.V(4).Infof("Could not retry request, can't Seek() back to beginning of body for %T", r.body) fn(req, resp) return true } } glog.V(4).Infof("Got a Retry-After %s response for attempt %d to %v", seconds, retries, url) r.backoffMgr.Sleep(time.Duration(seconds) * time.Second) return false } fn(req, resp) return true }() if done { return nil } } }