package httputils import ( "net/http" "strings" "github.com/Sirupsen/logrus" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/versions" "github.com/gorilla/mux" "google.golang.org/grpc" ) // httpStatusError is an interface // that errors with custom status codes // implement to tell the api layer // which response status to set. type httpStatusError interface { HTTPErrorStatusCode() int } // inputValidationError is an interface // that errors generated by invalid // inputs can implement to tell the // api layer to set a 400 status code // in the response. type inputValidationError interface { IsValidationError() bool } // 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 errMsg := err.Error() switch e := err.(type) { case httpStatusError: statusCode = e.HTTPErrorStatusCode() case inputValidationError: statusCode = http.StatusBadRequest default: // FIXME: this is brittle and should not be necessary, but we still need to identify if // there are errors falling back into this logic. // If we need to differentiate between different possible error types, // we should create appropriate error types that implement the httpStatusError interface. errStr := strings.ToLower(errMsg) for _, status := range []struct { keyword string code int }{ {"not found", http.StatusNotFound}, {"cannot find", http.StatusNotFound}, {"no such", http.StatusNotFound}, {"bad parameter", http.StatusBadRequest}, {"no command", http.StatusBadRequest}, {"conflict", http.StatusConflict}, {"impossible", http.StatusNotAcceptable}, {"wrong login/password", http.StatusUnauthorized}, {"unauthorized", http.StatusUnauthorized}, {"hasn't been activated", http.StatusForbidden}, {"this node", http.StatusServiceUnavailable}, {"needs to be unlocked", http.StatusServiceUnavailable}, {"certificates have expired", http.StatusServiceUnavailable}, } { if strings.Contains(errStr, status.keyword) { statusCode = status.code break } } } 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) } } }