| 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) |
| } |