blob: ca76324efb1fdf5c091d981ef15286acfb5f62d9 [file] [log] [blame]
khenaidooab1f7bd2019-11-14 14:00:27 -05001package runtime
2
3import (
4 "context"
5 "io"
6 "net/http"
7
8 "github.com/golang/protobuf/ptypes/any"
9 "github.com/grpc-ecosystem/grpc-gateway/internal"
10 "google.golang.org/grpc/codes"
11 "google.golang.org/grpc/grpclog"
12 "google.golang.org/grpc/status"
13)
14
15// StreamErrorHandlerFunc accepts an error as a gRPC error generated via status package and translates it into a
16// a proto struct used to represent error at the end of a stream.
17type StreamErrorHandlerFunc func(context.Context, error) *StreamError
18
19// StreamError is the payload for the final message in a server stream in the event that the server returns an
20// error after a response message has already been sent.
21type StreamError internal.StreamError
22
23// ProtoErrorHandlerFunc handles the error as a gRPC error generated via status package and replies to the request.
24type ProtoErrorHandlerFunc func(context.Context, *ServeMux, Marshaler, http.ResponseWriter, *http.Request, error)
25
26var _ ProtoErrorHandlerFunc = DefaultHTTPProtoErrorHandler
27
28// DefaultHTTPProtoErrorHandler is an implementation of HTTPError.
29// If "err" is an error from gRPC system, the function replies with the status code mapped by HTTPStatusFromCode.
30// If otherwise, it replies with http.StatusInternalServerError.
31//
32// The response body returned by this function is a Status message marshaled by a Marshaler.
33//
34// Do not set this function to HTTPError variable directly, use WithProtoErrorHandler option instead.
35func DefaultHTTPProtoErrorHandler(ctx context.Context, mux *ServeMux, marshaler Marshaler, w http.ResponseWriter, _ *http.Request, err error) {
36 // return Internal when Marshal failed
37 const fallback = `{"code": 13, "message": "failed to marshal error message"}`
38
39 s, ok := status.FromError(err)
40 if !ok {
41 s = status.New(codes.Unknown, err.Error())
42 }
43
44 w.Header().Del("Trailer")
45
46 contentType := marshaler.ContentType()
47 // Check marshaler on run time in order to keep backwards compatability
48 // An interface param needs to be added to the ContentType() function on
49 // the Marshal interface to be able to remove this check
50 if httpBodyMarshaler, ok := marshaler.(*HTTPBodyMarshaler); ok {
51 pb := s.Proto()
52 contentType = httpBodyMarshaler.ContentTypeFromMessage(pb)
53 }
54 w.Header().Set("Content-Type", contentType)
55
56 buf, merr := marshaler.Marshal(s.Proto())
57 if merr != nil {
58 grpclog.Infof("Failed to marshal error message %q: %v", s.Proto(), merr)
59 w.WriteHeader(http.StatusInternalServerError)
60 if _, err := io.WriteString(w, fallback); err != nil {
61 grpclog.Infof("Failed to write response: %v", err)
62 }
63 return
64 }
65
66 md, ok := ServerMetadataFromContext(ctx)
67 if !ok {
68 grpclog.Infof("Failed to extract ServerMetadata from context")
69 }
70
71 handleForwardResponseServerMetadata(w, mux, md)
72 handleForwardResponseTrailerHeader(w, md)
73 st := HTTPStatusFromCode(s.Code())
74 w.WriteHeader(st)
75 if _, err := w.Write(buf); err != nil {
76 grpclog.Infof("Failed to write response: %v", err)
77 }
78
79 handleForwardResponseTrailer(w, md)
80}
81
82// DefaultHTTPStreamErrorHandler converts the given err into a *StreamError via
83// default logic.
84//
85// It extracts the gRPC status from err if possible. The fields of the status are
86// used to populate the returned StreamError, and the HTTP status code is derived
87// from the gRPC code via HTTPStatusFromCode. If the given err does not contain a
88// gRPC status, an "Unknown" gRPC code is used and "Internal Server Error" HTTP code.
89func DefaultHTTPStreamErrorHandler(_ context.Context, err error) *StreamError {
90 grpcCode := codes.Unknown
91 grpcMessage := err.Error()
92 var grpcDetails []*any.Any
93 if s, ok := status.FromError(err); ok {
94 grpcCode = s.Code()
95 grpcMessage = s.Message()
96 grpcDetails = s.Proto().GetDetails()
97 }
98 httpCode := HTTPStatusFromCode(grpcCode)
99 return &StreamError{
100 GrpcCode: int32(grpcCode),
101 HttpCode: int32(httpCode),
102 Message: grpcMessage,
103 HttpStatus: http.StatusText(httpCode),
104 Details: grpcDetails,
105 }
106}