errors.go 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. package runtime
  2. import (
  3. "context"
  4. "io"
  5. "net/http"
  6. "strings"
  7. "github.com/grpc-ecosystem/grpc-gateway/internal"
  8. "google.golang.org/grpc/codes"
  9. "google.golang.org/grpc/grpclog"
  10. "google.golang.org/grpc/status"
  11. )
  12. // HTTPStatusFromCode converts a gRPC error code into the corresponding HTTP response status.
  13. // See: https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto
  14. func HTTPStatusFromCode(code codes.Code) int {
  15. switch code {
  16. case codes.OK:
  17. return http.StatusOK
  18. case codes.Canceled:
  19. return http.StatusRequestTimeout
  20. case codes.Unknown:
  21. return http.StatusInternalServerError
  22. case codes.InvalidArgument:
  23. return http.StatusBadRequest
  24. case codes.DeadlineExceeded:
  25. return http.StatusGatewayTimeout
  26. case codes.NotFound:
  27. return http.StatusNotFound
  28. case codes.AlreadyExists:
  29. return http.StatusConflict
  30. case codes.PermissionDenied:
  31. return http.StatusForbidden
  32. case codes.Unauthenticated:
  33. return http.StatusUnauthorized
  34. case codes.ResourceExhausted:
  35. return http.StatusTooManyRequests
  36. case codes.FailedPrecondition:
  37. // Note, this deliberately doesn't translate to the similarly named '412 Precondition Failed' HTTP response status.
  38. return http.StatusBadRequest
  39. case codes.Aborted:
  40. return http.StatusConflict
  41. case codes.OutOfRange:
  42. return http.StatusBadRequest
  43. case codes.Unimplemented:
  44. return http.StatusNotImplemented
  45. case codes.Internal:
  46. return http.StatusInternalServerError
  47. case codes.Unavailable:
  48. return http.StatusServiceUnavailable
  49. case codes.DataLoss:
  50. return http.StatusInternalServerError
  51. }
  52. grpclog.Infof("Unknown gRPC error code: %v", code)
  53. return http.StatusInternalServerError
  54. }
  55. var (
  56. // HTTPError replies to the request with an error.
  57. //
  58. // HTTPError is called:
  59. // - From generated per-endpoint gateway handler code, when calling the backend results in an error.
  60. // - From gateway runtime code, when forwarding the response message results in an error.
  61. //
  62. // The default value for HTTPError calls the custom error handler configured on the ServeMux via the
  63. // WithProtoErrorHandler serve option if that option was used, calling GlobalHTTPErrorHandler otherwise.
  64. //
  65. // To customize the error handling of a particular ServeMux instance, use the WithProtoErrorHandler
  66. // serve option.
  67. //
  68. // To customize the error format for all ServeMux instances not using the WithProtoErrorHandler serve
  69. // option, set GlobalHTTPErrorHandler to a custom function.
  70. //
  71. // Setting this variable directly to customize error format is deprecated.
  72. HTTPError = MuxOrGlobalHTTPError
  73. // GlobalHTTPErrorHandler is the HTTPError handler for all ServeMux instances not using the
  74. // WithProtoErrorHandler serve option.
  75. //
  76. // You can set a custom function to this variable to customize error format.
  77. GlobalHTTPErrorHandler = DefaultHTTPError
  78. // OtherErrorHandler handles gateway errors from parsing and routing client requests for all
  79. // ServeMux instances not using the WithProtoErrorHandler serve option.
  80. //
  81. // It returns the following error codes: StatusMethodNotAllowed StatusNotFound StatusBadRequest
  82. //
  83. // To customize parsing and routing error handling of a particular ServeMux instance, use the
  84. // WithProtoErrorHandler serve option.
  85. //
  86. // To customize parsing and routing error handling of all ServeMux instances not using the
  87. // WithProtoErrorHandler serve option, set a custom function to this variable.
  88. OtherErrorHandler = DefaultOtherErrorHandler
  89. )
  90. // MuxOrGlobalHTTPError uses the mux-configured error handler, falling back to GlobalErrorHandler.
  91. func MuxOrGlobalHTTPError(ctx context.Context, mux *ServeMux, marshaler Marshaler, w http.ResponseWriter, r *http.Request, err error) {
  92. if mux.protoErrorHandler != nil {
  93. mux.protoErrorHandler(ctx, mux, marshaler, w, r, err)
  94. } else {
  95. GlobalHTTPErrorHandler(ctx, mux, marshaler, w, r, err)
  96. }
  97. }
  98. // DefaultHTTPError is the default implementation of HTTPError.
  99. // If "err" is an error from gRPC system, the function replies with the status code mapped by HTTPStatusFromCode.
  100. // If otherwise, it replies with http.StatusInternalServerError.
  101. //
  102. // The response body returned by this function is a JSON object,
  103. // which contains a member whose key is "error" and whose value is err.Error().
  104. func DefaultHTTPError(ctx context.Context, mux *ServeMux, marshaler Marshaler, w http.ResponseWriter, r *http.Request, err error) {
  105. const fallback = `{"error": "failed to marshal error message"}`
  106. s, ok := status.FromError(err)
  107. if !ok {
  108. s = status.New(codes.Unknown, err.Error())
  109. }
  110. w.Header().Del("Trailer")
  111. w.Header().Del("Transfer-Encoding")
  112. contentType := marshaler.ContentType()
  113. // Check marshaler on run time in order to keep backwards compatibility
  114. // An interface param needs to be added to the ContentType() function on
  115. // the Marshal interface to be able to remove this check
  116. if typeMarshaler, ok := marshaler.(contentTypeMarshaler); ok {
  117. pb := s.Proto()
  118. contentType = typeMarshaler.ContentTypeFromMessage(pb)
  119. }
  120. w.Header().Set("Content-Type", contentType)
  121. body := &internal.Error{
  122. Error: s.Message(),
  123. Message: s.Message(),
  124. Code: int32(s.Code()),
  125. Details: s.Proto().GetDetails(),
  126. }
  127. buf, merr := marshaler.Marshal(body)
  128. if merr != nil {
  129. grpclog.Infof("Failed to marshal error message %q: %v", body, merr)
  130. w.WriteHeader(http.StatusInternalServerError)
  131. if _, err := io.WriteString(w, fallback); err != nil {
  132. grpclog.Infof("Failed to write response: %v", err)
  133. }
  134. return
  135. }
  136. md, ok := ServerMetadataFromContext(ctx)
  137. if !ok {
  138. grpclog.Infof("Failed to extract ServerMetadata from context")
  139. }
  140. handleForwardResponseServerMetadata(w, mux, md)
  141. // RFC 7230 https://tools.ietf.org/html/rfc7230#section-4.1.2
  142. // Unless the request includes a TE header field indicating "trailers"
  143. // is acceptable, as described in Section 4.3, a server SHOULD NOT
  144. // generate trailer fields that it believes are necessary for the user
  145. // agent to receive.
  146. var wantsTrailers bool
  147. if te := r.Header.Get("TE"); strings.Contains(strings.ToLower(te), "trailers") {
  148. wantsTrailers = true
  149. handleForwardResponseTrailerHeader(w, md)
  150. w.Header().Set("Transfer-Encoding", "chunked")
  151. }
  152. st := HTTPStatusFromCode(s.Code())
  153. w.WriteHeader(st)
  154. if _, err := w.Write(buf); err != nil {
  155. grpclog.Infof("Failed to write response: %v", err)
  156. }
  157. if wantsTrailers {
  158. handleForwardResponseTrailer(w, md)
  159. }
  160. }
  161. // DefaultOtherErrorHandler is the default implementation of OtherErrorHandler.
  162. // It simply writes a string representation of the given error into "w".
  163. func DefaultOtherErrorHandler(w http.ResponseWriter, _ *http.Request, msg string, code int) {
  164. http.Error(w, msg, code)
  165. }