Metadata provides a way to send request and response headers and trailers in gRPC. Context carries deadlines, cancellation signals, and request-scoped values.
Import: google.golang.org/grpc/metadata
type MD map[string][]stringMD is a mapping from metadata keys to values. Keys are case-insensitive and stored in lowercase. Values can have multiple entries.
// New creates an MD from a given key-value map
func New(m map[string]string) MD
// Pairs creates an MD from a variable number of key-value pairs
// Keys and values must alternate
func Pairs(kv ...string) MD
// Example
md := metadata.New(map[string]string{
"key1": "val1",
"key2": "val2",
})
md := metadata.Pairs(
"key1", "val1",
"key2", "val2",
)// Append returns a new MD with the appended values
func (md MD) Append(k string, vals ...string) MD
// Get returns the values for a given key
func (md MD) Get(k string) []string
// Set sets the values for a given key, overwriting existing values
func (md MD) Set(k string, vals ...string)
// Delete removes the values for a given key
func (md MD) Delete(k string)
// Len returns the number of key-value pairs
func (md MD) Len() int
// Copy returns a copy of the MD
func (md MD) Copy() MD
// Join joins multiple MDs into a new MD
func Join(mds ...MD) MDExample:
md := metadata.Pairs("key1", "val1")
md = md.Append("key2", "val2", "val3")
vals := md.Get("key2") // ["val2", "val3"]
md.Set("key1", "newval")
md.Delete("key2")// NewOutgoingContext creates a new context with outgoing metadata
func NewOutgoingContext(ctx context.Context, md MD) context.Context
// AppendToOutgoingContext returns a context with appended metadata
// More efficient than creating new MD and using NewOutgoingContext
func AppendToOutgoingContext(ctx context.Context, kv ...string) context.Context
// FromOutgoingContext retrieves outgoing metadata from context
func FromOutgoingContext(ctx context.Context) (MD, bool)Client Example:
// Add metadata to outgoing context
md := metadata.Pairs(
"authorization", "Bearer token123",
"timestamp", time.Now().String(),
)
ctx := metadata.NewOutgoingContext(context.Background(), md)
// Or append to existing context
ctx = metadata.AppendToOutgoingContext(ctx,
"request-id", "12345",
"client-version", "1.0.0",
)
// Make RPC with metadata
response, err := client.SomeMethod(ctx, request)// NewIncomingContext creates a new context with incoming metadata
func NewIncomingContext(ctx context.Context, md MD) context.Context
// FromIncomingContext retrieves incoming metadata from context
func FromIncomingContext(ctx context.Context) (MD, bool)Server Example:
func (s *server) SomeMethod(ctx context.Context, req *pb.Request) (*pb.Response, error) {
// Read incoming metadata
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, status.Error(codes.Internal, "no metadata")
}
// Extract specific values
tokens := md.Get("authorization")
if len(tokens) == 0 {
return nil, status.Error(codes.Unauthenticated, "missing authorization")
}
// Process request
// ...
return &pb.Response{}, nil
}// Send metadata in context
ctx := metadata.AppendToOutgoingContext(ctx, "key", "value")
// Capture response headers and trailers
var header, trailer metadata.MD
r, err := client.SomeMethod(ctx, &req,
grpc.Header(&header),
grpc.Trailer(&trailer))
// Access metadata
log.Println("Header:", header)
log.Println("Trailer:", trailer)// Create stream with metadata
ctx := metadata.AppendToOutgoingContext(ctx, "key", "value")
stream, err := client.StreamingMethod(ctx)
// Get header (blocks until available)
header, err := stream.Header()
// Send messages
stream.Send(&req)
// Get trailer (after stream completes)
trailer := stream.Trailer()// In google.golang.org/grpc package
// SetHeader sets header metadata (can be called multiple times)
func SetHeader(ctx context.Context, md metadata.MD) error
// SendHeader sends header metadata (can only be called once)
func SendHeader(ctx context.Context, md metadata.MD) error
// SetTrailer sets trailer metadata
func SetTrailer(ctx context.Context, md metadata.MD) errorfunc (s *server) SomeMethod(ctx context.Context, req *pb.Request) (*pb.Response, error) {
// Set header (will be sent before first response)
header := metadata.Pairs("header-key", "header-value")
if err := grpc.SetHeader(ctx, header); err != nil {
return nil, err
}
// Or send immediately
if err := grpc.SendHeader(ctx, header); err != nil {
return nil, err
}
// Set trailer (will be sent after response)
trailer := metadata.Pairs("trailer-key", "trailer-value")
grpc.SetTrailer(ctx, trailer)
return &pb.Response{}, nil
}func (s *server) StreamingMethod(stream pb.Service_StreamingMethodServer) error {
// Set header
header := metadata.Pairs("stream-id", "12345")
if err := stream.SetHeader(header); err != nil {
return err
}
// Or send header immediately
if err := stream.SendHeader(header); err != nil {
return err
}
// Process stream
for {
req, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
return err
}
// Send response
if err := stream.Send(&pb.Response{}); err != nil {
return err
}
}
// Set trailer before returning
trailer := metadata.Pairs("request-count", "100")
stream.SetTrailer(trailer)
return nil
}Metadata keys ending in "-bin" are treated as binary data and automatically base64 encoded/decoded.
// Binary metadata
md := metadata.Pairs(
"key-bin", string([]byte{0x00, 0x01, 0x02}),
)
// gRPC automatically handles base64 encoding/decodingThe peer package provides access to peer information (client/server address and credentials).
Import: google.golang.org/grpc/peer
type Peer struct {
Addr net.Addr // Remote address
LocalAddr net.Addr // Local address (experimental)
AuthInfo credentials.AuthInfo // Auth information
}import "google.golang.org/grpc/peer"
// Client side - get peer info from context
func (s *server) SomeMethod(ctx context.Context, req *pb.Request) (*pb.Response, error) {
p, ok := peer.FromContext(ctx)
if ok {
log.Printf("Client address: %v", p.Addr)
log.Printf("Auth info: %v", p.AuthInfo)
}
return &pb.Response{}, nil
}
// Client side - capture peer info with call option
var p peer.Peer
r, err := client.SomeMethod(ctx, &req, grpc.Peer(&p))
log.Printf("Server address: %v", p.Addr)// NewContext creates a new context with peer information
func NewContext(ctx context.Context, p *Peer) context.Context
// FromContext retrieves peer information from context
func FromContext(ctx context.Context) (*Peer, bool)// Method returns the method string for the server context
// Returns format: "/service/method"
func Method(ctx context.Context) (string, bool)
// MethodFromServerStream returns method string from server stream
func MethodFromServerStream(stream ServerStream) (string, bool)Example:
func (s *server) UnaryMethod(ctx context.Context, req *pb.Request) (*pb.Response, error) {
method, ok := grpc.Method(ctx)
if ok {
log.Printf("Method called: %s", method)
// method = "/mypackage.MyService/UnaryMethod"
}
return &pb.Response{}, nil
}
func (s *server) StreamMethod(stream pb.Service_StreamMethodServer) error {
method, ok := grpc.MethodFromServerStream(stream)
if ok {
log.Printf("Stream method: %s", method)
}
return nil
}context.WithTimeout or context.WithDeadlinecontext.WithCancel for early terminationimport (
"context"
"log"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/peer"
)
func makeRequest(client pb.ServiceClient) {
// Create context with timeout
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// Add outgoing metadata
ctx = metadata.AppendToOutgoingContext(ctx,
"authorization", "Bearer token123",
"request-id", "unique-id-456",
"client-version", "1.0.0",
)
// Prepare to capture response metadata and peer info
var header, trailer metadata.MD
var p peer.Peer
// Make unary call
resp, err := client.UnaryMethod(ctx, &pb.Request{},
grpc.Header(&header),
grpc.Trailer(&trailer),
grpc.Peer(&p),
)
if err != nil {
log.Fatalf("RPC failed: %v", err)
}
// Access response metadata
log.Printf("Response header: %v", header)
log.Printf("Response trailer: %v", trailer)
log.Printf("Server address: %v", p.Addr)
log.Printf("Response: %v", resp)
}
func makeStreamingRequest(client pb.ServiceClient) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
// Add metadata
ctx = metadata.AppendToOutgoingContext(ctx,
"stream-id", "stream-789",
)
// Create stream
stream, err := client.StreamingMethod(ctx)
if err != nil {
log.Fatalf("Failed to create stream: %v", err)
}
// Get header (blocks until available)
header, err := stream.Header()
if err != nil {
log.Fatalf("Failed to get header: %v", err)
}
log.Printf("Stream header: %v", header)
// Send and receive
for i := 0; i < 5; i++ {
if err := stream.Send(&pb.Request{}); err != nil {
log.Fatalf("Send failed: %v", err)
}
resp, err := stream.Recv()
if err != nil {
log.Fatalf("Recv failed: %v", err)
}
log.Printf("Received: %v", resp)
}
// Close send side
if err := stream.CloseSend(); err != nil {
log.Fatalf("CloseSend failed: %v", err)
}
// Get trailer
trailer := stream.Trailer()
log.Printf("Stream trailer: %v", trailer)
}import (
"context"
"io"
"log"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/peer"
"google.golang.org/grpc/status"
)
type server struct {
pb.UnimplementedServiceServer
}
func (s *server) UnaryMethod(ctx context.Context, req *pb.Request) (*pb.Response, error) {
// Read incoming metadata
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, status.Error(codes.Internal, "no metadata received")
}
// Extract and validate auth token
tokens := md.Get("authorization")
if len(tokens) == 0 {
return nil, status.Error(codes.Unauthenticated, "missing auth token")
}
log.Printf("Auth token: %s", tokens[0])
// Get method and peer info
method, _ := grpc.Method(ctx)
p, _ := peer.FromContext(ctx)
log.Printf("Method: %s, Client: %v", method, p.Addr)
// Set response header
header := metadata.Pairs(
"server-version", "1.0.0",
"response-time", time.Now().String(),
)
if err := grpc.SendHeader(ctx, header); err != nil {
return nil, err
}
// Process request
// ...
// Set trailer
trailer := metadata.Pairs(
"server-timing", "total;dur=50",
)
grpc.SetTrailer(ctx, trailer)
return &pb.Response{}, nil
}
func (s *server) StreamingMethod(stream pb.Service_StreamingMethodServer) error {
ctx := stream.Context()
// Read incoming metadata
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return status.Error(codes.Internal, "no metadata received")
}
log.Printf("Stream metadata: %v", md)
// Get method and peer
method, _ := grpc.MethodFromServerStream(stream)
p, _ := peer.FromContext(ctx)
log.Printf("Stream method: %s, Client: %v", method, p.Addr)
// Send header
header := metadata.Pairs("stream-started", "true")
if err := stream.SendHeader(header); err != nil {
return err
}
// Process stream
messageCount := 0
for {
req, err := stream.Recv()
if err == io.EOF {
break
}
if err != nil {
return err
}
messageCount++
// Send response
if err := stream.Send(&pb.Response{}); err != nil {
return err
}
}
// Set trailer
trailer := metadata.Pairs(
"message-count", fmt.Sprintf("%d", messageCount),
)
stream.SetTrailer(trailer)
return nil
}// Client interceptor to add metadata to all requests
func metadataUnaryInterceptor(ctx context.Context, method string, req, reply interface{},
cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
// Add metadata to all outgoing requests
ctx = metadata.AppendToOutgoingContext(ctx,
"client-id", "my-client",
"timestamp", time.Now().Format(time.RFC3339),
)
return invoker(ctx, method, req, reply, cc, opts...)
}
// Server interceptor to validate metadata
func metadataServerInterceptor(ctx context.Context, req interface{},
info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
// Validate incoming metadata
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, status.Error(codes.InvalidArgument, "missing metadata")
}
clientIDs := md.Get("client-id")
if len(clientIDs) == 0 {
return nil, status.Error(codes.InvalidArgument, "missing client-id")
}
// Continue with handler
return handler(ctx, req)
}