github.com/fabiokung/docker@v0.11.2-0.20170222101415-4534dcd49497/api/server/httputils/errors.go (about) 1 package httputils 2 3 import ( 4 "net/http" 5 "strings" 6 7 "github.com/Sirupsen/logrus" 8 "github.com/docker/docker/api/types" 9 "github.com/docker/docker/api/types/versions" 10 "github.com/gorilla/mux" 11 "google.golang.org/grpc" 12 ) 13 14 // httpStatusError is an interface 15 // that errors with custom status codes 16 // implement to tell the api layer 17 // which response status to set. 18 type httpStatusError interface { 19 HTTPErrorStatusCode() int 20 } 21 22 // inputValidationError is an interface 23 // that errors generated by invalid 24 // inputs can implement to tell the 25 // api layer to set a 400 status code 26 // in the response. 27 type inputValidationError interface { 28 IsValidationError() bool 29 } 30 31 // GetHTTPErrorStatusCode retrieves status code from error message. 32 func GetHTTPErrorStatusCode(err error) int { 33 if err == nil { 34 logrus.WithFields(logrus.Fields{"error": err}).Error("unexpected HTTP error handling") 35 return http.StatusInternalServerError 36 } 37 38 var statusCode int 39 errMsg := err.Error() 40 41 switch e := err.(type) { 42 case httpStatusError: 43 statusCode = e.HTTPErrorStatusCode() 44 case inputValidationError: 45 statusCode = http.StatusBadRequest 46 default: 47 // FIXME: this is brittle and should not be necessary, but we still need to identify if 48 // there are errors falling back into this logic. 49 // If we need to differentiate between different possible error types, 50 // we should create appropriate error types that implement the httpStatusError interface. 51 errStr := strings.ToLower(errMsg) 52 for _, status := range []struct { 53 keyword string 54 code int 55 }{ 56 {"not found", http.StatusNotFound}, 57 {"cannot find", http.StatusNotFound}, 58 {"no such", http.StatusNotFound}, 59 {"bad parameter", http.StatusBadRequest}, 60 {"no command", http.StatusBadRequest}, 61 {"conflict", http.StatusConflict}, 62 {"impossible", http.StatusNotAcceptable}, 63 {"wrong login/password", http.StatusUnauthorized}, 64 {"unauthorized", http.StatusUnauthorized}, 65 {"hasn't been activated", http.StatusForbidden}, 66 {"this node", http.StatusServiceUnavailable}, 67 {"needs to be unlocked", http.StatusServiceUnavailable}, 68 {"certificates have expired", http.StatusServiceUnavailable}, 69 } { 70 if strings.Contains(errStr, status.keyword) { 71 statusCode = status.code 72 break 73 } 74 } 75 } 76 77 if statusCode == 0 { 78 statusCode = http.StatusInternalServerError 79 } 80 81 return statusCode 82 } 83 84 func apiVersionSupportsJSONErrors(version string) bool { 85 const firstAPIVersionWithJSONErrors = "1.23" 86 return version == "" || versions.GreaterThan(version, firstAPIVersionWithJSONErrors) 87 } 88 89 // MakeErrorHandler makes an HTTP handler that decodes a Docker error and 90 // returns it in the response. 91 func MakeErrorHandler(err error) http.HandlerFunc { 92 return func(w http.ResponseWriter, r *http.Request) { 93 statusCode := GetHTTPErrorStatusCode(err) 94 vars := mux.Vars(r) 95 if apiVersionSupportsJSONErrors(vars["version"]) { 96 response := &types.ErrorResponse{ 97 Message: err.Error(), 98 } 99 WriteJSON(w, statusCode, response) 100 } else { 101 http.Error(w, grpc.ErrorDesc(err), statusCode) 102 } 103 } 104 }