errors.go 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. package runtime
  2. import (
  3. "context"
  4. "errors"
  5. "io"
  6. "net/http"
  7. "google.golang.org/grpc/codes"
  8. "google.golang.org/grpc/grpclog"
  9. "google.golang.org/grpc/status"
  10. )
  11. // ErrorHandlerFunc is the signature used to configure error handling.
  12. type ErrorHandlerFunc func(context.Context, *ServeMux, Marshaler, http.ResponseWriter, *http.Request, error)
  13. // StreamErrorHandlerFunc is the signature used to configure stream error handling.
  14. type StreamErrorHandlerFunc func(context.Context, error) *status.Status
  15. // RoutingErrorHandlerFunc is the signature used to configure error handling for routing errors.
  16. type RoutingErrorHandlerFunc func(context.Context, *ServeMux, Marshaler, http.ResponseWriter, *http.Request, int)
  17. // HTTPStatusError is the error to use when needing to provide a different HTTP status code for an error
  18. // passed to the DefaultRoutingErrorHandler.
  19. type HTTPStatusError struct {
  20. HTTPStatus int
  21. Err error
  22. }
  23. func (e *HTTPStatusError) Error() string {
  24. return e.Err.Error()
  25. }
  26. // HTTPStatusFromCode converts a gRPC error code into the corresponding HTTP response status.
  27. // See: https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto
  28. func HTTPStatusFromCode(code codes.Code) int {
  29. switch code {
  30. case codes.OK:
  31. return http.StatusOK
  32. case codes.Canceled:
  33. return 499
  34. case codes.Unknown:
  35. return http.StatusInternalServerError
  36. case codes.InvalidArgument:
  37. return http.StatusBadRequest
  38. case codes.DeadlineExceeded:
  39. return http.StatusGatewayTimeout
  40. case codes.NotFound:
  41. return http.StatusNotFound
  42. case codes.AlreadyExists:
  43. return http.StatusConflict
  44. case codes.PermissionDenied:
  45. return http.StatusForbidden
  46. case codes.Unauthenticated:
  47. return http.StatusUnauthorized
  48. case codes.ResourceExhausted:
  49. return http.StatusTooManyRequests
  50. case codes.FailedPrecondition:
  51. // Note, this deliberately doesn't translate to the similarly named '412 Precondition Failed' HTTP response status.
  52. return http.StatusBadRequest
  53. case codes.Aborted:
  54. return http.StatusConflict
  55. case codes.OutOfRange:
  56. return http.StatusBadRequest
  57. case codes.Unimplemented:
  58. return http.StatusNotImplemented
  59. case codes.Internal:
  60. return http.StatusInternalServerError
  61. case codes.Unavailable:
  62. return http.StatusServiceUnavailable
  63. case codes.DataLoss:
  64. return http.StatusInternalServerError
  65. default:
  66. grpclog.Infof("Unknown gRPC error code: %v", code)
  67. return http.StatusInternalServerError
  68. }
  69. }
  70. // HTTPError uses the mux-configured error handler.
  71. func HTTPError(ctx context.Context, mux *ServeMux, marshaler Marshaler, w http.ResponseWriter, r *http.Request, err error) {
  72. mux.errorHandler(ctx, mux, marshaler, w, r, err)
  73. }
  74. // DefaultHTTPErrorHandler is the default error handler.
  75. // If "err" is a gRPC Status, the function replies with the status code mapped by HTTPStatusFromCode.
  76. // If "err" is a HTTPStatusError, the function replies with the status code provide by that struct. This is
  77. // intended to allow passing through of specific statuses via the function set via WithRoutingErrorHandler
  78. // for the ServeMux constructor to handle edge cases which the standard mappings in HTTPStatusFromCode
  79. // are insufficient for.
  80. // If otherwise, it replies with http.StatusInternalServerError.
  81. //
  82. // The response body written by this function is a Status message marshaled by the Marshaler.
  83. func DefaultHTTPErrorHandler(ctx context.Context, mux *ServeMux, marshaler Marshaler, w http.ResponseWriter, r *http.Request, err error) {
  84. // return Internal when Marshal failed
  85. const fallback = `{"code": 13, "message": "failed to marshal error message"}`
  86. var customStatus *HTTPStatusError
  87. if errors.As(err, &customStatus) {
  88. err = customStatus.Err
  89. }
  90. s := status.Convert(err)
  91. pb := s.Proto()
  92. w.Header().Del("Trailer")
  93. w.Header().Del("Transfer-Encoding")
  94. contentType := marshaler.ContentType(pb)
  95. w.Header().Set("Content-Type", contentType)
  96. if s.Code() == codes.Unauthenticated {
  97. w.Header().Set("WWW-Authenticate", s.Message())
  98. }
  99. buf, merr := marshaler.Marshal(pb)
  100. if merr != nil {
  101. grpclog.Infof("Failed to marshal error message %q: %v", s, merr)
  102. w.WriteHeader(http.StatusInternalServerError)
  103. if _, err := io.WriteString(w, fallback); err != nil {
  104. grpclog.Infof("Failed to write response: %v", err)
  105. }
  106. return
  107. }
  108. md, ok := ServerMetadataFromContext(ctx)
  109. if !ok {
  110. grpclog.Infof("Failed to extract ServerMetadata from context")
  111. }
  112. handleForwardResponseServerMetadata(w, mux, md)
  113. // RFC 7230 https://tools.ietf.org/html/rfc7230#section-4.1.2
  114. // Unless the request includes a TE header field indicating "trailers"
  115. // is acceptable, as described in Section 4.3, a server SHOULD NOT
  116. // generate trailer fields that it believes are necessary for the user
  117. // agent to receive.
  118. doForwardTrailers := requestAcceptsTrailers(r)
  119. if doForwardTrailers {
  120. handleForwardResponseTrailerHeader(w, md)
  121. w.Header().Set("Transfer-Encoding", "chunked")
  122. }
  123. st := HTTPStatusFromCode(s.Code())
  124. if customStatus != nil {
  125. st = customStatus.HTTPStatus
  126. }
  127. w.WriteHeader(st)
  128. if _, err := w.Write(buf); err != nil {
  129. grpclog.Infof("Failed to write response: %v", err)
  130. }
  131. if doForwardTrailers {
  132. handleForwardResponseTrailer(w, md)
  133. }
  134. }
  135. func DefaultStreamErrorHandler(_ context.Context, err error) *status.Status {
  136. return status.Convert(err)
  137. }
  138. // DefaultRoutingErrorHandler is our default handler for routing errors.
  139. // By default http error codes mapped on the following error codes:
  140. //
  141. // NotFound -> grpc.NotFound
  142. // StatusBadRequest -> grpc.InvalidArgument
  143. // MethodNotAllowed -> grpc.Unimplemented
  144. // Other -> grpc.Internal, method is not expecting to be called for anything else
  145. func DefaultRoutingErrorHandler(ctx context.Context, mux *ServeMux, marshaler Marshaler, w http.ResponseWriter, r *http.Request, httpStatus int) {
  146. sterr := status.Error(codes.Internal, "Unexpected routing error")
  147. switch httpStatus {
  148. case http.StatusBadRequest:
  149. sterr = status.Error(codes.InvalidArgument, http.StatusText(httpStatus))
  150. case http.StatusMethodNotAllowed:
  151. sterr = status.Error(codes.Unimplemented, http.StatusText(httpStatus))
  152. case http.StatusNotFound:
  153. sterr = status.Error(codes.NotFound, http.StatusText(httpStatus))
  154. }
  155. mux.errorHandler(ctx, mux, marshaler, w, r, sterr)
  156. }