// transformUnstructuredResponseError handles an error from the server that is not in a structured form. // It is expected to transform any response that is not recognizable as a clear server sent error from the // K8S API using the information provided with the request. In practice, HTTP proxies and client libraries // introduce a level of uncertainty to the responses returned by servers that in common use result in // unexpected responses. The rough structure is: // // 1. Assume the server sends you something sane - JSON + well defined error objects + proper codes // - this is the happy path // - when you get this output, trust what the server sends // 2. Guard against empty fields / bodies in received JSON and attempt to cull sufficient info from them to // generate a reasonable facsimile of the original failure. // - Be sure to use a distinct error type or flag that allows a client to distinguish between this and error 1 above // 3. Handle true disconnect failures / completely malformed data by moving up to a more generic client error // 4. Distinguish between various connection failures like SSL certificates, timeouts, proxy errors, unexpected // initial contact, the presence of mismatched body contents from posted content types // - Give these a separate distinct error type and capture as much as possible of the original message // // TODO: introduce transformation of generic http.Client.Do() errors that separates 4. func (r *Request) transformUnstructuredResponseError(resp *http.Response, req *http.Request, body []byte) error { if body == nil && resp.Body != nil { if data, err := ioutil.ReadAll(resp.Body); err == nil { body = data } } glog.V(8).Infof("Response Body: %s", string(body)) message := "unknown" if isTextResponse(resp) { message = strings.TrimSpace(string(body)) } retryAfter, _ := retryAfterSeconds(resp) return errors.NewGenericServerResponse( resp.StatusCode, req.Method, unversioned.GroupResource{ Group: r.content.GroupVersion.Group, Resource: r.resource, }, r.resourceName, message, retryAfter, true, ) }
func serviceErrorHandler(s runtime.NegotiatedSerializer, serviceErr restful.ServiceError, request *restful.Request, response *restful.Response) { errorNegotiated( apierrors.NewGenericServerResponse(serviceErr.Code, "", api.Resource(""), "", serviceErr.Message, 0, false), s, schema.GroupVersion{}, response.ResponseWriter, request.Request, ) }
func (checker GenericHttpResponseChecker) Check(resp *http.Response) error { if resp.StatusCode < http.StatusOK || resp.StatusCode > http.StatusPartialContent { defer resp.Body.Close() bodyBytes, err := ioutil.ReadAll(io.LimitReader(resp.Body, maxReadLength)) if err != nil { return errors.NewInternalError(err) } bodyText := string(bodyBytes) switch { case resp.StatusCode == http.StatusInternalServerError: return errors.NewInternalError(fmt.Errorf("%s", bodyText)) case resp.StatusCode == http.StatusBadRequest: return errors.NewBadRequest(bodyText) case resp.StatusCode == http.StatusNotFound: return errors.NewGenericServerResponse(resp.StatusCode, "", checker.QualifiedResource, checker.Name, bodyText, 0, false) } return errors.NewGenericServerResponse(resp.StatusCode, "", checker.QualifiedResource, checker.Name, bodyText, 0, false) } return nil }
//TODO: Unify with RecoverPanics? func logStackOnRecover(panicReason interface{}, httpWriter http.ResponseWriter) { var buffer bytes.Buffer buffer.WriteString(fmt.Sprintf("recover from panic situation: - %v\r\n", panicReason)) for i := 2; ; i += 1 { _, file, line, ok := rt.Caller(i) if !ok { break } buffer.WriteString(fmt.Sprintf(" %s:%d\r\n", file, line)) } glog.Errorln(buffer.String()) // TODO: make status unversioned or plumb enough of the request to deduce the requested API version errorJSON(apierrors.NewGenericServerResponse(http.StatusInternalServerError, "", "", "", "", 0, false), latest.GroupOrDie("").Codec, httpWriter) }
func serviceErrorHandler(requestResolver *APIRequestInfoResolver, apiVersions []string, serviceErr restful.ServiceError, request *restful.Request, response *restful.Response) { requestInfo, err := requestResolver.GetAPIRequestInfo(request.Request) codec := latest.GroupOrDie("").Codec if err == nil && requestInfo.APIVersion != "" { // check if the api version is valid. for _, version := range apiVersions { if requestInfo.APIVersion == version { // valid api version. codec = runtime.CodecFor(api.Scheme, requestInfo.APIVersion) break } } } errorJSON(apierrors.NewGenericServerResponse(serviceErr.Code, "", "", "", "", 0, false), codec, response.ResponseWriter) }
//TODO: Unify with RecoverPanics? func logStackOnRecover(s runtime.NegotiatedSerializer, panicReason interface{}, w http.ResponseWriter) { var buffer bytes.Buffer buffer.WriteString(fmt.Sprintf("recover from panic situation: - %v\r\n", panicReason)) for i := 2; ; i += 1 { _, file, line, ok := rt.Caller(i) if !ok { break } buffer.WriteString(fmt.Sprintf(" %s:%d\r\n", file, line)) } glog.Errorln(buffer.String()) headers := http.Header{} if ct := w.Header().Get("Content-Type"); len(ct) > 0 { headers.Set("Accept", ct) } errorNegotiated(apierrors.NewGenericServerResponse(http.StatusInternalServerError, "", api.Resource(""), "", "", 0, false), s, unversioned.GroupVersion{}, w, &http.Request{Header: headers}) }
// newUnstructuredResponseError instantiates the appropriate generic error for the provided input. It also logs the body. func (r *Request) newUnstructuredResponseError(body []byte, isTextResponse bool, statusCode int, method string, retryAfter int) error { // cap the amount of output we create if len(body) > maxUnstructuredResponseTextBytes { body = body[:maxUnstructuredResponseTextBytes] } message := "unknown" if isTextResponse { message = strings.TrimSpace(string(body)) } return errors.NewGenericServerResponse( statusCode, method, schema.GroupResource{ Group: r.content.GroupVersion.Group, Resource: r.resource, }, r.resourceName, message, retryAfter, true, ) }
func serviceErrorHandler(s runtime.NegotiatedSerializer, requestResolver *RequestInfoResolver, apiVersions []string, serviceErr restful.ServiceError, request *restful.Request, response *restful.Response) { errorNegotiated(apierrors.NewGenericServerResponse(serviceErr.Code, "", api.Resource(""), "", "", 0, false), s, unversioned.GroupVersion{}, response.ResponseWriter, request.Request) }