123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145 |
- 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"
- "google.golang.org/grpc/codes"
- )
- // 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:
- statusCode = statusCodeFromGRPCError(err)
- if statusCode != http.StatusInternalServerError {
- return statusCode
- }
- // 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},
- {"repository does not exist", http.StatusNotFound},
- } {
- 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)
- }
- }
- }
- // 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:
- // codes.Canceled(1)
- // codes.Unknown(2)
- // codes.DeadlineExceeded(4)
- // codes.ResourceExhausted(8)
- // codes.Aborted(10)
- // codes.Internal(13)
- // codes.DataLoss(15)
- return http.StatusInternalServerError
- }
- }
|