Go 中 gRPC Metadata 使用详解

发布于:2025-06-29 ⋅ 阅读:(21) ⋅ 点赞:(0)

在分布式系统中,客户端和服务端之间的通信不仅仅是数据的交换,还涉及到身份验证、日志追踪等额外信息的传递。gRPC 提供了一种名为 Metadata 的机制来满足这种需求。本文将通过一个具体的示例来讲解如何在 Go 语言中使用 gRPC 的 Metadata。

一、简介

Metadata 是一种键值对结构,它可以在不改变请求或响应消息体的情况下携带额外的信息。这些信息通常用于认证(如 token)、追踪(如 trace id)等场景。值得注意的是,Metadata 键是大小写不敏感的,并且仅支持 ASCII 字符串作为键名。

二、示例代码概览

本文使用的示例是一个简单的问候服务,它不仅返回问候消息,还会打印从客户端传来的元数据。这个例子包括了服务端和客户端两部分代码。

三、Protocol Buffer 定义(.proto 文件)

syntax = "proto3";
option go_package = ".;proto";  // 生成的 Go 代码所在的包名是 proto

service Greeter {
    rpc SayHello (HelloRequest) returns (HelloReply);  // 一个远程方法
}

message HelloRequest {
    string name = 1;
}

message HelloReply {
    string message = 1;
}

解释:

  • 定义了一个服务接口 Greeter,其中有一个远程调用方法 SayHello
  • 请求参数是 HelloRequest,包含字段 name
  • 返回值是 HelloReply,包含字段 message

这个 .proto 文件会被编译成 Go 代码供服务端和客户端使用。


四、服务端代码详解

package main

import (
	"GolandProjects/awesomeProject1/grpc_test2/metadata_test/proto"
	"context"
	"fmt"
	"net"

	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc"
)

type Server struct{}

func (s *Server) SayHello(ctx context.Context, request *proto.HelloRequest) (*proto.HelloReply, error) {

	md, ok := metadata.FromIncomingContext(ctx)
	if ok {
		fmt.Println("get metadata error")
	}
	if nameSlice, ok := md["name"]; ok {
		fmt.Println(nameSlice)
		for i, e := range nameSlice {
			fmt.Println(i, e)
		}
	}
	return &proto.HelloReply{
		Message: "hello " + request.Name,
	}, nil
}

func main() {
	g := grpc.NewServer()
	proto.RegisterGreeterServer(g, &Server{})
	lis, err := net.Listen("tcp", "0.0.0.0:50051")
	if err != nil {
		panic("failed to listen:" + err.Error())
	}
	err = g.Serve(lis)
	if err != nil {
		panic("failed to start grpc:" + err.Error())
	}
}

 功能说明:

1. 创建并启动 gRPC 服务
g := grpc.NewServer()
proto.RegisterGreeterServer(g, &Server{})
lis, err := net.Listen("tcp", "0.0.0.0:50051")
err = g.Serve(lis)
  • 启动一个 gRPC 服务器监听 50051 端口。
  • 注册 Greeter 服务到服务器上。
2. SayHello 方法实现
func (s *Server) SayHello(ctx context.Context, request *proto.HelloRequest) (*proto.HelloReply, error)
  • 这是客户端调用的方法。
  • 接收请求参数 request,返回响应 HelloReply
获取元数据(Metadata)
md, ok := metadata.FromIncomingContext(ctx)
if ok {
    fmt.Println("get metadata error")
}
  • 从上下文 ctx 中获取客户端传来的 Metadata。
  • 如果没有 Metadata,ok == false,否则可以读取。
if nameSlice, ok := md["name"]; ok {
    fmt.Println(nameSlice)
    for i, e := range nameSlice {
        fmt.Println(i, e)
    }
}
  • 检查是否有键为 "name" 的 Metadata。
  • 输出它的内容(注意 Metadata 是字符串切片)。
构造返回值
return &proto.HelloReply{
    Message: "hello " + request.Name,
}, nil
  • 返回一条问候语。

 五、客户端代码详解

package main

import (
	"GolandProjects/awesomeProject1/grpc_test2/metadata_test/proto"
	"context"
	"fmt"
	"google.golang.org/grpc"
	"google.golang.org/grpc/metadata"
)

func main() {
	conn, err := grpc.Dial("127.0.0.1:50051", grpc.WithInsecure())
	if err != nil {
		panic(err)
	}
	defer conn.Close()

	c := proto.NewGreeterClient(conn)

	md := metadata.New(map[string]string{
		"name":    "bobby",
		"pasword": "imooc",
	})
	ctx := metadata.NewOutgoingContext(context.Background(), md)
	r, err := c.SayHello(ctx, &proto.HelloRequest{Name: "bobby"})
	if err != nil {
		panic(err)
	}
	fmt.Println(r.Message)
}

 功能说明:

1. 建立 gRPC 连接
conn, err := grpc.Dial("127.0.0.1:50051", grpc.WithInsecure())
  • 连接到本地运行的 gRPC 服务。
  • grpc.WithInsecure() 表示不启用 TLS 加密。
2. 创建客户端对象
c := proto.NewGreeterClient(conn)
  • 使用连接创建一个客户端实例。
3. 添加 Metadata 到请求上下文中
md := metadata.New(map[string]string{
	"name":    "bobby",
	"pasword": "imooc",
})
ctx := metadata.NewOutgoingContext(context.Background(), md)
  • 创建 Metadata,包含两个键值对。
  • 将其绑定到新的上下文 ctx 上,用于后续 RPC 调用。

⚠️ 注意:gRPC 中 Metadata 是 HTTP Headers 的一种抽象,在 gRPC 中作为附加信息传递。

4. 发起远程调用
r, err := c.SayHello(ctx, &proto.HelloRequest{Name: "bobby"})
  • 调用服务端的 SayHello 方法。
  • 传入请求参数和带有 Metadata 的上下文。
5. 输出响应结果
fmt.Println(r.Message)
  • 打印服务端返回的问候语。

服务器端实现的主要步骤:

  1. 创建一个结构体Server,实现Greeter服务接口中定义的SayHello方法。
  2. SayHello方法中,从上下文ctx中提取元数据,并打印出来。
  3. 返回一个包含问候消息的HelloReply消息。
  4. main函数中,创建 gRPC 服务器,注册服务实现,创建 TCP 监听器,并启动服务器

客户端实现的主要步骤:

  1. 连接到 gRPC 服务器(使用grpc.Dial)。
  2. 创建Greeter服务的客户端(使用proto.NewGreeterClient)。
  3. 创建元数据(使用metadata.New)。
  4. 将元数据添加到上下文(使用metadata.NewOutgoingContext)。
  5. 调用服务方法(使用c.SayHello)。
  6. 处理响应并打印结果。

六、总结

关于 Metadata 的说明

  • 在 gRPC 中,Metadata 是 key-value 形式的附加信息。
  • 可以用来传输 session id、认证 token、trace id 等信息。
  • 不属于请求体,而是类似于 HTTP Header 的存在。
  • 支持多个值(例如 Set-Cookie),因此是 []string 类型。

 常见用途举例

  • 用户身份验证(token)
  • 日志追踪 ID(trace-id, span-id)
  • 客户端版本信息
  • Session ID(如题中提到的“将 sessionid 放入 cookie”)

❗ 注意:gRPC 中没有直接的 Cookie 支持(HTTP 1.1 特性),但可以通过 Metadata 模拟。


 最佳实践建议

  • Metadata 键名推荐小写(避免大小写问题)。
  • 敏感信息如密码应加密或避免通过 Metadata 传输。
  • 使用 metadata.AppendToMD 或 metadata.Pairs 来构造更复杂的 Metadata。

Metadata 提供了一种灵活的方式,让开发者能够在不影响原有协议的前提下,增加额外的功能特性,比如安全性和可追踪性。


    网站公告

    今日签到

    点亮在社区的每一天
    去签到