errors.go 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. package client
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "fmt"
  6. "io"
  7. "io/ioutil"
  8. "net/http"
  9. "github.com/docker/distribution/registry/api/errcode"
  10. )
  11. // ErrNoErrorsInBody is returned when an HTTP response body parses to an empty
  12. // errcode.Errors slice.
  13. var ErrNoErrorsInBody = errors.New("no error details found in HTTP response body")
  14. // UnexpectedHTTPStatusError is returned when an unexpected HTTP status is
  15. // returned when making a registry api call.
  16. type UnexpectedHTTPStatusError struct {
  17. Status string
  18. }
  19. func (e *UnexpectedHTTPStatusError) Error() string {
  20. return fmt.Sprintf("received unexpected HTTP status: %s", e.Status)
  21. }
  22. // UnexpectedHTTPResponseError is returned when an expected HTTP status code
  23. // is returned, but the content was unexpected and failed to be parsed.
  24. type UnexpectedHTTPResponseError struct {
  25. ParseErr error
  26. StatusCode int
  27. Response []byte
  28. }
  29. func (e *UnexpectedHTTPResponseError) Error() string {
  30. return fmt.Sprintf("error parsing HTTP %d response body: %s: %q", e.StatusCode, e.ParseErr.Error(), string(e.Response))
  31. }
  32. func parseHTTPErrorResponse(statusCode int, r io.Reader) error {
  33. var errors errcode.Errors
  34. body, err := ioutil.ReadAll(r)
  35. if err != nil {
  36. return err
  37. }
  38. // For backward compatibility, handle irregularly formatted
  39. // messages that contain a "details" field.
  40. var detailsErr struct {
  41. Details string `json:"details"`
  42. }
  43. err = json.Unmarshal(body, &detailsErr)
  44. if err == nil && detailsErr.Details != "" {
  45. switch statusCode {
  46. case http.StatusUnauthorized:
  47. return errcode.ErrorCodeUnauthorized.WithMessage(detailsErr.Details)
  48. case http.StatusTooManyRequests:
  49. return errcode.ErrorCodeTooManyRequests.WithMessage(detailsErr.Details)
  50. default:
  51. return errcode.ErrorCodeUnknown.WithMessage(detailsErr.Details)
  52. }
  53. }
  54. if err := json.Unmarshal(body, &errors); err != nil {
  55. return &UnexpectedHTTPResponseError{
  56. ParseErr: err,
  57. StatusCode: statusCode,
  58. Response: body,
  59. }
  60. }
  61. if len(errors) == 0 {
  62. // If there was no error specified in the body, return
  63. // UnexpectedHTTPResponseError.
  64. return &UnexpectedHTTPResponseError{
  65. ParseErr: ErrNoErrorsInBody,
  66. StatusCode: statusCode,
  67. Response: body,
  68. }
  69. }
  70. return errors
  71. }
  72. // HandleErrorResponse returns error parsed from HTTP response for an
  73. // unsuccessful HTTP response code (in the range 400 - 499 inclusive). An
  74. // UnexpectedHTTPStatusError returned for response code outside of expected
  75. // range.
  76. func HandleErrorResponse(resp *http.Response) error {
  77. if resp.StatusCode == 401 {
  78. err := parseHTTPErrorResponse(resp.StatusCode, resp.Body)
  79. if uErr, ok := err.(*UnexpectedHTTPResponseError); ok {
  80. return errcode.ErrorCodeUnauthorized.WithDetail(uErr.Response)
  81. }
  82. return err
  83. }
  84. if resp.StatusCode >= 400 && resp.StatusCode < 500 {
  85. return parseHTTPErrorResponse(resp.StatusCode, resp.Body)
  86. }
  87. return &UnexpectedHTTPStatusError{Status: resp.Status}
  88. }
  89. // SuccessStatus returns true if the argument is a successful HTTP response
  90. // code (in the range 200 - 399 inclusive).
  91. func SuccessStatus(status int) bool {
  92. return status >= 200 && status <= 399
  93. }