package runtime import ( "context" "io" "net/http" "github.com/golang/protobuf/proto" "github.com/golang/protobuf/ptypes/any" "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: return http.StatusPreconditionFailed 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 the error. // You can set a custom function to this variable to customize error format. HTTPError = DefaultHTTPError // OtherErrorHandler handles the following error used by the gateway: StatusMethodNotAllowed StatusNotFound and StatusBadRequest OtherErrorHandler = DefaultOtherErrorHandler ) type errorBody struct { Error string `protobuf:"bytes,1,name=error" json:"error"` // This is to make the error more compatible with users that expect errors to be Status objects: // https://github.com/grpc/grpc/blob/master/src/proto/grpc/status/status.proto // It should be the exact same message as the Error field. Message string `protobuf:"bytes,1,name=message" json:"message"` Code int32 `protobuf:"varint,2,name=code" json:"code"` Details []*any.Any `protobuf:"bytes,3,rep,name=details" json:"details,omitempty"` } // Make this also conform to proto.Message for builtin JSONPb Marshaler func (e *errorBody) Reset() { *e = errorBody{} } func (e *errorBody) String() string { return proto.CompactTextString(e) } func (*errorBody) ProtoMessage() {} // 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, _ *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") contentType := marshaler.ContentType() // Check marshaler on run time in order to keep backwards compatability // An interface param needs to be added to the ContentType() function on // the Marshal interface to be able to remove this check if httpBodyMarshaler, ok := marshaler.(*HTTPBodyMarshaler); ok { pb := s.Proto() contentType = httpBodyMarshaler.ContentTypeFromMessage(pb) } w.Header().Set("Content-Type", contentType) body := &errorBody{ 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) handleForwardResponseTrailerHeader(w, md) st := HTTPStatusFromCode(s.Code()) w.WriteHeader(st) if _, err := w.Write(buf); err != nil { grpclog.Infof("Failed to write response: %v", err) } 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) }