errors.go 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  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 retrieves 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 _, status := range []struct {
  46. keyword string
  47. code int
  48. }{
  49. {"not found", http.StatusNotFound},
  50. {"no such", http.StatusNotFound},
  51. {"bad parameter", http.StatusBadRequest},
  52. {"no command", http.StatusBadRequest},
  53. {"conflict", http.StatusConflict},
  54. {"impossible", http.StatusNotAcceptable},
  55. {"wrong login/password", http.StatusUnauthorized},
  56. {"unauthorized", http.StatusUnauthorized},
  57. {"hasn't been activated", http.StatusForbidden},
  58. {"this node", http.StatusServiceUnavailable},
  59. } {
  60. if strings.Contains(errStr, status.keyword) {
  61. statusCode = status.code
  62. break
  63. }
  64. }
  65. }
  66. if statusCode == 0 {
  67. statusCode = http.StatusInternalServerError
  68. }
  69. return statusCode
  70. }
  71. func apiVersionSupportsJSONErrors(version string) bool {
  72. const firstAPIVersionWithJSONErrors = "1.23"
  73. return version == "" || versions.GreaterThan(version, firstAPIVersionWithJSONErrors)
  74. }
  75. // MakeErrorHandler makes an HTTP handler that decodes a Docker error and
  76. // returns it in the response.
  77. func MakeErrorHandler(err error) http.HandlerFunc {
  78. return func(w http.ResponseWriter, r *http.Request) {
  79. statusCode := GetHTTPErrorStatusCode(err)
  80. vars := mux.Vars(r)
  81. if apiVersionSupportsJSONErrors(vars["version"]) {
  82. response := &types.ErrorResponse{
  83. Message: err.Error(),
  84. }
  85. WriteJSON(w, statusCode, response)
  86. } else {
  87. http.Error(w, grpc.ErrorDesc(err), statusCode)
  88. }
  89. }
  90. }