123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186 |
- package runtime
- import (
- "context"
- "io"
- "net/http"
- "strings"
- "github.com/grpc-ecosystem/grpc-gateway/internal"
- "google.golang.org/grpc/codes"
- "google.golang.org/grpc/grpclog"
- "google.golang.org/grpc/status"
- )
- // HTTPStatusFromCode converts a gRPC error code into the corresponding HTTP response status.
- // See: https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto
- func HTTPStatusFromCode(code codes.Code) int {
- switch code {
- case codes.OK:
- return http.StatusOK
- case codes.Canceled:
- return http.StatusRequestTimeout
- case codes.Unknown:
- return http.StatusInternalServerError
- case codes.InvalidArgument:
- return http.StatusBadRequest
- case codes.DeadlineExceeded:
- return http.StatusGatewayTimeout
- case codes.NotFound:
- return http.StatusNotFound
- case codes.AlreadyExists:
- return http.StatusConflict
- case codes.PermissionDenied:
- return http.StatusForbidden
- case codes.Unauthenticated:
- return http.StatusUnauthorized
- case codes.ResourceExhausted:
- return http.StatusTooManyRequests
- case codes.FailedPrecondition:
- // Note, this deliberately doesn't translate to the similarly named '412 Precondition Failed' HTTP response status.
- return http.StatusBadRequest
- case codes.Aborted:
- return http.StatusConflict
- case codes.OutOfRange:
- return http.StatusBadRequest
- case codes.Unimplemented:
- return http.StatusNotImplemented
- case codes.Internal:
- return http.StatusInternalServerError
- case codes.Unavailable:
- return http.StatusServiceUnavailable
- case codes.DataLoss:
- return http.StatusInternalServerError
- }
- grpclog.Infof("Unknown gRPC error code: %v", code)
- return http.StatusInternalServerError
- }
- var (
- // HTTPError replies to the request with an error.
- //
- // HTTPError is called:
- // - From generated per-endpoint gateway handler code, when calling the backend results in an error.
- // - From gateway runtime code, when forwarding the response message results in an error.
- //
- // The default value for HTTPError calls the custom error handler configured on the ServeMux via the
- // WithProtoErrorHandler serve option if that option was used, calling GlobalHTTPErrorHandler otherwise.
- //
- // To customize the error handling of a particular ServeMux instance, use the WithProtoErrorHandler
- // serve option.
- //
- // To customize the error format for all ServeMux instances not using the WithProtoErrorHandler serve
- // option, set GlobalHTTPErrorHandler to a custom function.
- //
- // Setting this variable directly to customize error format is deprecated.
- HTTPError = MuxOrGlobalHTTPError
- // GlobalHTTPErrorHandler is the HTTPError handler for all ServeMux instances not using the
- // WithProtoErrorHandler serve option.
- //
- // You can set a custom function to this variable to customize error format.
- GlobalHTTPErrorHandler = DefaultHTTPError
- // OtherErrorHandler handles gateway errors from parsing and routing client requests for all
- // ServeMux instances not using the WithProtoErrorHandler serve option.
- //
- // It returns the following error codes: StatusMethodNotAllowed StatusNotFound StatusBadRequest
- //
- // To customize parsing and routing error handling of a particular ServeMux instance, use the
- // WithProtoErrorHandler serve option.
- //
- // To customize parsing and routing error handling of all ServeMux instances not using the
- // WithProtoErrorHandler serve option, set a custom function to this variable.
- OtherErrorHandler = DefaultOtherErrorHandler
- )
- // MuxOrGlobalHTTPError uses the mux-configured error handler, falling back to GlobalErrorHandler.
- func MuxOrGlobalHTTPError(ctx context.Context, mux *ServeMux, marshaler Marshaler, w http.ResponseWriter, r *http.Request, err error) {
- if mux.protoErrorHandler != nil {
- mux.protoErrorHandler(ctx, mux, marshaler, w, r, err)
- } else {
- GlobalHTTPErrorHandler(ctx, mux, marshaler, w, r, err)
- }
- }
- // DefaultHTTPError is the default implementation of HTTPError.
- // If "err" is an error from gRPC system, the function replies with the status code mapped by HTTPStatusFromCode.
- // If otherwise, it replies with http.StatusInternalServerError.
- //
- // The response body returned by this function is a JSON object,
- // which contains a member whose key is "error" and whose value is err.Error().
- func DefaultHTTPError(ctx context.Context, mux *ServeMux, marshaler Marshaler, w http.ResponseWriter, r *http.Request, err error) {
- const fallback = `{"error": "failed to marshal error message"}`
- s, ok := status.FromError(err)
- if !ok {
- s = status.New(codes.Unknown, err.Error())
- }
- w.Header().Del("Trailer")
- w.Header().Del("Transfer-Encoding")
- contentType := marshaler.ContentType()
- // Check marshaler on run time in order to keep backwards compatibility
- // An interface param needs to be added to the ContentType() function on
- // the Marshal interface to be able to remove this check
- if typeMarshaler, ok := marshaler.(contentTypeMarshaler); ok {
- pb := s.Proto()
- contentType = typeMarshaler.ContentTypeFromMessage(pb)
- }
- w.Header().Set("Content-Type", contentType)
- body := &internal.Error{
- Error: s.Message(),
- Message: s.Message(),
- Code: int32(s.Code()),
- Details: s.Proto().GetDetails(),
- }
- buf, merr := marshaler.Marshal(body)
- if merr != nil {
- grpclog.Infof("Failed to marshal error message %q: %v", body, merr)
- w.WriteHeader(http.StatusInternalServerError)
- if _, err := io.WriteString(w, fallback); err != nil {
- grpclog.Infof("Failed to write response: %v", err)
- }
- return
- }
- md, ok := ServerMetadataFromContext(ctx)
- if !ok {
- grpclog.Infof("Failed to extract ServerMetadata from context")
- }
- handleForwardResponseServerMetadata(w, mux, md)
- // RFC 7230 https://tools.ietf.org/html/rfc7230#section-4.1.2
- // Unless the request includes a TE header field indicating "trailers"
- // is acceptable, as described in Section 4.3, a server SHOULD NOT
- // generate trailer fields that it believes are necessary for the user
- // agent to receive.
- var wantsTrailers bool
- if te := r.Header.Get("TE"); strings.Contains(strings.ToLower(te), "trailers") {
- wantsTrailers = true
- handleForwardResponseTrailerHeader(w, md)
- w.Header().Set("Transfer-Encoding", "chunked")
- }
- st := HTTPStatusFromCode(s.Code())
- w.WriteHeader(st)
- if _, err := w.Write(buf); err != nil {
- grpclog.Infof("Failed to write response: %v", err)
- }
- if wantsTrailers {
- handleForwardResponseTrailer(w, md)
- }
- }
- // DefaultOtherErrorHandler is the default implementation of OtherErrorHandler.
- // It simply writes a string representation of the given error into "w".
- func DefaultOtherErrorHandler(w http.ResponseWriter, _ *http.Request, msg string, code int) {
- http.Error(w, msg, code)
- }
|