diff --git a/Documentation/grpc-metadata.md b/Documentation/grpc-metadata.md index 06b36f4ac171..f8d2b371ff98 100644 --- a/Documentation/grpc-metadata.md +++ b/Documentation/grpc-metadata.md @@ -12,11 +12,11 @@ Four kinds of service method: - [Client streaming RPC](https://grpc.io/docs/guides/concepts.html#client-streaming-rpc) - [Bidirectional streaming RPC](https://grpc.io/docs/guides/concepts.html#bidirectional-streaming-rpc) -And concept of [metadata](https://grpc.io/docs/guides/concepts.html#metadata). +And concept of [metadata]. ## Constructing metadata -A metadata can be created using package [metadata](https://godoc.org/google.golang.org/grpc/metadata). +A metadata can be created using package [metadata]. The type MD is actually a map from string to a list of strings: ```go @@ -64,20 +64,10 @@ md := metadata.Pairs( ) ``` -## Retrieving metadata from context - -Metadata can be retrieved from context using `FromIncomingContext`: - -```go -func (s *server) SomeRPC(ctx context.Context, in *pb.SomeRequest) (*pb.SomeResponse, err) { - md, ok := metadata.FromIncomingContext(ctx) - // do something with metadata -} -``` - ## Sending and receiving metadata - client side -Client side metadata sending and receiving examples are available [here](../examples/features/metadata/client/main.go). +Client side metadata sending and receiving examples are available +[here](../examples/features/metadata/client/main.go). ### Sending metadata @@ -127,7 +117,8 @@ Metadata that a client can receive includes header and trailer. #### Unary call -Header and trailer sent along with a unary call can be retrieved using function [Header](https://godoc.org/google.golang.org/grpc#Header) and [Trailer](https://godoc.org/google.golang.org/grpc#Trailer) in [CallOption](https://godoc.org/google.golang.org/grpc#CallOption): +Header and trailer sent along with a unary call can be retrieved using function +[Header] and [Trailer] in [CallOption]: ```go var header, trailer metadata.MD // variable to store header and trailer @@ -149,7 +140,8 @@ For streaming calls including: - Client streaming RPC - Bidirectional streaming RPC -Header and trailer can be retrieved from the returned stream using function `Header` and `Trailer` in interface [ClientStream](https://godoc.org/google.golang.org/grpc#ClientStream): +Header and trailer can be retrieved from the returned stream using function +`Header` and `Trailer` in interface [ClientStream]: ```go stream, err := client.SomeStreamingRPC(ctx) @@ -164,11 +156,13 @@ trailer := stream.Trailer() ## Sending and receiving metadata - server side -Server side metadata sending and receiving examples are available [here](../examples/features/metadata/server/main.go). +Server side metadata sending and receiving examples are available +[here](../examples/features/metadata/server/main.go). ### Receiving metadata -To read metadata sent by the client, the server needs to retrieve it from RPC context. +To read metadata sent by the client, the server needs to retrieve it from RPC +context using [FromIncomingContext]. If it is a unary call, the RPC handler's context can be used. For streaming calls, the server needs to get context from the stream. @@ -194,15 +188,16 @@ func (s *server) SomeStreamingRPC(stream pb.Service_SomeStreamingRPCServer) erro #### Unary call -To send header and trailer to client in unary call, the server can call [SendHeader](https://godoc.org/google.golang.org/grpc#SendHeader) and [SetTrailer](https://godoc.org/google.golang.org/grpc#SetTrailer) functions in module [grpc](https://godoc.org/google.golang.org/grpc). +To send header and trailer to client in unary call, the server can call +[SetHeader] and [SetTrailer] functions in module [grpc]. These two functions take a context as the first parameter. It should be the RPC handler's context or one derived from it: ```go func (s *server) SomeRPC(ctx context.Context, in *pb.someRequest) (*pb.someResponse, error) { - // create and send header + // create and set header header := metadata.Pairs("header-key", "val") - grpc.SendHeader(ctx, header) + grpc.SetHeader(ctx, header) // create and set trailer trailer := metadata.Pairs("trailer-key", "val") grpc.SetTrailer(ctx, trailer) @@ -211,20 +206,39 @@ func (s *server) SomeRPC(ctx context.Context, in *pb.someRequest) (*pb.someRespo #### Streaming call -For streaming calls, header and trailer can be sent using function `SendHeader` and `SetTrailer` in interface [ServerStream](https://godoc.org/google.golang.org/grpc#ServerStream): +For streaming calls, header and trailer can be sent using function +[SetHeader] and [SetTrailer] in interface [ServerStream]: ```go func (s *server) SomeStreamingRPC(stream pb.Service_SomeStreamingRPCServer) error { - // create and send header + // create and set header header := metadata.Pairs("header-key", "val") - stream.SendHeader(header) + stream.SetHeader(header) // create and set trailer trailer := metadata.Pairs("trailer-key", "val") stream.SetTrailer(trailer) } ``` +**Important** + +Do not use +[FromOutgoingContext] on the server to write metadata to be sent to the client. +[FromOutgoingContext] is for client-side use only. + ## Updating metadata from a server interceptor An example for updating metadata from a server interceptor is available [here](../examples/features/metadata_interceptor/server/main.go). + +[FromIncomingContext]: +[SetHeader]: +[SetTrailer]: https://godoc.org/google.golang.org/grpc#SetTrailer +[FromOutgoingContext]: https://pkg.go.dev/google.golang.org/grpc/metadata#FromOutgoingContext +[ServerStream]: https://godoc.org/google.golang.org/grpc#ServerStream +[grpc]: https://godoc.org/google.golang.org/grpc +[ClientStream]: https://godoc.org/google.golang.org/grpc#ClientStream +[Header]: https://godoc.org/google.golang.org/grpc#Header +[Trailer]: https://godoc.org/google.golang.org/grpc#Trailer +[CallOption]: https://godoc.org/google.golang.org/grpc#CallOption +[metadata]: https://godoc.org/google.golang.org/grpc/metadata \ No newline at end of file diff --git a/examples/features/metadata_interceptor/client/main.go b/examples/features/metadata_interceptor/client/main.go index 99d031dacd06..3c950ed7c57b 100644 --- a/examples/features/metadata_interceptor/client/main.go +++ b/examples/features/metadata_interceptor/client/main.go @@ -29,6 +29,7 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/metadata" pb "google.golang.org/grpc/examples/features/proto/echo" ) @@ -36,11 +37,22 @@ import ( var addr = flag.String("addr", "localhost:50051", "the address to connect to") func callUnaryEcho(ctx context.Context, client pb.EchoClient) { - resp, err := client.UnaryEcho(ctx, &pb.EchoRequest{Message: "hello world"}) + var header, trailer metadata.MD + resp, err := client.UnaryEcho(ctx, &pb.EchoRequest{Message: "hello world"}, grpc.Header(&header), grpc.Trailer(&trailer)) if err != nil { log.Fatalf("UnaryEcho: %v", err) } fmt.Println("UnaryEcho: ", resp.Message) + + fmt.Println("Received headers:") + for k, v := range header { + fmt.Printf("%s: %v\n", k, v) + } + + fmt.Println("Received trailers:") + for k, v := range trailer { + fmt.Printf("%s: %v\n", k, v) + } } func callBidiStreamingEcho(ctx context.Context, client pb.EchoClient) { @@ -64,6 +76,21 @@ func callBidiStreamingEcho(ctx context.Context, client pb.EchoClient) { } fmt.Println("BidiStreaming Echo: ", resp.Message) } + + header, err := c.Header() + if err != nil { + log.Fatalf("Receiving headers: %v", err) + } + fmt.Println("Received headers:") + for k, v := range header { + fmt.Printf("%s: %v\n", k, v) + } + + trailer := c.Trailer() + fmt.Println("Received tailers:") + for k, v := range trailer { + fmt.Printf("%s: %v\n", k, v) + } } func main() { diff --git a/examples/features/metadata_interceptor/server/main.go b/examples/features/metadata_interceptor/server/main.go index 1d8f1d3262f8..f267bde11982 100644 --- a/examples/features/metadata_interceptor/server/main.go +++ b/examples/features/metadata_interceptor/server/main.go @@ -49,10 +49,22 @@ func unaryInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo, return nil, errMissingMetadata } + // Create and set metadata from interceptor to server. md.Append("key1", "value1") ctx = metadata.NewIncomingContext(ctx, md) - return handler(ctx, req) + // Call the handler to complete the normal execution of the RPC. + resp, err := handler(ctx, req) + + // Create and set header metadata from interceptor to client. + header := metadata.Pairs("header-key", "val") + grpc.SetHeader(ctx, header) + + // Create and set trailer metadata from interceptor to client. + trailer := metadata.Pairs("trailer-key", "val") + grpc.SetTrailer(ctx, trailer) + + return resp, err } func (s *server) UnaryEcho(ctx context.Context, in *pb.EchoRequest) (*pb.EchoResponse, error) { @@ -89,10 +101,22 @@ func streamInterceptor(srv any, ss grpc.ServerStream, info *grpc.StreamServerInf return errMissingMetadata } + // Create and set metadata from interceptor to server. md.Append("key1", "value1") ctx := metadata.NewIncomingContext(ss.Context(), md) - return handler(srv, &wrappedStream{ss, ctx}) + // Call the handler to complete the normal execution of the RPC. + err := handler(srv, &wrappedStream{ss, ctx}) + + // Create and set header metadata from interceptor to client. + header := metadata.Pairs("header-key", "val") + ss.SetHeader(header) + + // Create and set trailer metadata from interceptor to client. + trailer := metadata.Pairs("trailer-key", "val") + ss.SetTrailer(trailer) + + return err } func (s *server) BidirectionalStreamingEcho(stream pb.Echo_BidirectionalStreamingEchoServer) error {