@@ -17,6 +17,7 @@ import (
1717 "google.golang.org/grpc"
1818 "google.golang.org/grpc/codes"
1919 "google.golang.org/grpc/credentials"
20+ "google.golang.org/grpc/metadata"
2021 "google.golang.org/protobuf/reflect/protoreflect"
2122)
2223
@@ -25,6 +26,7 @@ type Config struct {
2526 Creds credentials.TransportCredentials
2627 UserAgent string
2728 Protoset string
29+ Headers []string
2830}
2931
3032type Client struct {
@@ -34,6 +36,10 @@ type Client struct {
3436}
3537
3638func NewClient (ctx context.Context , config Config ) (* Client , error ) {
39+ if err := validateHeaders (config .Headers ); err != nil {
40+ return nil , err
41+ }
42+
3743 opts := []grpc.DialOption {
3844 grpc .WithUserAgent (config .UserAgent ),
3945 }
@@ -50,6 +56,9 @@ func NewClient(ctx context.Context, config Config) (*Client, error) {
5056 }
5157 } else {
5258 refCtx := context .Background ()
59+ if len (config .Headers ) > 0 {
60+ refCtx = metadata .NewOutgoingContext (refCtx , grpcurl .MetadataFromHeaders (config .Headers ))
61+ }
5362 refClient := grpcreflect .NewClientAuto (refCtx , cc )
5463 refClient .AllowMissingFileDescriptors ()
5564 source = grpcurl .DescriptorSourceFromServer (refCtx , refClient )
@@ -58,6 +67,16 @@ func NewClient(ctx context.Context, config Config) (*Client, error) {
5867 return & Client {source : source , conn : cc , config : config }, nil
5968}
6069
70+ func validateHeaders (headers []string ) error {
71+ for _ , header := range headers {
72+ name , _ , ok := strings .Cut (header , ":" )
73+ if ! ok || strings .TrimSpace (name ) == "" {
74+ return fmt .Errorf ("invalid header %q: expected 'name: value'" , header )
75+ }
76+ }
77+ return nil
78+ }
79+
6180func (c * Client ) InvokeRPC (ctx context.Context , methodFullName string , request map [string ]any ) (string , error ) {
6281 jsonData , err := json .Marshal (request )
6382 if err != nil {
@@ -78,7 +97,7 @@ func (c *Client) InvokeRPC(ctx context.Context, methodFullName string, request m
7897 VerbosityLevel : 0 ,
7998 }
8099
81- err = grpcurl .InvokeRPC (ctx , c .source , c .conn , methodFullName , nil , handler , rf .Next )
100+ err = grpcurl .InvokeRPC (ctx , c .source , c .conn , methodFullName , c . config . Headers , handler , rf .Next )
82101 if err != nil {
83102 return "" , fmt .Errorf ("RPC invocation failed: %w" , err )
84103 }
@@ -120,7 +139,7 @@ func (c *Client) InvokeStreaming(ctx context.Context, methodFullName string, req
120139 return rf .Next (msg )
121140 }
122141
123- if err := grpcurl .InvokeRPC (ctx , c .source , c .conn , methodFullName , nil , handler , requestSupplier ); err != nil {
142+ if err := grpcurl .InvokeRPC (ctx , c .source , c .conn , methodFullName , c . config . Headers , handler , requestSupplier ); err != nil {
124143 events <- StreamEvent {Kind : StreamEventError , Err : fmt .Errorf ("RPC invocation failed: %w" , err )}
125144 return err
126145 }
@@ -152,6 +171,9 @@ func (c *Client) GRPCURLCommand(methodFullName string, request map[string]any) (
152171 if c .config .UserAgent != "" {
153172 args = append (args , "-user-agent" , c .config .UserAgent )
154173 }
174+ for _ , header := range c .config .Headers {
175+ args = append (args , "-H" , header )
176+ }
155177 args = append (args , "-d" , string (jsonData ), c .config .Target , methodFullName )
156178
157179 for i , arg := range args {
0 commit comments