package httputils // import "github.com/docker/docker/api/server/httputils" import ( "fmt" "net/http" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/versions" "github.com/docker/docker/errdefs" "github.com/gorilla/mux" "github.com/sirupsen/logrus" "google.golang.org/grpc" "google.golang.org/grpc/codes" ) type causer interface { Cause() error } // GetHTTPErrorStatusCode retrieves status code from error message. func GetHTTPErrorStatusCode(err error) int { if err == nil { logrus.WithFields(logrus.Fields{"error": err}).Error("unexpected HTTP error handling") return http.StatusInternalServerError } var statusCode int // Stop right there // Are you sure you should be adding a new error class here? Do one of the existing ones work? // Note that the below functions are already checking the error causal chain for matches. switch { case errdefs.IsNotFound(err): statusCode = http.StatusNotFound case errdefs.IsInvalidParameter(err): statusCode = http.StatusBadRequest case errdefs.IsConflict(err) || errdefs.IsAlreadyExists(err): statusCode = http.StatusConflict case errdefs.IsUnauthorized(err): statusCode = http.StatusUnauthorized case errdefs.IsUnavailable(err): statusCode = http.StatusServiceUnavailable case errdefs.IsForbidden(err): statusCode = http.StatusForbidden case errdefs.IsNotModified(err): statusCode = http.StatusNotModified case errdefs.IsNotImplemented(err): statusCode = http.StatusNotImplemented case errdefs.IsSystem(err) || errdefs.IsUnknown(err) || errdefs.IsDataLoss(err) || errdefs.IsDeadline(err) || errdefs.IsCancelled(err): statusCode = http.StatusInternalServerError default: statusCode = statusCodeFromGRPCError(err) if statusCode != http.StatusInternalServerError { return statusCode } if e, ok := err.(causer); ok { return GetHTTPErrorStatusCode(e.Cause()) } logrus.WithFields(logrus.Fields{ "module": "api", "error_type": fmt.Sprintf("%T", err), }).Debugf("FIXME: Got an API for which error does not match any expected type!!!: %+v", err) } if statusCode == 0 { statusCode = http.StatusInternalServerError } return statusCode } func apiVersionSupportsJSONErrors(version string) bool { const firstAPIVersionWithJSONErrors = "1.23" return version == "" || versions.GreaterThan(version, firstAPIVersionWithJSONErrors) } // MakeErrorHandler makes an HTTP handler that decodes a Docker error and // returns it in the response. func MakeErrorHandler(err error) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { statusCode := GetHTTPErrorStatusCode(err) vars := mux.Vars(r) if apiVersionSupportsJSONErrors(vars["version"]) { response := &types.ErrorResponse{ Message: err.Error(), } WriteJSON(w, statusCode, response) } else { http.Error(w, grpc.ErrorDesc(err), statusCode) } } } // statusCodeFromGRPCError returns status code according to gRPC error func statusCodeFromGRPCError(err error) int { switch grpc.Code(err) { case codes.InvalidArgument: // code 3 return http.StatusBadRequest case codes.NotFound: // code 5 return http.StatusNotFound case codes.AlreadyExists: // code 6 return http.StatusConflict case codes.PermissionDenied: // code 7 return http.StatusForbidden case codes.FailedPrecondition: // code 9 return http.StatusBadRequest case codes.Unauthenticated: // code 16 return http.StatusUnauthorized case codes.OutOfRange: // code 11 return http.StatusBadRequest case codes.Unimplemented: // code 12 return http.StatusNotImplemented case codes.Unavailable: // code 14 return http.StatusServiceUnavailable default: if e, ok := err.(causer); ok { return statusCodeFromGRPCError(e.Cause()) } // codes.Canceled(1) // codes.Unknown(2) // codes.DeadlineExceeded(4) // codes.ResourceExhausted(8) // codes.Aborted(10) // codes.Internal(13) // codes.DataLoss(15) return http.StatusInternalServerError } }