Working with Metadata
1. Creating Metadata
Example: Build MD
md := metadata.New(map[string]string{
"x-request-id": "abc-123",
"authorization": "Bearer " + token,
})
// or
md := metadata.Pairs("x-tenant", "acme", "x-region", "us-east-1")
| Rule | Detail |
|---|---|
| Keys lowercase | Auto-lowered; case-insensitive |
Suffix -bin | Binary metadata (auto base64 over wire) |
Reserved prefix grpc- | Don't use for app metadata |
2. Sending Metadata from Client
Example: Outgoing context
ctx = metadata.AppendToOutgoingContext(ctx, "authorization", "Bearer "+tok)
res, err := client.GetUser(ctx, req)
3. Receiving Metadata on Server
Example: Read incoming MD
md, ok := metadata.FromIncomingContext(ctx)
if ok {
if v := md.Get("authorization"); len(v) > 0 { token = v[0] }
}
| API | Returns |
|---|---|
FromIncomingContext | Map of header values |
md.Get("key") | []string (multiple values allowed) |
4. Sending Headers from Server
| API | Detail |
|---|---|
grpc.SendHeader(ctx, md) | Send before first response message |
grpc.SetHeader(ctx, md) | Merge with future Send |
5. Sending Trailers from Server
| API | Detail |
|---|---|
grpc.SetTrailer(ctx, md) | Sent after final message + status |
| Use | Summary stats, total counts, request id echo |
6. Receiving Headers on Client
Example: Capture headers
var header metadata.MD
res, err := client.GetUser(ctx, req, grpc.Header(&header))
7. Receiving Trailers on Client
Example: Capture trailers
var trailer metadata.MD
res, err := client.GetUser(ctx, req, grpc.Trailer(&trailer))
| Note | Detail |
|---|---|
| Trailers always sent | Even on error |
8. Appending Metadata
| API | Detail |
|---|---|
md.Append(k, v...) | Add to existing key |
metadata.Join(md1, md2) | Merge multiple MDs |
AppendToOutgoingContext | Variadic key/value pairs |
9. Using Binary Metadata
Example: Binary key
md := metadata.Pairs("trace-context-bin", string(binData))
// Decoded on receiver as []byte
10. Forwarding Metadata
Example: Proxy metadata downstream
if md, ok := metadata.FromIncomingContext(ctx); ok {
ctx = metadata.NewOutgoingContext(ctx, md.Copy())
}
res, err := downstream.Call(ctx, ...)
| Best Practice | Detail |
|---|---|
| Filter sensitive | Strip authorization if downstream uses its own |
| Preserve tracing | Always forward traceparent, x-request-id |