2017-07-06 05:38:20 -07:00
|
|
|
package runtime
|
|
|
|
|
|
|
|
import (
|
2019-01-15 06:32:05 -08:00
|
|
|
"context"
|
2017-07-06 05:38:20 -07:00
|
|
|
"io"
|
|
|
|
"net/http"
|
|
|
|
|
|
|
|
"github.com/golang/protobuf/proto"
|
2019-01-15 06:32:05 -08:00
|
|
|
"github.com/golang/protobuf/ptypes/any"
|
2017-07-06 05:38:20 -07:00
|
|
|
"google.golang.org/grpc/codes"
|
|
|
|
"google.golang.org/grpc/grpclog"
|
2017-11-30 02:19:08 -08:00
|
|
|
"google.golang.org/grpc/status"
|
2017-07-06 05:38:20 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
// HTTPStatusFromCode converts a gRPC error code into the corresponding HTTP response status.
|
2019-01-15 06:32:05 -08:00
|
|
|
// See: https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto
|
2017-07-06 05:38:20 -07:00
|
|
|
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:
|
2019-01-15 06:32:05 -08:00
|
|
|
return http.StatusGatewayTimeout
|
2017-07-06 05:38:20 -07:00
|
|
|
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:
|
2019-01-15 06:32:05 -08:00
|
|
|
return http.StatusTooManyRequests
|
2017-07-06 05:38:20 -07:00
|
|
|
case codes.FailedPrecondition:
|
2019-08-14 02:00:39 -07:00
|
|
|
// Note, this deliberately doesn't translate to the similarly named '412 Precondition Failed' HTTP response status.
|
|
|
|
return http.StatusBadRequest
|
2017-07-06 05:38:20 -07:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2019-01-15 06:32:05 -08:00
|
|
|
grpclog.Infof("Unknown gRPC error code: %v", code)
|
2017-07-06 05:38:20 -07:00
|
|
|
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 {
|
2019-04-04 02:55:32 -07:00
|
|
|
Error string `protobuf:"bytes,1,name=error" json:"error"`
|
2019-01-15 06:32:05 -08:00
|
|
|
// 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"`
|
2017-07-06 05:38:20 -07:00
|
|
|
}
|
|
|
|
|
2019-01-15 06:32:05 -08:00
|
|
|
// Make this also conform to proto.Message for builtin JSONPb Marshaler
|
2017-07-06 05:38:20 -07:00
|
|
|
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().
|
2017-11-30 02:19:08 -08:00
|
|
|
func DefaultHTTPError(ctx context.Context, mux *ServeMux, marshaler Marshaler, w http.ResponseWriter, _ *http.Request, err error) {
|
2017-07-06 05:38:20 -07:00
|
|
|
const fallback = `{"error": "failed to marshal error message"}`
|
|
|
|
|
2017-11-30 02:19:08 -08:00
|
|
|
s, ok := status.FromError(err)
|
|
|
|
if !ok {
|
|
|
|
s = status.New(codes.Unknown, err.Error())
|
|
|
|
}
|
|
|
|
|
2019-04-04 02:55:32 -07:00
|
|
|
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)
|
|
|
|
|
2017-07-06 05:38:20 -07:00
|
|
|
body := &errorBody{
|
2019-01-15 06:32:05 -08:00
|
|
|
Error: s.Message(),
|
|
|
|
Message: s.Message(),
|
|
|
|
Code: int32(s.Code()),
|
|
|
|
Details: s.Proto().GetDetails(),
|
2017-07-06 05:38:20 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
buf, merr := marshaler.Marshal(body)
|
|
|
|
if merr != nil {
|
2019-01-15 06:32:05 -08:00
|
|
|
grpclog.Infof("Failed to marshal error message %q: %v", body, merr)
|
2017-07-06 05:38:20 -07:00
|
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
|
|
if _, err := io.WriteString(w, fallback); err != nil {
|
2019-01-15 06:32:05 -08:00
|
|
|
grpclog.Infof("Failed to write response: %v", err)
|
2017-07-06 05:38:20 -07:00
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
md, ok := ServerMetadataFromContext(ctx)
|
|
|
|
if !ok {
|
2019-01-15 06:32:05 -08:00
|
|
|
grpclog.Infof("Failed to extract ServerMetadata from context")
|
2017-07-06 05:38:20 -07:00
|
|
|
}
|
|
|
|
|
2017-11-30 02:19:08 -08:00
|
|
|
handleForwardResponseServerMetadata(w, mux, md)
|
2017-07-06 05:38:20 -07:00
|
|
|
handleForwardResponseTrailerHeader(w, md)
|
2017-11-30 02:19:08 -08:00
|
|
|
st := HTTPStatusFromCode(s.Code())
|
2017-07-06 05:38:20 -07:00
|
|
|
w.WriteHeader(st)
|
|
|
|
if _, err := w.Write(buf); err != nil {
|
2019-01-15 06:32:05 -08:00
|
|
|
grpclog.Infof("Failed to write response: %v", err)
|
2017-07-06 05:38:20 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
}
|