mirror of https://github.com/grpc/grpc-go.git
documentation: on server, use FromIncomingContext for retrieving context and `SetHeader`, `SetTrailer` to send metadata to client (#7238)
This commit is contained in:
parent
7e5898e7c5
commit
6d236200ea
|
|
@ -12,11 +12,11 @@ Four kinds of service method:
|
||||||
- [Client streaming RPC](https://grpc.io/docs/guides/concepts.html#client-streaming-rpc)
|
- [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)
|
- [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
|
## 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:
|
The type MD is actually a map from string to a list of strings:
|
||||||
|
|
||||||
```go
|
```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
|
## 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
|
### Sending metadata
|
||||||
|
|
||||||
|
|
@ -127,7 +117,8 @@ Metadata that a client can receive includes header and trailer.
|
||||||
|
|
||||||
#### Unary call
|
#### 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
|
```go
|
||||||
var header, trailer metadata.MD // variable to store header and trailer
|
var header, trailer metadata.MD // variable to store header and trailer
|
||||||
|
|
@ -149,7 +140,8 @@ For streaming calls including:
|
||||||
- Client streaming RPC
|
- Client streaming RPC
|
||||||
- Bidirectional 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
|
```go
|
||||||
stream, err := client.SomeStreamingRPC(ctx)
|
stream, err := client.SomeStreamingRPC(ctx)
|
||||||
|
|
@ -164,11 +156,13 @@ trailer := stream.Trailer()
|
||||||
|
|
||||||
## Sending and receiving metadata - server side
|
## 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
|
### 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.
|
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.
|
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
|
#### 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.
|
These two functions take a context as the first parameter.
|
||||||
It should be the RPC handler's context or one derived from it:
|
It should be the RPC handler's context or one derived from it:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func (s *server) SomeRPC(ctx context.Context, in *pb.someRequest) (*pb.someResponse, error) {
|
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")
|
header := metadata.Pairs("header-key", "val")
|
||||||
grpc.SendHeader(ctx, header)
|
grpc.SetHeader(ctx, header)
|
||||||
// create and set trailer
|
// create and set trailer
|
||||||
trailer := metadata.Pairs("trailer-key", "val")
|
trailer := metadata.Pairs("trailer-key", "val")
|
||||||
grpc.SetTrailer(ctx, trailer)
|
grpc.SetTrailer(ctx, trailer)
|
||||||
|
|
@ -211,20 +206,39 @@ func (s *server) SomeRPC(ctx context.Context, in *pb.someRequest) (*pb.someRespo
|
||||||
|
|
||||||
#### Streaming call
|
#### 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
|
```go
|
||||||
func (s *server) SomeStreamingRPC(stream pb.Service_SomeStreamingRPCServer) error {
|
func (s *server) SomeStreamingRPC(stream pb.Service_SomeStreamingRPCServer) error {
|
||||||
// create and send header
|
// create and set header
|
||||||
header := metadata.Pairs("header-key", "val")
|
header := metadata.Pairs("header-key", "val")
|
||||||
stream.SendHeader(header)
|
stream.SetHeader(header)
|
||||||
// create and set trailer
|
// create and set trailer
|
||||||
trailer := metadata.Pairs("trailer-key", "val")
|
trailer := metadata.Pairs("trailer-key", "val")
|
||||||
stream.SetTrailer(trailer)
|
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
|
## Updating metadata from a server interceptor
|
||||||
|
|
||||||
An example for updating metadata from a server interceptor is
|
An example for updating metadata from a server interceptor is
|
||||||
available [here](../examples/features/metadata_interceptor/server/main.go).
|
available [here](../examples/features/metadata_interceptor/server/main.go).
|
||||||
|
|
||||||
|
[FromIncomingContext]: <https://pkg.go.dev/google.golang.org/grpc/metadata#FromIncomingContext>
|
||||||
|
[SetHeader]: <https://godoc.org/google.golang.org/grpc#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
|
||||||
|
|
@ -29,6 +29,7 @@ import (
|
||||||
|
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/credentials/insecure"
|
"google.golang.org/grpc/credentials/insecure"
|
||||||
|
"google.golang.org/grpc/metadata"
|
||||||
|
|
||||||
pb "google.golang.org/grpc/examples/features/proto/echo"
|
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")
|
var addr = flag.String("addr", "localhost:50051", "the address to connect to")
|
||||||
|
|
||||||
func callUnaryEcho(ctx context.Context, client pb.EchoClient) {
|
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 {
|
if err != nil {
|
||||||
log.Fatalf("UnaryEcho: %v", err)
|
log.Fatalf("UnaryEcho: %v", err)
|
||||||
}
|
}
|
||||||
fmt.Println("UnaryEcho: ", resp.Message)
|
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) {
|
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)
|
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() {
|
func main() {
|
||||||
|
|
|
||||||
|
|
@ -49,10 +49,22 @@ func unaryInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo,
|
||||||
return nil, errMissingMetadata
|
return nil, errMissingMetadata
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create and set metadata from interceptor to server.
|
||||||
md.Append("key1", "value1")
|
md.Append("key1", "value1")
|
||||||
ctx = metadata.NewIncomingContext(ctx, md)
|
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) {
|
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
|
return errMissingMetadata
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create and set metadata from interceptor to server.
|
||||||
md.Append("key1", "value1")
|
md.Append("key1", "value1")
|
||||||
ctx := metadata.NewIncomingContext(ss.Context(), md)
|
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 {
|
func (s *server) BidirectionalStreamingEcho(stream pb.Echo_BidirectionalStreamingEchoServer) error {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue