客户端传 metadata 必须显式挂载,不能仅用 context.WithValue;服务端需在 handler 第一行调用 metadata.FromIncomingContext 读取,且 key 自动转小写、二进制值须 base64 编码加 -bin 后缀。

客户端传 metadata 必须显式挂载,不能只塞 context
直接用 context.WithValue 或自己封装的 context 工具函数往 ctx 里塞 map、struct、自定义结构体,服务端一定收不到。gRPC 的 metadata 不是靠 context.Value 透传的,它依赖 HTTP/2 header 的序列化机制。
正确做法只有两种:
-
metadata.AppendToOutgoingContext(ctx, "key1", "val1", "key2", "val2")—— 推荐,会合并已有元数据 -
metadata.NewOutgoingContext(ctx, md)—— 会覆盖 ctx 中原有 metadata,慎用
注意:metadata.Pairs 只接受偶数个字符串参数,奇数个或 key/value 错位会 panic;键名自动转小写("Auth-Token" → "auth-token"),服务端必须用小写取值。
服务端读 metadata 必须在 handler 第一行调用 metadata.FromIncomingContext
常见错误是:在中间件、日志、鉴权逻辑之后才调 metadata.FromIncomingContext。只要中间任何一次 context.WithTimeout、context.WithValue 或日志库(如 grpc-zap)新建了 ctx,原始 metadata 就断链了。
务必这样写:
func (s *MyService) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.GetUserResponse, error) {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
// 没收到任何 metadata,不是空 map
return nil, status.Error(codes.InvalidArgument, "missing metadata")
}
userID := md.Get("user-id")
if len(userID) == 0 {
return nil, status.Error(codes.Unauthenticated, "user-id required")
}
// 后续逻辑...
}
不要用 md["user-id"],要用 md.Get("user-id"),它返回 []string;取单值记得判空再取 [0],否则 panic。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
Header 和 Trailer 的发送时机和约束很严格
grpc.SendHeader 必须在首次响应前调用,grpc.SetTrailer 必须在 RPC 返回前调用。晚了就丢弃,客户端收不到。
Header 常用于认证、trace-id、client-version;Trailer 常用于 process-time、audit-log、错误上下文。但要注意:
- HTTP/2 网关(如 gRPC-Web)通常不转发 Trailer,浏览器客户端基本收不到
- nginx 默认不透传未知 header,需加配置:
grpc_set_header X-User-ID $http_x_user_id;+grpc_pass_request_headers on; - 服务端监听必须是
grpc.Server,不是普通 HTTP server;HTTP/1.1 网关会丢光所有 metadata
二进制值和非法键名会导致静默失败或截断
中文、JSON、字节切片等非 ASCII 值不能直接当 value 传。必须 base64 编码,并加 -bin 后缀:
binaryData := []byte{0x48, 0x65, 0x6c, 0x6c, 0x6f} // "Hello"
md := metadata.Pairs(
"payload-bin", base64.StdEncoding.EncodeToString(binaryData),
)
键名有硬限制:
- 必须以字母或
-开头 - 只允许小写字母、数字、
_、-、. - 禁止大写(
"X-User-ID"→ 自动转成"x-user-id",但某些代理可能不识别)、禁止空格、禁止grpc-开头(框架保留)
最容易被忽略的是:经 Envoy/Nginx 代理或重试时,metadata 极易丢失——它不是强保障机制,本质是 HTTP/2 header,依赖整条链路的兼容性与配置。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










