From bfaf0423469fc4f95e9eac7e3eec0b2abb46fcca Mon Sep 17 00:00:00 2001 From: Camilo Aguilar Date: Tue, 15 Aug 2017 10:45:05 -0700 Subject: [PATCH] Add status details support to server HTTP handler (#1438) --- transport/handler_server.go | 13 +++++-- transport/handler_server_test.go | 58 ++++++++++++++++++++++++++++++-- 2 files changed, 66 insertions(+), 5 deletions(-) diff --git a/transport/handler_server.go b/transport/handler_server.go index c2648a3f..85b8ee0a 100644 --- a/transport/handler_server.go +++ b/transport/handler_server.go @@ -33,6 +33,7 @@ import ( "sync" "time" + "github.com/golang/protobuf/proto" "golang.org/x/net/context" "golang.org/x/net/http2" "google.golang.org/grpc/codes" @@ -197,7 +198,15 @@ func (ht *serverHandlerTransport) WriteStatus(s *Stream, st *status.Status) erro h.Set("Grpc-Message", encodeGrpcMessage(m)) } - // TODO: Support Grpc-Status-Details-Bin + if p := st.Proto(); p != nil && len(p.Details) > 0 { + stBytes, err := proto.Marshal(p) + if err != nil { + // TODO: return error instead, when callers are able to handle it. + panic(err) + } + + h.Set("Grpc-Status-Details-Bin", encodeBinHeader(stBytes)) + } if md := s.Trailer(); len(md) > 0 { for k, vv := range md { @@ -239,7 +248,7 @@ func (ht *serverHandlerTransport) writeCommonHeaders(s *Stream) { // and https://golang.org/pkg/net/http/#example_ResponseWriter_trailers h.Add("Trailer", "Grpc-Status") h.Add("Trailer", "Grpc-Message") - // TODO: Support Grpc-Status-Details-Bin + h.Add("Trailer", "Grpc-Status-Details-Bin") if s.sendCompress != "" { h.Set("Grpc-Encoding", s.sendCompress) diff --git a/transport/handler_server_test.go b/transport/handler_server_test.go index 65d61a8b..262e6016 100644 --- a/transport/handler_server_test.go +++ b/transport/handler_server_test.go @@ -29,7 +29,10 @@ import ( "testing" "time" + "github.com/golang/protobuf/proto" + dpb "github.com/golang/protobuf/ptypes/duration" "golang.org/x/net/context" + epb "google.golang.org/genproto/googleapis/rpc/errdetails" "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" @@ -199,6 +202,7 @@ func TestHandlerTransport_NewServerHandlerTransport(t *testing.T) { "user-agent": {"x/y a/b"}, "meta-foo": {"foo-val"}, } + if !reflect.DeepEqual(ht.headerMD, want) { return fmt.Errorf("metdata = %#v; want %#v", ht.headerMD, want) } @@ -294,7 +298,7 @@ func TestHandlerTransport_HandleStreams(t *testing.T) { wantHeader := http.Header{ "Date": nil, "Content-Type": {"application/grpc"}, - "Trailer": {"Grpc-Status", "Grpc-Message"}, + "Trailer": {"Grpc-Status", "Grpc-Message", "Grpc-Status-Details-Bin"}, "Grpc-Status": {"0"}, } if !reflect.DeepEqual(st.rw.HeaderMap, wantHeader) { @@ -314,6 +318,7 @@ func TestHandlerTransport_HandleStreams_InvalidArgument(t *testing.T) { func handleStreamCloseBodyTest(t *testing.T, statusCode codes.Code, msg string) { st := newHandleStreamTest(t) + handleStream := func(s *Stream) { st.ht.WriteStatus(s, status.New(statusCode, msg)) } @@ -324,10 +329,11 @@ func handleStreamCloseBodyTest(t *testing.T, statusCode codes.Code, msg string) wantHeader := http.Header{ "Date": nil, "Content-Type": {"application/grpc"}, - "Trailer": {"Grpc-Status", "Grpc-Message"}, + "Trailer": {"Grpc-Status", "Grpc-Message", "Grpc-Status-Details-Bin"}, "Grpc-Status": {fmt.Sprint(uint32(statusCode))}, "Grpc-Message": {encodeGrpcMessage(msg)}, } + if !reflect.DeepEqual(st.rw.HeaderMap, wantHeader) { t.Errorf("Header+Trailer mismatch.\n got: %#v\nwant: %#v", st.rw.HeaderMap, wantHeader) } @@ -375,7 +381,7 @@ func TestHandlerTransport_HandleStreams_Timeout(t *testing.T) { wantHeader := http.Header{ "Date": nil, "Content-Type": {"application/grpc"}, - "Trailer": {"Grpc-Status", "Grpc-Message"}, + "Trailer": {"Grpc-Status", "Grpc-Message", "Grpc-Status-Details-Bin"}, "Grpc-Status": {"4"}, "Grpc-Message": {encodeGrpcMessage("too slow")}, } @@ -383,3 +389,49 @@ func TestHandlerTransport_HandleStreams_Timeout(t *testing.T) { t.Errorf("Header+Trailer Map mismatch.\n got: %#v\nwant: %#v", rw.HeaderMap, wantHeader) } } + +func TestHandlerTransport_HandleStreams_ErrDetails(t *testing.T) { + errDetails := []proto.Message{ + &epb.RetryInfo{ + RetryDelay: &dpb.Duration{Seconds: 60}, + }, + &epb.ResourceInfo{ + ResourceType: "foo bar", + ResourceName: "service.foo.bar", + Owner: "User", + }, + } + + statusCode := codes.ResourceExhausted + msg := "you are being throttled" + st, err := status.New(statusCode, msg).WithDetails(errDetails...) + if err != nil { + t.Fatal(err) + } + + stBytes, err := proto.Marshal(st.Proto()) + if err != nil { + t.Fatal(err) + } + + hst := newHandleStreamTest(t) + handleStream := func(s *Stream) { + hst.ht.WriteStatus(s, st) + } + hst.ht.HandleStreams( + func(s *Stream) { go handleStream(s) }, + func(ctx context.Context, method string) context.Context { return ctx }, + ) + wantHeader := http.Header{ + "Date": nil, + "Content-Type": {"application/grpc"}, + "Trailer": {"Grpc-Status", "Grpc-Message", "Grpc-Status-Details-Bin"}, + "Grpc-Status": {fmt.Sprint(uint32(statusCode))}, + "Grpc-Message": {encodeGrpcMessage(msg)}, + "Grpc-Status-Details-Bin": {encodeBinHeader(stBytes)}, + } + + if !reflect.DeepEqual(hst.rw.HeaderMap, wantHeader) { + t.Errorf("Header+Trailer mismatch.\n got: %#v\nwant: %#v", hst.rw.HeaderMap, wantHeader) + } +}