errors.go 4.2 KB

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