Move httputils error helpers to errdefs package

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
Sebastiaan van Stijn 2019-02-09 15:53:29 +01:00
parent ae875d4069
commit 2a9c987e5a
No known key found for this signature in database
GPG key ID: 76698F39D527CE8C
8 changed files with 86 additions and 81 deletions

View file

@ -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)
}

View file

@ -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)

View file

@ -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)

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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) {

View file

@ -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) {

View file

@ -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,
},
}