diff --git a/js/modules/k6/grpc/client.go b/js/modules/k6/grpc/client.go index 6d00a46ceea..f35cd981414 100644 --- a/js/modules/k6/grpc/client.go +++ b/js/modules/k6/grpc/client.go @@ -260,6 +260,9 @@ func (c *Client) Connect(addr string, params goja.Value) (bool, error) { if !p.UseReflectionProtocol { return true, nil } + + ctx = metadata.NewOutgoingContext(ctx, p.ReflectionMetadata) + fdset, err := c.conn.Reflect(ctx) if err != nil { return false, err @@ -510,6 +513,7 @@ func newMetadata(input goja.Value) (metadata.MD, error) { type connectParams struct { IsPlaintext bool + ReflectionMetadata metadata.MD UseReflectionProtocol bool Timeout time.Duration MaxReceiveSize int64 @@ -521,6 +525,7 @@ func newConnectParams(rt *goja.Runtime, input goja.Value) (connectParams, error) params := connectParams{ IsPlaintext: false, UseReflectionProtocol: false, + ReflectionMetadata: metadata.New(nil), Timeout: time.Minute, MaxReceiveSize: 0, MaxSendSize: 0, @@ -554,6 +559,12 @@ func newConnectParams(rt *goja.Runtime, input goja.Value) (connectParams, error) if !ok { return params, fmt.Errorf("invalid reflect value: '%#v', it needs to be boolean", v) } + case "reflectMetadata": + md, err := newMetadata(raw.Get(k)) + if err != nil { + return params, fmt.Errorf("invalid reflectMetadata param: %w", err) + } + params.ReflectionMetadata = md case "maxReceiveSize": var ok bool params.MaxReceiveSize, ok = v.(int64) diff --git a/js/modules/k6/grpc/client_test.go b/js/modules/k6/grpc/client_test.go index 0feba4269fc..90d4a02a43e 100644 --- a/js/modules/k6/grpc/client_test.go +++ b/js/modules/k6/grpc/client_test.go @@ -13,6 +13,7 @@ import ( "google.golang.org/protobuf/reflect/protoreflect" "google.golang.org/protobuf/reflect/protoregistry" "google.golang.org/protobuf/types/known/wrapperspb" + "gopkg.in/guregu/null.v3" "github.com/golang/protobuf/ptypes/any" "github.com/golang/protobuf/ptypes/wrappers" @@ -1193,3 +1194,46 @@ func TestClientLoadProto(t *testing.T) { assert.Nil(t, err, "It was not expected that there would be an error, but it got: %v", err) } } + +func TestClientConnectionReflectMetadata(t *testing.T) { + t.Parallel() + + ts := newTestState(t) + + reflection.Register(ts.httpBin.ServerGRPC) + + initString := codeBlock{ + code: `var client = new grpc.Client();`, + } + vuString := codeBlock{ + code: `client.connect("GRPCBIN_ADDR", {reflect: true, reflectMetadata: {"x-test": "custom-header-for-reflection"}})`, + } + + val, err := ts.Run(initString.code) + assertResponse(t, initString, err, val, ts) + + ts.ToVUContext() + + // this should trigger logging of the outgoing gRPC metadata + ts.VU.State().Options.HTTPDebug = null.NewString("full", true) + + val, err = ts.Run(vuString.code) + assertResponse(t, vuString, err, val, ts) + + entries := ts.loggerHook.Drain() + + // since we enable debug logging, we should see the metadata in the logs + foundReflectionCall := false + for _, entry := range entries { + if strings.Contains(entry.Message, "ServerReflection/ServerReflectionInfo") { + foundReflectionCall = true + + // check that the metadata is present + assert.Contains(t, entry.Message, "x-test: custom-header-for-reflection") + // check that user-agent header is present + assert.Contains(t, entry.Message, "user-agent: k6-test") + } + } + + assert.True(t, foundReflectionCall, "expected to find a reflection call in the logs, but didn't") +}