The Metadata API provides utilities for managing request metadata (headers) across RPC boundaries in both HTTP and gRPC transports.
Types
Represents request headers internally.
type Metadata map[string][]string
Metadata translates back and forth from transport headers (HTTP headers or gRPC metadata).
Functions
New
Creates Metadata from key-value maps.
func New(mds ...map[string][]string) Metadata
Variable number of key-value maps to merge into Metadata
New Metadata instance with merged values
Example:
md := metadata.New(
map[string][]string{
"authorization": {"Bearer token123"},
"x-request-id": {"req-001"},
},
)
NewServerContext
Creates a new context with server metadata attached.
func NewServerContext(ctx context.Context, md Metadata) context.Context
Context with server metadata
FromServerContext
Returns the server metadata from context.
func FromServerContext(ctx context.Context) (Metadata, bool)
Context to retrieve metadata from
Whether metadata was found in the context
Example:
if md, ok := metadata.FromServerContext(ctx); ok {
token := md.Get("authorization")
fmt.Printf("Token: %s\n", token)
}
NewClientContext
Creates a new context with client metadata attached.
func NewClientContext(ctx context.Context, md Metadata) context.Context
Context with client metadata
FromClientContext
Returns the client metadata from context.
func FromClientContext(ctx context.Context) (Metadata, bool)
Context to retrieve metadata from
Whether metadata was found in the context
AppendToClientContext
Returns a new context with provided key-value pairs merged into existing metadata.
func AppendToClientContext(ctx context.Context, kv ...string) context.Context
Context with existing metadata
Key-value pairs to add (must be even number of arguments)
Context with appended metadata
Example:
ctx = metadata.AppendToClientContext(ctx,
"x-trace-id", "trace-123",
"x-user-id", "user-456",
)
MergeToClientContext
Merges new metadata into the client context.
func MergeToClientContext(ctx context.Context, cmd Metadata) context.Context
Context with existing metadata
Context with merged metadata
Example:
md := metadata.New(map[string][]string{
"authorization": {"Bearer token"},
"content-type": {"application/json"},
})
ctx = metadata.MergeToClientContext(ctx, md)
Add
Adds a key-value pair to the metadata.
func (m Metadata) Add(key, value string)
Header key (case-insensitive, stored as lowercase)
Example:
md := metadata.New()
md.Add("x-custom-header", "value1")
md.Add("x-custom-header", "value2")
// Results in: x-custom-header: ["value1", "value2"]
Get
Returns the first value associated with the key.
func (m Metadata) Get(key string) string
Header key to retrieve (case-insensitive)
First value associated with the key, or empty string if not found
Set
Stores a key-value pair, replacing existing values.
func (m Metadata) Set(key string, value string)
Header key (case-insensitive)
Example:
md.Set("authorization", "Bearer new-token")
Values
Returns all values associated with the key.
func (m Metadata) Values(key string) []string
Header key to retrieve (case-insensitive)
All values associated with the key
Range
Iterates over all elements in the metadata.
func (m Metadata) Range(f func(k string, v []string) bool)
f
func(k string, v []string) bool
Function called for each key-value pair. Return false to stop iteration.
Example:
md.Range(func(key string, values []string) bool {
fmt.Printf("%s: %v\n", key, values)
return true // continue iteration
})
Clone
Returns a deep copy of the metadata.
func (m Metadata) Clone() Metadata
Usage Examples
package main
import (
"context"
"fmt"
"github.com/go-kratos/kratos/v2/metadata"
)
func handleRequest(ctx context.Context) {
if md, ok := metadata.FromServerContext(ctx); ok {
// Get single header value
token := md.Get("authorization")
userAgent := md.Get("user-agent")
// Get all values for a key
cookies := md.Values("cookie")
fmt.Printf("Token: %s\n", token)
fmt.Printf("User-Agent: %s\n", userAgent)
fmt.Printf("Cookies: %v\n", cookies)
// Iterate all headers
md.Range(func(key string, values []string) bool {
fmt.Printf("%s: %v\n", key, values)
return true
})
}
}
func makeRequest(ctx context.Context) {
// Method 1: Using AppendToClientContext
ctx = metadata.AppendToClientContext(ctx,
"authorization", "Bearer token123",
"x-request-id", "req-001",
"x-user-id", "user-456",
)
// Method 2: Creating and merging metadata
md := metadata.New(map[string][]string{
"content-type": {"application/json"},
"accept": {"application/json"},
})
ctx = metadata.MergeToClientContext(ctx, md)
// Make gRPC or HTTP request with ctx
client.SomeMethod(ctx, req)
}
import (
"github.com/go-kratos/kratos/v2/middleware"
"github.com/go-kratos/kratos/v2/metadata"
)
func ForwardHeaders() middleware.Middleware {
return func(handler middleware.Handler) middleware.Handler {
return func(ctx context.Context, req interface{}) (interface{}, error) {
// Extract headers from incoming request
if md, ok := metadata.FromServerContext(ctx); ok {
// Forward specific headers to downstream services
ctx = metadata.AppendToClientContext(ctx,
"x-request-id", md.Get("x-request-id"),
"x-trace-id", md.Get("x-trace-id"),
"authorization", md.Get("authorization"),
)
}
return handler(ctx, req)
}
}
}
Authentication Middleware
func Authentication() middleware.Middleware {
return func(handler middleware.Handler) middleware.Handler {
return func(ctx context.Context, req interface{}) (interface{}, error) {
md, ok := metadata.FromServerContext(ctx)
if !ok {
return nil, errors.Unauthorized("UNAUTHORIZED", "missing metadata")
}
token := md.Get("authorization")
if token == "" {
return nil, errors.Unauthorized("UNAUTHORIZED", "missing auth token")
}
// Validate token
user, err := validateToken(token)
if err != nil {
return nil, errors.Unauthorized("UNAUTHORIZED", "invalid token")
}
// Add user info to context
ctx = metadata.AppendToClientContext(ctx,
"x-user-id", user.ID,
"x-user-role", user.Role,
)
return handler(ctx, req)
}
}
}
Request Tracing
import (
"github.com/google/uuid"
"github.com/go-kratos/kratos/v2/metadata"
)
func TraceMiddleware() middleware.Middleware {
return func(handler middleware.Handler) middleware.Handler {
return func(ctx context.Context, req interface{}) (interface{}, error) {
md, _ := metadata.FromServerContext(ctx)
// Get or generate trace ID
traceID := md.Get("x-trace-id")
if traceID == "" {
traceID = uuid.New().String()
}
// Get or generate request ID
requestID := md.Get("x-request-id")
if requestID == "" {
requestID = uuid.New().String()
}
// Forward to downstream services
ctx = metadata.AppendToClientContext(ctx,
"x-trace-id", traceID,
"x-request-id", requestID,
)
return handler(ctx, req)
}
}
}
func customMetadataOps() {
// Create metadata
md := metadata.New()
// Add headers
md.Set("content-type", "application/json")
md.Set("authorization", "Bearer token")
md.Add("accept-language", "en-US")
md.Add("accept-language", "en")
// Get values
contentType := md.Get("content-type")
languages := md.Values("accept-language")
// Clone for modification
mdCopy := md.Clone()
mdCopy.Set("x-custom", "value")
// Iterate
md.Range(func(key string, values []string) bool {
fmt.Printf("%s: %v\n", key, values)
return true
})
}