希曼日记

metadata

本页目录 5
  1. 一元 RPC (Unary RPC)
  2. 流式 RPC (Streaming RPC)
  3. 核心方法对比
  4. 最佳实践
  5. 关键点总结

gRPC Metadata 使用核心指南

一元 RPC (Unary RPC)

客户端发送 Metadata

// 创建初始 Metadata
md := metadata.Pairs("client-id", "12345", "auth-token", "bearer xyz")

// 方式1: 创建新上下文 
ctx := metadata.NewOutgoingContext(context.Background(), md)

// 方式2: 向现有上下文追加
ctx = context.Background()
ctx = metadata.AppendToOutgoingContext(ctx, "client-id", "12345")
ctx = metadata.AppendToOutgoingContext(ctx, "auth-token", "bearer xyz")

// 发起调用
response, err := client.UnaryRPC(ctx, request)

服务器接收 Metadata


func (s *server) UnaryRPC(ctx context.Context, req *pb.Request) (*pb.Response, error) {
    // 获取客户端 Metadata
    md, ok := metadata.FromIncomingContext(ctx)
    if ok {
        // 获取特定值
        clientID := md.Get("client-id")
        authToken := md.Get("auth-token")
    }
    
    // 发送 Header Metadata
    header := metadata.Pairs("server-header", "value")
    grpc.SendHeader(ctx, header)
    
    // 设置 Trailer Metadata
    trailer := metadata.Pairs("server-trailer", "value")
    grpc.SetTrailer(ctx, trailer)
    
    return response, nil
}

客户端接收 Metadata


response, err := client.UnaryRPC(ctx, request)

// 接收 Header
header, err := grpc.Header(ctx)

// 接收 Trailer
trailer := grpc.Trailer(ctx)

流式 RPC (Streaming RPC)

客户端发送 Metadata


// 创建带 Metadata 的上下文
md := metadata.Pairs("stream-id", "stream-001")
ctx := metadata.NewOutgoingContext(context.Background(), md)

// 创建流
stream, err := client.StreamRPC(ctx)

// 追加额外 Metadata
ctx = metadata.AppendToOutgoingContext(ctx, "additional-data", "extra")

服务器接收 Metadata


func (s *server) StreamRPC(stream pb.Service_StreamRPCServer) error {
    // 获取客户端 Metadata
    md, ok := metadata.FromIncomingContext(stream.Context())
    if ok {
        streamID := md.Get("stream-id")
    }
    
    // 发送 Header Metadata
    header := metadata.Pairs("stream-header", "value")
    stream.SendHeader(header)
    
    // 处理流...
    
    // 设置 Trailer Metadata
    trailer := metadata.Pairs("stream-trailer", "value")
    stream.SetTrailer(trailer)
    
    return nil
}

客户端接收 Metadata


// 创建流后立即接收 Header
header, err := stream.Header()

// 流结束后接收 Trailer
trailer := stream.Trailer()

核心方法对比

NewOutgoingContext vs AppendToOutgoingContext

方法用途示例
NewOutgoingContext创建新的带 Metadata 的上下文ctx := metadata.NewOutgoingContext(baseCtx, md)
AppendToOutgoingContext向现有上下文追加 Metadatactx = metadata.AppendToOutgoingContext(ctx, "key", "value")

// 组合使用示例
func prepareContext() context.Context {
    // 初始 Metadata
    baseMD := metadata.Pairs("request-id", "req-123")
    ctx := metadata.NewOutgoingContext(context.Background(), baseMD)
    
    // 动态追加 Metadata
    if authRequired {
        ctx = metadata.AppendToOutgoingContext(ctx, "authorization", token)
    }
    
    // 添加时间戳
    ctx = metadata.AppendToOutgoingContext(ctx, "timestamp", time.Now().Format(time.RFC3339))
    
    return ctx
}

最佳实践

  1. 键命名规范
// 推荐小写 + 连字符格式
 metadata.Pairs("client-id", "123", "auth-token", "xyz")
  1. 二进制数据
// 使用 -bin 后缀标识二进制数据
 binData := []byte{0x01, 0x02}
md := metadata.Pairs("binary-header-bin", string(binData))
  1. 错误处理中的 Metadata
    // 服务器端
    if err != nil {
        trailer := metadata.Pairs("error-details", "Invalid input")
        stream.SetTrailer(trailer)
        return status.Errorf(codes.InvalidArgument, "invalid request")
    }
  1. 客户端接收错误详情
    
    if err != nil {
        st, _ := status.FromError(err)
        details := st.Details()
        trailer := grpc.Trailer(ctx)
    }

关键点总结

  1. 客户端发送:使用 NewOutgoingContext 或 AppendToOutgoingContext

  2. 服务器接收:使用 metadata.FromIncomingContext

  3. 服务器发送

    • Header: grpc.SendHeader() (Unary) 或 stream.SendHeader() (Stream)

    • Trailer: grpc.SetTrailer() (Unary) 或 stream.SetTrailer() (Stream)

  4. 客户端接收

    • Header: grpc.Header() (Unary) 或 stream.Header() (Stream)

    • Trailer: grpc.Trailer() (Unary) 或 stream.Trailer() (Stream)