Skip to content

Commit 62adda2

Browse files
authored
client: fix ForceCodec to set content-type header appropriately (#4401)
1 parent 81b8cca commit 62adda2

File tree

2 files changed

+66
-6
lines changed

2 files changed

+66
-6
lines changed

rpc_util.go

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -429,9 +429,10 @@ func (o ContentSubtypeCallOption) before(c *callInfo) error {
429429
}
430430
func (o ContentSubtypeCallOption) after(c *callInfo, attempt *csAttempt) {}
431431

432-
// ForceCodec returns a CallOption that will set codec to be
433-
// used for all request and response messages for a call. The result of calling
434-
// Name() will be used as the content-subtype in a case-insensitive manner.
432+
// ForceCodec returns a CallOption that will set codec to be used for all
433+
// request and response messages for a call. The result of calling Name() will
434+
// be used as the content-subtype after converting to lowercase, unless
435+
// CallContentSubtype is also used.
435436
//
436437
// See Content-Type on
437438
// https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests for
@@ -853,7 +854,17 @@ func toRPCErr(err error) error {
853854
// setCallInfoCodec should only be called after CallOptions have been applied.
854855
func setCallInfoCodec(c *callInfo) error {
855856
if c.codec != nil {
856-
// codec was already set by a CallOption; use it.
857+
// codec was already set by a CallOption; use it, but set the content
858+
// subtype if it is not set.
859+
if c.contentSubtype == "" {
860+
// c.codec is a baseCodec to hide the difference between grpc.Codec and
861+
// encoding.Codec (Name vs. String method name). We only support
862+
// setting content subtype from encoding.Codec to avoid a behavior
863+
// change with the deprecated version.
864+
if ec, ok := c.codec.(encoding.Codec); ok {
865+
c.contentSubtype = strings.ToLower(ec.Name())
866+
}
867+
}
857868
return nil
858869
}
859870

test/end2end_test.go

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5293,7 +5293,7 @@ func (s) TestGRPCMethod(t *testing.T) {
52935293
}
52945294
defer ss.Stop()
52955295

5296-
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
5296+
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
52975297
defer cancel()
52985298

52995299
if _, err := ss.Client.EmptyCall(ctx, &testpb.Empty{}); err != nil {
@@ -5305,6 +5305,55 @@ func (s) TestGRPCMethod(t *testing.T) {
53055305
}
53065306
}
53075307

5308+
// renameProtoCodec is an encoding.Codec wrapper that allows customizing the
5309+
// Name() of another codec.
5310+
type renameProtoCodec struct {
5311+
encoding.Codec
5312+
name string
5313+
}
5314+
5315+
func (r *renameProtoCodec) Name() string { return r.name }
5316+
5317+
// TestForceCodecName confirms that the ForceCodec call option sets the subtype
5318+
// in the content-type header according to the Name() of the codec provided.
5319+
func (s) TestForceCodecName(t *testing.T) {
5320+
wantContentTypeCh := make(chan []string, 1)
5321+
defer close(wantContentTypeCh)
5322+
5323+
ss := &stubserver.StubServer{
5324+
EmptyCallF: func(ctx context.Context, in *testpb.Empty) (*testpb.Empty, error) {
5325+
md, ok := metadata.FromIncomingContext(ctx)
5326+
if !ok {
5327+
return nil, status.Errorf(codes.Internal, "no metadata in context")
5328+
}
5329+
if got, want := md["content-type"], <-wantContentTypeCh; !reflect.DeepEqual(got, want) {
5330+
return nil, status.Errorf(codes.Internal, "got content-type=%q; want [%q]", got, want)
5331+
}
5332+
return &testpb.Empty{}, nil
5333+
},
5334+
}
5335+
if err := ss.Start([]grpc.ServerOption{grpc.ForceServerCodec(encoding.GetCodec("proto"))}); err != nil {
5336+
t.Fatalf("Error starting endpoint server: %v", err)
5337+
}
5338+
defer ss.Stop()
5339+
5340+
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
5341+
defer cancel()
5342+
5343+
codec := &renameProtoCodec{Codec: encoding.GetCodec("proto"), name: "some-test-name"}
5344+
wantContentTypeCh <- []string{"application/grpc+some-test-name"}
5345+
if _, err := ss.Client.EmptyCall(ctx, &testpb.Empty{}, grpc.ForceCodec(codec)); err != nil {
5346+
t.Fatalf("ss.Client.EmptyCall(_, _) = _, %v; want _, nil", err)
5347+
}
5348+
5349+
// Confirm the name is converted to lowercase before transmitting.
5350+
codec.name = "aNoTHeRNaME"
5351+
wantContentTypeCh <- []string{"application/grpc+anothername"}
5352+
if _, err := ss.Client.EmptyCall(ctx, &testpb.Empty{}, grpc.ForceCodec(codec)); err != nil {
5353+
t.Fatalf("ss.Client.EmptyCall(_, _) = _, %v; want _, nil", err)
5354+
}
5355+
}
5356+
53085357
func (s) TestForceServerCodec(t *testing.T) {
53095358
ss := &stubserver.StubServer{
53105359
EmptyCallF: func(ctx context.Context, in *testpb.Empty) (*testpb.Empty, error) {
@@ -5317,7 +5366,7 @@ func (s) TestForceServerCodec(t *testing.T) {
53175366
}
53185367
defer ss.Stop()
53195368

5320-
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
5369+
ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
53215370
defer cancel()
53225371

53235372
if _, err := ss.Client.EmptyCall(ctx, &testpb.Empty{}); err != nil {

0 commit comments

Comments
 (0)