diff --git a/internal/transport/http2_client.go b/internal/transport/http2_client.go index 54471821..c96178d7 100644 --- a/internal/transport/http2_client.go +++ b/internal/transport/http2_client.go @@ -441,6 +441,15 @@ func (t *http2Client) createHeaderFields(ctx context.Context, callHdr *CallHdr) if md, added, ok := metadata.FromOutgoingContextRaw(ctx); ok { var k string + for k, vv := range md { + // HTTP doesn't allow you to set pseudoheaders after non pseudoheaders were set. + if isReservedHeader(k) { + continue + } + for _, v := range vv { + headerFields = append(headerFields, hpack.HeaderField{Name: k, Value: encodeMetadataHeader(k, v)}) + } + } for _, vv := range added { for i, v := range vv { if i%2 == 0 { @@ -454,15 +463,6 @@ func (t *http2Client) createHeaderFields(ctx context.Context, callHdr *CallHdr) headerFields = append(headerFields, hpack.HeaderField{Name: strings.ToLower(k), Value: encodeMetadataHeader(k, v)}) } } - for k, vv := range md { - // HTTP doesn't allow you to set pseudoheaders after non pseudoheaders were set. - if isReservedHeader(k) { - continue - } - for _, v := range vv { - headerFields = append(headerFields, hpack.HeaderField{Name: k, Value: encodeMetadataHeader(k, v)}) - } - } } if md, ok := t.md.(*metadata.MD); ok { for k, vv := range *md { diff --git a/test/end2end_test.go b/test/end2end_test.go index 30356a6e..c0489256 100644 --- a/test/end2end_test.go +++ b/test/end2end_test.go @@ -3146,6 +3146,43 @@ func testMetadataUnaryRPC(t *testing.T, e env) { } } +func (s) TestMetadataOrderUnaryRPC(t *testing.T) { + for _, e := range listTestEnv() { + testMetadataOrderUnaryRPC(t, e) + } +} + +func testMetadataOrderUnaryRPC(t *testing.T, e env) { + te := newTest(t, e) + te.startServer(&testServer{security: e.security}) + defer te.tearDown() + tc := testpb.NewTestServiceClient(te.clientConn()) + + ctx := metadata.NewOutgoingContext(context.Background(), testMetadata) + ctx = metadata.AppendToOutgoingContext(ctx, "key1", "value2") + ctx = metadata.AppendToOutgoingContext(ctx, "key1", "value3") + + // using Join to built expected metadata instead of FromOutgoingContext + newMetadata := metadata.Join(testMetadata, metadata.Pairs("key1", "value2", "key1", "value3")) + + var header metadata.MD + if _, err := tc.UnaryCall(ctx, &testpb.SimpleRequest{}, grpc.Header(&header)); err != nil { + t.Fatal(err) + } + + // Ignore optional response headers that Servers may set: + if header != nil { + delete(header, "trailer") // RFC 2616 says server SHOULD (but optional) declare trailers + delete(header, "date") // the Date header is also optional + delete(header, "user-agent") + delete(header, "content-type") + } + + if !reflect.DeepEqual(header, newMetadata) { + t.Fatalf("Received header metadata %v, want %v", header, newMetadata) + } +} + func (s) TestMultipleSetTrailerUnaryRPC(t *testing.T) { for _, e := range listTestEnv() { testMultipleSetTrailerUnaryRPC(t, e)