Some checks failed
codecov / upload (push) Failing after 13m59s
CodeQL / Analyze (push) Failing after 1s
govulncheck / govulncheck (.) (push) Failing after 1s
govulncheck / govulncheck (./cmd/protoc-gen-go-grpc) (push) Failing after 1s
govulncheck / govulncheck (./gcp/observability) (push) Failing after 1s
govulncheck / govulncheck (./security/advancedtls) (push) Failing after 1s
govulncheck / govulncheck (./stats/opencensus) (push) Failing after 1s
Testing / vet-proto (push) Failing after 1s
Testing / tests (arm, latest) (push) Waiting to run
Testing / extras (latest) (push) Failing after 1s
Testing / static checks (latest-1) (push) Failing after 1s
Testing / tests (-race, latest) (push) Failing after 1s
Testing / tests (i386, latest) (push) Failing after 1s
Testing / tests (latest) (push) Failing after 1s
Testing / tests (latest-1) (push) Failing after 1s
Lock Threads / lock (push) Failing after 1s
Stale bot / stale (push) Failing after 1s
245 lines
7.6 KiB
Markdown
245 lines
7.6 KiB
Markdown
# Metadata
|
|
|
|
gRPC supports sending metadata between client and server.
|
|
This doc shows how to send and receive metadata in gRPC-go.
|
|
|
|
## Background
|
|
|
|
Four kinds of service method:
|
|
|
|
- [Unary RPC](https://grpc.io/docs/guides/concepts.html#unary-rpc)
|
|
- [Server streaming RPC](https://grpc.io/docs/guides/concepts.html#server-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)
|
|
|
|
And concept of [metadata].
|
|
|
|
## Constructing metadata
|
|
|
|
A metadata can be created using package [metadata].
|
|
The type MD is actually a map from string to a list of strings:
|
|
|
|
```go
|
|
type MD map[string][]string
|
|
```
|
|
|
|
Metadata can be read like a normal map.
|
|
Note that the value type of this map is `[]string`,
|
|
so that users can attach multiple values using a single key.
|
|
|
|
### Creating a new metadata
|
|
|
|
A metadata can be created from a `map[string]string` using function `New`:
|
|
|
|
```go
|
|
md := metadata.New(map[string]string{"key1": "val1", "key2": "val2"})
|
|
```
|
|
|
|
Another way is to use `Pairs`.
|
|
Values with the same key will be merged into a list:
|
|
|
|
```go
|
|
md := metadata.Pairs(
|
|
"key1", "val1",
|
|
"key1", "val1-2", // "key1" will have map value []string{"val1", "val1-2"}
|
|
"key2", "val2",
|
|
)
|
|
```
|
|
|
|
__Note:__ all the keys will be automatically converted to lowercase,
|
|
so "key1" and "kEy1" will be the same key and their values will be merged into the same list.
|
|
This happens for both `New` and `Pairs`.
|
|
|
|
### Storing binary data in metadata
|
|
|
|
In metadata, keys are always strings. But values can be strings or binary data.
|
|
To store binary data value in metadata, simply add "-bin" suffix to the key.
|
|
The values with "-bin" suffixed keys will be encoded when creating the metadata:
|
|
|
|
```go
|
|
md := metadata.Pairs(
|
|
"key", "string value",
|
|
"key-bin", string([]byte{96, 102}), // this binary data will be encoded (base64) before sending
|
|
// and will be decoded after being transferred.
|
|
)
|
|
```
|
|
|
|
## Sending and receiving metadata - client side
|
|
|
|
Client side metadata sending and receiving examples are available
|
|
[here](../examples/features/metadata/client/main.go).
|
|
|
|
### Sending metadata
|
|
|
|
There are two ways to send metadata to the server. The recommended way is to append kv pairs to the context using
|
|
`AppendToOutgoingContext`. This can be used with or without existing metadata on the context. When there is no prior
|
|
metadata, metadata is added; when metadata already exists on the context, kv pairs are merged in.
|
|
|
|
```go
|
|
// create a new context with some metadata
|
|
ctx := metadata.AppendToOutgoingContext(ctx, "k1", "v1", "k1", "v2", "k2", "v3")
|
|
|
|
// later, add some more metadata to the context (e.g. in an interceptor)
|
|
ctx := metadata.AppendToOutgoingContext(ctx, "k3", "v4")
|
|
|
|
// make unary RPC
|
|
response, err := client.SomeRPC(ctx, someRequest)
|
|
|
|
// or make streaming RPC
|
|
stream, err := client.SomeStreamingRPC(ctx)
|
|
```
|
|
|
|
Alternatively, metadata may be attached to the context using `NewOutgoingContext`. However, this
|
|
replaces any existing metadata in the context, so care must be taken to preserve the existing
|
|
metadata if desired. This is slower than using `AppendToOutgoingContext`. An example of this
|
|
is below:
|
|
|
|
```go
|
|
// create a new context with some metadata
|
|
md := metadata.Pairs("k1", "v1", "k1", "v2", "k2", "v3")
|
|
ctx := metadata.NewOutgoingContext(context.Background(), md)
|
|
|
|
// later, add some more metadata to the context (e.g. in an interceptor)
|
|
send, _ := metadata.FromOutgoingContext(ctx)
|
|
newMD := metadata.Pairs("k3", "v3")
|
|
ctx = metadata.NewOutgoingContext(ctx, metadata.Join(send, newMD))
|
|
|
|
// make unary RPC
|
|
response, err := client.SomeRPC(ctx, someRequest)
|
|
|
|
// or make streaming RPC
|
|
stream, err := client.SomeStreamingRPC(ctx)
|
|
```
|
|
|
|
### Receiving metadata
|
|
|
|
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] and [Trailer] in [CallOption]:
|
|
|
|
```go
|
|
var header, trailer metadata.MD // variable to store header and trailer
|
|
r, err := client.SomeRPC(
|
|
ctx,
|
|
someRequest,
|
|
grpc.Header(&header), // will retrieve header
|
|
grpc.Trailer(&trailer), // will retrieve trailer
|
|
)
|
|
|
|
// do something with header and trailer
|
|
```
|
|
|
|
#### Streaming call
|
|
|
|
For streaming calls including:
|
|
|
|
- Server streaming RPC
|
|
- Client streaming RPC
|
|
- Bidirectional streaming RPC
|
|
|
|
Header and trailer can be retrieved from the returned stream using function
|
|
`Header` and `Trailer` in interface [ClientStream]:
|
|
|
|
```go
|
|
stream, err := client.SomeStreamingRPC(ctx)
|
|
|
|
// retrieve header
|
|
header, err := stream.Header()
|
|
|
|
// retrieve trailer
|
|
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).
|
|
|
|
### Receiving metadata
|
|
|
|
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.
|
|
|
|
#### Unary call
|
|
|
|
```go
|
|
func (s *server) SomeRPC(ctx context.Context, in *pb.someRequest) (*pb.someResponse, error) {
|
|
md, ok := metadata.FromIncomingContext(ctx)
|
|
// do something with metadata
|
|
}
|
|
```
|
|
|
|
#### Streaming call
|
|
|
|
```go
|
|
func (s *server) SomeStreamingRPC(stream pb.Service_SomeStreamingRPCServer) error {
|
|
md, ok := metadata.FromIncomingContext(stream.Context()) // get context from stream
|
|
// do something with metadata
|
|
}
|
|
```
|
|
|
|
### Sending metadata
|
|
|
|
#### Unary call
|
|
|
|
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 set header
|
|
header := metadata.Pairs("header-key", "val")
|
|
grpc.SetHeader(ctx, header)
|
|
// create and set trailer
|
|
trailer := metadata.Pairs("trailer-key", "val")
|
|
grpc.SetTrailer(ctx, trailer)
|
|
}
|
|
```
|
|
|
|
#### Streaming call
|
|
|
|
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 set header
|
|
header := metadata.Pairs("header-key", "val")
|
|
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]: <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
|