errors.go 2.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  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. )
  11. // httpStatusError is an interface
  12. // that errors with custom status codes
  13. // implement to tell the api layer
  14. // which response status to set.
  15. type httpStatusError interface {
  16. HTTPErrorStatusCode() int
  17. }
  18. // inputValidationError is an interface
  19. // that errors generated by invalid
  20. // inputs can implement to tell the
  21. // api layer to set a 400 status code
  22. // in the response.
  23. type inputValidationError interface {
  24. IsValidationError() bool
  25. }
  26. // GetHTTPErrorStatusCode retrieve status code from error message
  27. func GetHTTPErrorStatusCode(err error) int {
  28. if err == nil {
  29. logrus.WithFields(logrus.Fields{"error": err}).Error("unexpected HTTP error handling")
  30. return http.StatusInternalServerError
  31. }
  32. var statusCode int
  33. errMsg := err.Error()
  34. switch e := err.(type) {
  35. case httpStatusError:
  36. statusCode = e.HTTPErrorStatusCode()
  37. case inputValidationError:
  38. statusCode = http.StatusBadRequest
  39. default:
  40. // FIXME: this is brittle and should not be necessary, but we still need to identify if
  41. // there are errors falling back into this logic.
  42. // If we need to differentiate between different possible error types,
  43. // we should create appropriate error types that implement the httpStatusError interface.
  44. errStr := strings.ToLower(errMsg)
  45. for keyword, status := range map[string]int{
  46. "not found": http.StatusNotFound,
  47. "no such": http.StatusNotFound,
  48. "bad parameter": http.StatusBadRequest,
  49. "no command": http.StatusBadRequest,
  50. "conflict": http.StatusConflict,
  51. "impossible": http.StatusNotAcceptable,
  52. "wrong login/password": http.StatusUnauthorized,
  53. "unauthorized": http.StatusUnauthorized,
  54. "hasn't been activated": http.StatusForbidden,
  55. "this node": http.StatusNotAcceptable,
  56. } {
  57. if strings.Contains(errStr, keyword) {
  58. statusCode = status
  59. break
  60. }
  61. }
  62. }
  63. if statusCode == 0 {
  64. statusCode = http.StatusInternalServerError
  65. }
  66. return statusCode
  67. }
  68. func apiVersionSupportsJSONErrors(version string) bool {
  69. const firstAPIVersionWithJSONErrors = "1.23"
  70. return version == "" || versions.GreaterThan(version, firstAPIVersionWithJSONErrors)
  71. }
  72. // MakeErrorHandler makes an HTTP handler that decodes a Docker error and
  73. // returns it in the response.
  74. func MakeErrorHandler(err error) http.HandlerFunc {
  75. return func(w http.ResponseWriter, r *http.Request) {
  76. statusCode := GetHTTPErrorStatusCode(err)
  77. vars := mux.Vars(r)
  78. if apiVersionSupportsJSONErrors(vars["version"]) {
  79. response := &types.ErrorResponse{
  80. Message: err.Error(),
  81. }
  82. WriteJSON(w, statusCode, response)
  83. } else {
  84. http.Error(w, grpc.ErrorDesc(err), statusCode)
  85. }
  86. }
  87. }