diff --git a/api/server/httputils/errors_deprecated.go b/api/server/httputils/errors_deprecated.go new file mode 100644 index 0000000000..1263f02f9c --- /dev/null +++ b/api/server/httputils/errors_deprecated.go @@ -0,0 +1,9 @@ +package httputils // import "github.com/docker/docker/api/server/httputils" +import "github.com/docker/docker/errdefs" + +// GetHTTPErrorStatusCode retrieves status code from error message. +// +// Deprecated: use errdefs.GetHTTPErrorStatusCode +func GetHTTPErrorStatusCode(err error) int { + return errdefs.GetHTTPErrorStatusCode(err) +} diff --git a/api/server/httputils/httputils.go b/api/server/httputils/httputils.go index 7303814997..d344562cf7 100644 --- a/api/server/httputils/httputils.go +++ b/api/server/httputils/httputils.go @@ -7,9 +7,13 @@ import ( "net/http" "strings" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/versions" "github.com/docker/docker/errdefs" + "github.com/gorilla/mux" "github.com/pkg/errors" "github.com/sirupsen/logrus" + "google.golang.org/grpc/status" ) // APIVersionKey is the client's requested API version. @@ -88,6 +92,28 @@ func VersionFromContext(ctx context.Context) string { return "" } +// 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 := errdefs.GetHTTPErrorStatusCode(err) + vars := mux.Vars(r) + if apiVersionSupportsJSONErrors(vars["version"]) { + response := &types.ErrorResponse{ + Message: err.Error(), + } + WriteJSON(w, statusCode, response) + } else { + http.Error(w, status.Convert(err).Message(), statusCode) + } + } +} + +func apiVersionSupportsJSONErrors(version string) bool { + const firstAPIVersionWithJSONErrors = "1.23" + return version == "" || versions.GreaterThan(version, firstAPIVersionWithJSONErrors) +} + // matchesContentType validates the content type against the expected one func matchesContentType(contentType, expectedType string) bool { mimetype, _, err := mime.ParseMediaType(contentType) diff --git a/api/server/router/container/container_routes.go b/api/server/router/container/container_routes.go index 87292ffb62..45a2355c2b 100644 --- a/api/server/router/container/container_routes.go +++ b/api/server/router/container/container_routes.go @@ -589,7 +589,7 @@ func (s *containerRouter) postContainersAttach(ctx context.Context, w http.Respo // Remember to close stream if error happens conn, _, errHijack := hijacker.Hijack() if errHijack == nil { - statusCode := httputils.GetHTTPErrorStatusCode(err) + statusCode := errdefs.GetHTTPErrorStatusCode(err) statusText := http.StatusText(statusCode) fmt.Fprintf(conn, "HTTP/1.1 %d %s\r\nContent-Type: application/vnd.docker.raw-stream\r\n\r\n%s\r\n", statusCode, statusText, err.Error()) httputils.CloseStreams(conn) diff --git a/api/server/server.go b/api/server/server.go index 9c9b8fec6a..def0f9f0f0 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -12,6 +12,7 @@ import ( "github.com/docker/docker/api/server/router" "github.com/docker/docker/api/server/router/debug" "github.com/docker/docker/dockerversion" + "github.com/docker/docker/errdefs" "github.com/gorilla/mux" "github.com/sirupsen/logrus" ) @@ -139,7 +140,7 @@ func (s *Server) makeHTTPHandler(handler httputils.APIFunc) http.HandlerFunc { } if err := handlerFunc(ctx, w, r, vars); err != nil { - statusCode := httputils.GetHTTPErrorStatusCode(err) + statusCode := errdefs.GetHTTPErrorStatusCode(err) if statusCode >= 500 { logrus.Errorf("Handler for %s %s returned error: %v", r.Method, r.URL.Path, err) } diff --git a/client/ping.go b/client/ping.go index e4d9be447d..5cfadaa206 100644 --- a/client/ping.go +++ b/client/ping.go @@ -5,8 +5,8 @@ import ( "net/http" "path" - "github.com/docker/docker/api/server/httputils" "github.com/docker/docker/api/types" + "github.com/docker/docker/errdefs" ) // Ping pings the server and returns the value of the "Docker-Experimental", @@ -49,7 +49,7 @@ func parsePingResponse(cli *Client, resp serverResponse) (types.Ping, error) { var ping types.Ping if resp.header == nil { err := cli.checkResponseErr(resp) - return ping, httputils.FromStatusCode(err, resp.statusCode) + return ping, errdefs.FromStatusCode(err, resp.statusCode) } ping.APIVersion = resp.header.Get("API-Version") ping.OSType = resp.header.Get("OSType") @@ -60,5 +60,5 @@ func parsePingResponse(cli *Client, resp serverResponse) (types.Ping, error) { ping.BuilderVersion = types.BuilderVersion(bv) } err := cli.checkResponseErr(resp) - return ping, httputils.FromStatusCode(err, resp.statusCode) + return ping, errdefs.FromStatusCode(err, resp.statusCode) } diff --git a/client/request.go b/client/request.go index 86c03b066d..0afe26d588 100644 --- a/client/request.go +++ b/client/request.go @@ -13,9 +13,9 @@ import ( "os" "strings" - "github.com/docker/docker/api/server/httputils" "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/versions" + "github.com/docker/docker/errdefs" "github.com/pkg/errors" ) @@ -121,10 +121,10 @@ func (cli *Client) sendRequest(ctx context.Context, method, path string, query u } resp, err := cli.doRequest(ctx, req) if err != nil { - return resp, httputils.FromStatusCode(err, resp.statusCode) + return resp, errdefs.FromStatusCode(err, resp.statusCode) } err = cli.checkResponseErr(resp) - return resp, httputils.FromStatusCode(err, resp.statusCode) + return resp, errdefs.FromStatusCode(err, resp.statusCode) } func (cli *Client) doRequest(ctx context.Context, req *http.Request) (serverResponse, error) { diff --git a/api/server/httputils/errors.go b/errdefs/http_helpers.go similarity index 68% rename from api/server/httputils/errors.go rename to errdefs/http_helpers.go index 241cf097b3..9884eb86b9 100644 --- a/api/server/httputils/errors.go +++ b/errdefs/http_helpers.go @@ -1,23 +1,15 @@ -package httputils // import "github.com/docker/docker/api/server/httputils" +package errdefs // import "github.com/docker/docker/errdefs" import ( "fmt" "net/http" "github.com/docker/distribution/registry/api/errcode" - "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/codes" "google.golang.org/grpc/status" ) -type causer interface { - Cause() error -} - // GetHTTPErrorStatusCode retrieves status code from error message. func GetHTTPErrorStatusCode(err error) int { if err == nil { @@ -32,23 +24,23 @@ func GetHTTPErrorStatusCode(err error) int { // Note that the below functions are already checking the error causal chain for matches. switch { - case errdefs.IsNotFound(err): + case IsNotFound(err): statusCode = http.StatusNotFound - case errdefs.IsInvalidParameter(err): + case IsInvalidParameter(err): statusCode = http.StatusBadRequest - case errdefs.IsConflict(err) || errdefs.IsAlreadyExists(err): + case IsConflict(err) || IsAlreadyExists(err): statusCode = http.StatusConflict - case errdefs.IsUnauthorized(err): + case IsUnauthorized(err): statusCode = http.StatusUnauthorized - case errdefs.IsUnavailable(err): + case IsUnavailable(err): statusCode = http.StatusServiceUnavailable - case errdefs.IsForbidden(err): + case IsForbidden(err): statusCode = http.StatusForbidden - case errdefs.IsNotModified(err): + case IsNotModified(err): statusCode = http.StatusNotModified - case errdefs.IsNotImplemented(err): + case IsNotImplemented(err): statusCode = http.StatusNotImplemented - case errdefs.IsSystem(err) || errdefs.IsUnknown(err) || errdefs.IsDataLoss(err) || errdefs.IsDeadline(err) || errdefs.IsCancelled(err): + case IsSystem(err) || IsUnknown(err) || IsDataLoss(err) || IsDeadline(err) || IsCancelled(err): statusCode = http.StatusInternalServerError default: statusCode = statusCodeFromGRPCError(err) @@ -76,31 +68,31 @@ func GetHTTPErrorStatusCode(err error) int { return statusCode } -// FromStatusCode creates an errdef error, based on the provided status-code +// FromStatusCode creates an errdef error, based on the provided HTTP status-code func FromStatusCode(err error, statusCode int) error { if err == nil { return err } switch statusCode { case http.StatusNotFound: - err = errdefs.NotFound(err) + err = NotFound(err) case http.StatusBadRequest: - err = errdefs.InvalidParameter(err) + err = InvalidParameter(err) case http.StatusConflict: - err = errdefs.Conflict(err) + err = Conflict(err) case http.StatusUnauthorized: - err = errdefs.Unauthorized(err) + err = Unauthorized(err) case http.StatusServiceUnavailable: - err = errdefs.Unavailable(err) + err = Unavailable(err) case http.StatusForbidden: - err = errdefs.Forbidden(err) + err = Forbidden(err) case http.StatusNotModified: - err = errdefs.NotModified(err) + err = NotModified(err) case http.StatusNotImplemented: - err = errdefs.NotImplemented(err) + err = NotImplemented(err) case http.StatusInternalServerError: - if !errdefs.IsSystem(err) && !errdefs.IsUnknown(err) && !errdefs.IsDataLoss(err) && !errdefs.IsDeadline(err) && !errdefs.IsCancelled(err) { - err = errdefs.System(err) + if !IsSystem(err) && !IsUnknown(err) && !IsDataLoss(err) && !IsDeadline(err) && !IsCancelled(err) { + err = System(err) } default: logrus.WithFields(logrus.Fields{ @@ -112,38 +104,16 @@ func FromStatusCode(err error, statusCode int) error { case statusCode >= 200 && statusCode < 400: // it's a client error case statusCode >= 400 && statusCode < 500: - err = errdefs.InvalidParameter(err) + err = InvalidParameter(err) case statusCode >= 500 && statusCode < 600: - err = errdefs.System(err) + err = System(err) default: - err = errdefs.Unknown(err) + err = Unknown(err) } } return err } -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, status.Convert(err).Message(), statusCode) - } - } -} - // statusCodeFromGRPCError returns status code according to gRPC error func statusCodeFromGRPCError(err error) int { switch status.Code(err) { diff --git a/api/server/httputils/errors_test.go b/errdefs/http_helpers_test.go similarity index 66% rename from api/server/httputils/errors_test.go rename to errdefs/http_helpers_test.go index f67a39213a..4ae17e4ef1 100644 --- a/api/server/httputils/errors_test.go +++ b/errdefs/http_helpers_test.go @@ -1,11 +1,10 @@ -package httputils +package errdefs import ( "fmt" "net/http" "testing" - "github.com/docker/docker/errdefs" "gotest.tools/assert" ) @@ -20,67 +19,67 @@ func TestFromStatusCode(t *testing.T) { { err: testErr, status: http.StatusNotFound, - check: errdefs.IsNotFound, + check: IsNotFound, }, { err: testErr, status: http.StatusBadRequest, - check: errdefs.IsInvalidParameter, + check: IsInvalidParameter, }, { err: testErr, status: http.StatusConflict, - check: errdefs.IsConflict, + check: IsConflict, }, { err: testErr, status: http.StatusUnauthorized, - check: errdefs.IsUnauthorized, + check: IsUnauthorized, }, { err: testErr, status: http.StatusServiceUnavailable, - check: errdefs.IsUnavailable, + check: IsUnavailable, }, { err: testErr, status: http.StatusForbidden, - check: errdefs.IsForbidden, + check: IsForbidden, }, { err: testErr, status: http.StatusNotModified, - check: errdefs.IsNotModified, + check: IsNotModified, }, { err: testErr, status: http.StatusNotImplemented, - check: errdefs.IsNotImplemented, + check: IsNotImplemented, }, { err: testErr, status: http.StatusInternalServerError, - check: errdefs.IsSystem, + check: IsSystem, }, { - err: errdefs.Unknown(testErr), + err: Unknown(testErr), status: http.StatusInternalServerError, - check: errdefs.IsUnknown, + check: IsUnknown, }, { - err: errdefs.DataLoss(testErr), + err: DataLoss(testErr), status: http.StatusInternalServerError, - check: errdefs.IsDataLoss, + check: IsDataLoss, }, { - err: errdefs.Deadline(testErr), + err: Deadline(testErr), status: http.StatusInternalServerError, - check: errdefs.IsDeadline, + check: IsDeadline, }, { - err: errdefs.Cancelled(testErr), + err: Cancelled(testErr), status: http.StatusInternalServerError, - check: errdefs.IsCancelled, + check: IsCancelled, }, }