// Stream formats and executes the request, and offers streaming of the response. // Returns io.ReadCloser which could be used for streaming of the response, or an error // Any non-2xx http status code causes an error. If we get a non-2xx code, we try to convert the body into an APIStatus object. // If we can, we return that as an error. Otherwise, we create an error that lists the http status and the content of the response. func (r *Request) Stream() (io.ReadCloser, error) { if r.err != nil { return nil, r.err } url := r.URL().String() req, err := http.NewRequest(r.verb, url, nil) if err != nil { return nil, err } client := r.client if client == nil { client = http.DefaultClient } time.Sleep(r.backoffMgr.CalculateBackoff(r.URL())) resp, err := client.Do(req) updateURLMetrics(r, resp, err) if r.baseURL != nil { if err != nil { r.backoffMgr.UpdateBackoff(r.URL(), err, 0) } else { r.backoffMgr.UpdateBackoff(r.URL(), err, resp.StatusCode) } } if err != nil { return nil, err } switch { case (resp.StatusCode >= 200) && (resp.StatusCode < 300): return resp.Body, nil default: // ensure we close the body before returning the error defer resp.Body.Close() // we have a decent shot at taking the object returned, parsing it as a status object and returning a more normal error bodyBytes, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("%v while accessing %v", resp.Status, url) } if runtimeObject, err := r.codec.Decode(bodyBytes); err == nil { statusError := errors.FromObject(runtimeObject) if _, ok := statusError.(errors.APIStatus); ok { return nil, statusError } } bodyText := string(bodyBytes) return nil, fmt.Errorf("%s while accessing %v: %s", resp.Status, url, bodyText) } }
// transformResponse converts an API response into a structured API object func (r *Request) transformResponse(resp *http.Response, req *http.Request) Result { var body []byte if resp.Body != nil { if data, err := ioutil.ReadAll(resp.Body); err == nil { body = data } } glog.V(8).Infof("Response Body: %s", string(body)) // Did the server give us a status response? isStatusResponse := false var status unversioned.Status if err := r.codec.DecodeInto(body, &status); err == nil && status.Status != "" { isStatusResponse = true } switch { case resp.StatusCode == http.StatusSwitchingProtocols: // no-op, we've been upgraded case resp.StatusCode < http.StatusOK || resp.StatusCode > http.StatusPartialContent: if !isStatusResponse { return Result{err: r.transformUnstructuredResponseError(resp, req, body)} } return Result{err: errors.FromObject(&status)} } // If the server gave us a status back, look at what it was. success := resp.StatusCode >= http.StatusOK && resp.StatusCode <= http.StatusPartialContent if isStatusResponse && (status.Status != unversioned.StatusSuccess && !success) { // "Failed" requests are clearly just an error and it makes sense to return them as such. return Result{err: errors.FromObject(&status)} } return Result{ body: body, statusCode: resp.StatusCode, codec: r.codec, } }