blob: a36080713ce3fd510ade3990596e67eddcfafa0c [file] [log] [blame]
Matteo Scandoloa6a3aee2019-11-26 13:30:14 -07001package runtime
2
3import (
4 "context"
5 "io"
6 "net/http"
7
8 "github.com/golang/protobuf/proto"
9 "github.com/golang/protobuf/ptypes/any"
10 "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 (
61 // HTTPError replies to the request with the error.
62 // You can set a custom function to this variable to customize error format.
63 HTTPError = DefaultHTTPError
64 // OtherErrorHandler handles the following error used by the gateway: StatusMethodNotAllowed StatusNotFound and StatusBadRequest
65 OtherErrorHandler = DefaultOtherErrorHandler
66)
67
68type errorBody struct {
69 Error string `protobuf:"bytes,100,name=error" json:"error"`
70 // This is to make the error more compatible with users that expect errors to be Status objects:
71 // https://github.com/grpc/grpc/blob/master/src/proto/grpc/status/status.proto
72 // It should be the exact same message as the Error field.
73 Code int32 `protobuf:"varint,1,name=code" json:"code"`
74 Message string `protobuf:"bytes,2,name=message" json:"message"`
75 Details []*any.Any `protobuf:"bytes,3,rep,name=details" json:"details,omitempty"`
76}
77
78// Make this also conform to proto.Message for builtin JSONPb Marshaler
79func (e *errorBody) Reset() { *e = errorBody{} }
80func (e *errorBody) String() string { return proto.CompactTextString(e) }
81func (*errorBody) ProtoMessage() {}
82
83// DefaultHTTPError is the default implementation of HTTPError.
84// If "err" is an error from gRPC system, the function replies with the status code mapped by HTTPStatusFromCode.
85// If otherwise, it replies with http.StatusInternalServerError.
86//
87// The response body returned by this function is a JSON object,
88// which contains a member whose key is "error" and whose value is err.Error().
89func DefaultHTTPError(ctx context.Context, mux *ServeMux, marshaler Marshaler, w http.ResponseWriter, _ *http.Request, err error) {
90 const fallback = `{"error": "failed to marshal error message"}`
91
92 s, ok := status.FromError(err)
93 if !ok {
94 s = status.New(codes.Unknown, err.Error())
95 }
96
97 w.Header().Del("Trailer")
98
99 contentType := marshaler.ContentType()
100 // Check marshaler on run time in order to keep backwards compatability
101 // An interface param needs to be added to the ContentType() function on
102 // the Marshal interface to be able to remove this check
103 if httpBodyMarshaler, ok := marshaler.(*HTTPBodyMarshaler); ok {
104 pb := s.Proto()
105 contentType = httpBodyMarshaler.ContentTypeFromMessage(pb)
106 }
107 w.Header().Set("Content-Type", contentType)
108
109 body := &errorBody{
110 Error: s.Message(),
111 Message: s.Message(),
112 Code: int32(s.Code()),
113 Details: s.Proto().GetDetails(),
114 }
115
116 buf, merr := marshaler.Marshal(body)
117 if merr != nil {
118 grpclog.Infof("Failed to marshal error message %q: %v", body, merr)
119 w.WriteHeader(http.StatusInternalServerError)
120 if _, err := io.WriteString(w, fallback); err != nil {
121 grpclog.Infof("Failed to write response: %v", err)
122 }
123 return
124 }
125
126 md, ok := ServerMetadataFromContext(ctx)
127 if !ok {
128 grpclog.Infof("Failed to extract ServerMetadata from context")
129 }
130
131 handleForwardResponseServerMetadata(w, mux, md)
132 handleForwardResponseTrailerHeader(w, md)
133 st := HTTPStatusFromCode(s.Code())
134 w.WriteHeader(st)
135 if _, err := w.Write(buf); err != nil {
136 grpclog.Infof("Failed to write response: %v", err)
137 }
138
139 handleForwardResponseTrailer(w, md)
140}
141
142// DefaultOtherErrorHandler is the default implementation of OtherErrorHandler.
143// It simply writes a string representation of the given error into "w".
144func DefaultOtherErrorHandler(w http.ResponseWriter, _ *http.Request, msg string, code int) {
145 http.Error(w, msg, code)
146}