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