errors.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. package httputils // import "github.com/docker/docker/api/server/httputils"
  2. import (
  3. "fmt"
  4. "net/http"
  5. "github.com/docker/distribution/registry/api/errcode"
  6. "github.com/docker/docker/api/types"
  7. "github.com/docker/docker/api/types/versions"
  8. "github.com/docker/docker/errdefs"
  9. "github.com/gorilla/mux"
  10. "github.com/sirupsen/logrus"
  11. "google.golang.org/grpc/codes"
  12. "google.golang.org/grpc/status"
  13. )
  14. type causer interface {
  15. Cause() error
  16. }
  17. // GetHTTPErrorStatusCode retrieves status code from error message.
  18. func GetHTTPErrorStatusCode(err error) int {
  19. if err == nil {
  20. logrus.WithFields(logrus.Fields{"error": err}).Error("unexpected HTTP error handling")
  21. return http.StatusInternalServerError
  22. }
  23. var statusCode int
  24. // Stop right there
  25. // Are you sure you should be adding a new error class here? Do one of the existing ones work?
  26. // Note that the below functions are already checking the error causal chain for matches.
  27. switch {
  28. case errdefs.IsNotFound(err):
  29. statusCode = http.StatusNotFound
  30. case errdefs.IsInvalidParameter(err):
  31. statusCode = http.StatusBadRequest
  32. case errdefs.IsConflict(err) || errdefs.IsAlreadyExists(err):
  33. statusCode = http.StatusConflict
  34. case errdefs.IsUnauthorized(err):
  35. statusCode = http.StatusUnauthorized
  36. case errdefs.IsUnavailable(err):
  37. statusCode = http.StatusServiceUnavailable
  38. case errdefs.IsForbidden(err):
  39. statusCode = http.StatusForbidden
  40. case errdefs.IsNotModified(err):
  41. statusCode = http.StatusNotModified
  42. case errdefs.IsNotImplemented(err):
  43. statusCode = http.StatusNotImplemented
  44. case errdefs.IsSystem(err) || errdefs.IsUnknown(err) || errdefs.IsDataLoss(err) || errdefs.IsDeadline(err) || errdefs.IsCancelled(err):
  45. statusCode = http.StatusInternalServerError
  46. default:
  47. statusCode = statusCodeFromGRPCError(err)
  48. if statusCode != http.StatusInternalServerError {
  49. return statusCode
  50. }
  51. statusCode = statusCodeFromDistributionError(err)
  52. if statusCode != http.StatusInternalServerError {
  53. return statusCode
  54. }
  55. if e, ok := err.(causer); ok {
  56. return GetHTTPErrorStatusCode(e.Cause())
  57. }
  58. logrus.WithFields(logrus.Fields{
  59. "module": "api",
  60. "error_type": fmt.Sprintf("%T", err),
  61. }).Debugf("FIXME: Got an API for which error does not match any expected type!!!: %+v", err)
  62. }
  63. if statusCode == 0 {
  64. statusCode = http.StatusInternalServerError
  65. }
  66. return statusCode
  67. }
  68. // FromStatusCode creates an errdef error, based on the provided status-code
  69. func FromStatusCode(err error, statusCode int) error {
  70. if err == nil {
  71. return err
  72. }
  73. switch statusCode {
  74. case http.StatusNotFound:
  75. err = errdefs.NotFound(err)
  76. case http.StatusBadRequest:
  77. err = errdefs.InvalidParameter(err)
  78. case http.StatusConflict:
  79. err = errdefs.Conflict(err)
  80. case http.StatusUnauthorized:
  81. err = errdefs.Unauthorized(err)
  82. case http.StatusServiceUnavailable:
  83. err = errdefs.Unavailable(err)
  84. case http.StatusForbidden:
  85. err = errdefs.Forbidden(err)
  86. case http.StatusNotModified:
  87. err = errdefs.NotModified(err)
  88. case http.StatusNotImplemented:
  89. err = errdefs.NotImplemented(err)
  90. case http.StatusInternalServerError:
  91. if !errdefs.IsSystem(err) && !errdefs.IsUnknown(err) && !errdefs.IsDataLoss(err) && !errdefs.IsDeadline(err) && !errdefs.IsCancelled(err) {
  92. err = errdefs.System(err)
  93. }
  94. default:
  95. logrus.WithFields(logrus.Fields{
  96. "module": "api",
  97. "status_code": fmt.Sprintf("%d", statusCode),
  98. }).Debugf("FIXME: Got an status-code for which error does not match any expected type!!!: %d", statusCode)
  99. switch {
  100. case statusCode >= 200 && statusCode < 400:
  101. // it's a client error
  102. case statusCode >= 400 && statusCode < 500:
  103. err = errdefs.InvalidParameter(err)
  104. case statusCode >= 500 && statusCode < 600:
  105. err = errdefs.System(err)
  106. default:
  107. err = errdefs.Unknown(err)
  108. }
  109. }
  110. return err
  111. }
  112. func apiVersionSupportsJSONErrors(version string) bool {
  113. const firstAPIVersionWithJSONErrors = "1.23"
  114. return version == "" || versions.GreaterThan(version, firstAPIVersionWithJSONErrors)
  115. }
  116. // MakeErrorHandler makes an HTTP handler that decodes a Docker error and
  117. // returns it in the response.
  118. func MakeErrorHandler(err error) http.HandlerFunc {
  119. return func(w http.ResponseWriter, r *http.Request) {
  120. statusCode := GetHTTPErrorStatusCode(err)
  121. vars := mux.Vars(r)
  122. if apiVersionSupportsJSONErrors(vars["version"]) {
  123. response := &types.ErrorResponse{
  124. Message: err.Error(),
  125. }
  126. WriteJSON(w, statusCode, response)
  127. } else {
  128. http.Error(w, status.Convert(err).Message(), statusCode)
  129. }
  130. }
  131. }
  132. // statusCodeFromGRPCError returns status code according to gRPC error
  133. func statusCodeFromGRPCError(err error) int {
  134. switch status.Code(err) {
  135. case codes.InvalidArgument: // code 3
  136. return http.StatusBadRequest
  137. case codes.NotFound: // code 5
  138. return http.StatusNotFound
  139. case codes.AlreadyExists: // code 6
  140. return http.StatusConflict
  141. case codes.PermissionDenied: // code 7
  142. return http.StatusForbidden
  143. case codes.FailedPrecondition: // code 9
  144. return http.StatusBadRequest
  145. case codes.Unauthenticated: // code 16
  146. return http.StatusUnauthorized
  147. case codes.OutOfRange: // code 11
  148. return http.StatusBadRequest
  149. case codes.Unimplemented: // code 12
  150. return http.StatusNotImplemented
  151. case codes.Unavailable: // code 14
  152. return http.StatusServiceUnavailable
  153. default:
  154. if e, ok := err.(causer); ok {
  155. return statusCodeFromGRPCError(e.Cause())
  156. }
  157. // codes.Canceled(1)
  158. // codes.Unknown(2)
  159. // codes.DeadlineExceeded(4)
  160. // codes.ResourceExhausted(8)
  161. // codes.Aborted(10)
  162. // codes.Internal(13)
  163. // codes.DataLoss(15)
  164. return http.StatusInternalServerError
  165. }
  166. }
  167. // statusCodeFromDistributionError returns status code according to registry errcode
  168. // code is loosely based on errcode.ServeJSON() in docker/distribution
  169. func statusCodeFromDistributionError(err error) int {
  170. switch errs := err.(type) {
  171. case errcode.Errors:
  172. if len(errs) < 1 {
  173. return http.StatusInternalServerError
  174. }
  175. if _, ok := errs[0].(errcode.ErrorCoder); ok {
  176. return statusCodeFromDistributionError(errs[0])
  177. }
  178. case errcode.ErrorCoder:
  179. return errs.ErrorCode().Descriptor().HTTPStatusCode
  180. default:
  181. if e, ok := err.(causer); ok {
  182. return statusCodeFromDistributionError(e.Cause())
  183. }
  184. }
  185. return http.StatusInternalServerError
  186. }