grpc元数据

grpc元数据
安安gRPC Metadata(元数据) 是 gRPC 通信中非常关键的概念。如果说 Protobuf 消息是信件内容,那么 Metadata 就是信封上的信息(如:寄件人、收件人、加急标记、邮戳)。
它主要用于传递 非业务数据,例如:认证 Token、链路追踪 ID、语言设置、请求来源等。
第一部分:核心概念(通俗版)
- **键值对 (Key-Value)**:Metadata 本质是
map[string][]string。- Key:必须是 ASCII 小写字符(如
authorization,user-id)。 - Value:是字符串切片
[]string。这意味着一个 Key 可以对应多个 Value。
- Key:必须是 ASCII 小写字符(如
- 绑定 Context:Metadata 不能单独发送,必须附着在
context.Context上。 - 方向性:
- **Outgoing (发出)**:客户端发给服务端,或服务端回给客户端。
- **Incoming (接收)**:服务端收到客户端的,或客户端收到服务端的。
- 特殊后缀:如果 Key 以
-bin结尾(如token-bin),表示这是二进制数据,gRPC 会自动进行 Base64 编码。
第二部分:环境准备
沿用之前的项目结构,定义一个简单的 Proto。
文件路径: proto/meta.proto
1 | syntax = "proto3"; |
(记得运行 protoc 生成 Go 代码)
第三部分:完整 Demo 演示
Demo 1:客户端发送,服务端接收 (最常用)
场景:客户端在请求头中携带 authorization (Token) 和 request-id (追踪 ID),服务端解析并打印。
1. 客户端 (client_send.go)
重点:metadata.Pairs 创建数据,metadata.NewOutgoingContext 绑定上下文。
1 | package main |
2. 服务端 (server_receive.go)
*重点:`metadata.FromIncomingContext
你好!gRPC Metadata(元数据) 是 gRPC 通信中非常关键的概念。如果说 Protobuf 消息是信件内容,那么 Metadata 就是信封上的信息(如:寄件人、收件人、加急标记、邮戳)。
它主要用于传递 非业务数据,例如:认证 Token、链路追踪 ID、语言设置、请求来源等。
本节课我将通过 3 个循序渐进的 Demo,带你彻底搞懂 Metadata 的发送、接收、以及响应头处理。
第一部分:核心概念(通俗版)
- **键值对 (Key-Value)**:Metadata 本质是
map[string][]string。- Key:必须是 ASCII 小写字符(如
authorization,user-id)。 - Value:是字符串切片
[]string。这意味着一个 Key 可以对应多个 Value。
- Key:必须是 ASCII 小写字符(如
- 绑定 Context:Metadata 不能单独发送,必须附着在
context.Context上。 - 方向性:
- **Outgoing (发出)**:客户端发给服务端,或服务端回给客户端。
- **Incoming (接收)**:服务端收到客户端的,或客户端收到服务端的。
- 特殊后缀:如果 Key 以
-bin结尾(如token-bin),表示这是二进制数据,gRPC 会自动进行 Base64 编码。
第二部分:环境准备
沿用之前的项目结构,定义一个简单的 Proto。
文件路径: proto/meta.proto
1 | syntax = "proto3"; |
(记得运行 protoc 生成 Go 代码)
第三部分:完整 Demo 演示
Demo 1:客户端发送,服务端接收 (最常用)
场景:客户端在请求头中携带 authorization (Token) 和 request-id (追踪 ID),服务端解析并打印。
1. 客户端 (client_send.go)
重点:metadata.Pairs 创建数据,metadata.NewOutgoingContext 绑定上下文。
1 | package main |
2. 服务端 (server_receive.go)
重点:metadata.FromIncomingContext 提取数据。
1 | package main |
运行结果:
服务端日志会清晰打印出客户端传来的所有元数据。
Demo 2:服务端发送响应元数据,客户端接收
场景:服务端处理完请求后,想在 响应头 (Header) 或 响应尾 (Trailer) 中返回一些信息(如:剩余配额、处理耗时),而不是放在业务消息体里。
1. 服务端 (server_send_resp.go)
重点:使用 grpc.SendHeader 和 grpc.SetTrailer。
注意:在 Unary 模式下,直接操作 Stream 比较麻烦,通常建议在 拦截器 中设置响应头。但为了演示 API,这里展示如何在 Handler 中通过 grpc.SetHeader 设置。
1 | func (s *Server) GetInfo(ctx context.Context, req *proto.Empty) (*proto.Response, error) { |
2. 客户端 (client_recv_resp.go)
重点:使用 grpc.Header 和 grpc.Trailer CallOption 来捕获响应元数据。
1 | func main() { |
Demo 3:二进制元数据 (Binary Metadata)
场景:传输非字符串数据(如 Protobuf 序列化的对象、加密的 Token)。
规则:Key 必须以 -bin 结尾。gRPC 会自动帮你做 Base64 编码/解码。
1. 客户端
1 | // 原始二进制数据 |
2. 服务端
1 | md, _ := metadata.FromIncomingContext(ctx) |
第四部分:常见陷阱与最佳实践 (Pitfalls)
这部分非常重要,能帮你避开 90% 的坑。
1. Context 不可变 (Immutable)
错误写法:
1
2
3ctx := context.Background()
metadata.NewOutgoingContext(ctx, md) // 返回值被忽略了!
client.Do(ctx) // 这里的 ctx 没有元数据!正确写法:
1
2
3ctx := context.Background()
ctx = metadata.NewOutgoingContext(ctx, md) // 必须接收返回值
client.Do(ctx)
2. 追加 vs 覆盖 (Append vs Replace)
metadata.NewOutgoingContext会 覆盖 当前 Context 中已有的同 Key 元数据。如果你想 追加 (保留旧的,增加新的),请使用
metadata.AppendToOutgoingContext。1
2
3
4
5// 覆盖
ctx = metadata.NewOutgoingContext(ctx, metadata.Pairs("token", "new"))
// 追加 (token 列表里会有 old 和 new)
ctx = metadata.AppendToOutgoingContext(ctx, "token", "new")
3. Key 的大小写
- HTTP/2 要求 Header Key 必须是 小写。
- gRPC 会自动将 Key 转为小写。
- 建议:自己写代码时永远用小写,如
user-id而不是User-Id。
4. 不要在 Metadata 中放业务数据
- 错误:把
OrderID,ProductPrice放在 Metadata 里。 - 正确:这些应该放在 Protobuf 消息体里。Metadata 仅用于 横切关注点 (认证、追踪、限流)。
5. 拦截器中的 Metadata
- 在拦截器中修改 Metadata 是最常见的用法。
- 如果在拦截器中添加了 Metadata,记得把新的 Context 传给
handler(ctx, req)。
第五部分:总结归纳表
| 操作方向 | 场景 | 核心 API | 注意事项 |
|---|---|---|---|
| 创建 | 构建元数据 | metadata.Pairs(k, v, ...) |
Value 是 []string |
| 创建 | 构建二进制元数据 | metadata.Pairs("key-bin", val) |
Key 必须以 -bin 结尾 |
| 客户端发送 | 将元数据放入请求 | metadata.NewOutgoingContext(ctx, md) |
必须接收返回的 ctx |
| 客户端发送 | 追加元数据 | metadata.AppendToOutgoingContext(ctx, k, v) |
不覆盖已有的 |
| 服务端接收 | 从请求中提取 | metadata.FromIncomingContext(ctx) |
返回 (md, ok) |
| 服务端发送 | 设置响应头 (Header) | grpc.SetHeader(ctx, md) |
需在返回响应前调用 |
| 服务端发送 | 设置响应尾 (Trailer) | grpc.SetTrailer(ctx, md) |
请求结束后发送 |
| 客户端接收 | 获取响应头/尾 | grpc.Header(&md), grpc.Trailer(&md) |
作为 CallOption 传入 |
| 通用 | 合并元数据 | metadata.Join(md1, md2) |
合并两个 MD 对象 |
学习路线建议
- 先掌握 Demo 1:这是 90% 的场景(客户端传 Token,服务端校验)。
- 理解 Context:明白 Metadata 是存在 Context 里的,Context 是传递的载体。
- 结合拦截器:实际开发中,你很少在业务代码里手动写
metadata.NewOutgoingContext,通常是在 客户端拦截器 里统一注入 Token,在 服务端拦截器 里统一校验。 - 了解响应头:当需要获取服务端返回的限流信息、TraceID 时,再学习 Demo 2 的
grpc.Header用法。

