在分布式系统中,客户端和服务端之间的通信不仅仅是数据的交换,还涉及到身份验证、日志追踪等额外信息的传递。gRPC 提供了一种名为 Metadata
的机制来满足这种需求。本文将通过一个具体的示例来讲解如何在 Go 语言中使用 gRPC 的 Metadata。
Metadata 是一种键值对结构,它可以在不改变请求或响应消息体的情况下携带额外的信息。这些信息通常用于认证(如 token)、追踪(如 trace id)等场景。值得注意的是,Metadata 键是大小写不敏感的,并且仅支持 ASCII 字符串作为键名。
本文使用的示例是一个简单的问候服务,它不仅返回问候消息,还会打印从客户端传来的元数据。这个例子包括了服务端和客户端两部分代码。
.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())
}
}
g := grpc.NewServer()
proto.RegisterGreeterServer(g, &Server{})
lis, err := net.Listen("tcp", "0.0.0.0:50051")
err = g.Serve(lis)
50051
端口。Greeter
服务到服务器上。SayHello
方法实现func (s *Server) SayHello(ctx context.Context, request *proto.HelloRequest) (*proto.HelloReply, error)
request
,返回响应 HelloReply
。md, ok := metadata.FromIncomingContext(ctx)
if ok {
fmt.Println("get metadata error")
}
ctx
中获取客户端传来的 Metadata。ok == false
,否则可以读取。if nameSlice, ok := md["name"]; ok {
fmt.Println(nameSlice)
for i, e := range nameSlice {
fmt.Println(i, e)
}
}
"name"
的 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)
}
conn, err := grpc.Dial("127.0.0.1:50051", grpc.WithInsecure())
grpc.WithInsecure()
表示不启用 TLS 加密。c := proto.NewGreeterClient(conn)
md := metadata.New(map[string]string{
"name": "bobby",
"pasword": "imooc",
})
ctx := metadata.NewOutgoingContext(context.Background(), md)
ctx
上,用于后续 RPC 调用。⚠️ 注意:gRPC 中 Metadata 是 HTTP Headers 的一种抽象,在 gRPC 中作为附加信息传递。
r, err := c.SayHello(ctx, &proto.HelloRequest{Name: "bobby"})
SayHello
方法。fmt.Println(r.Message)
服务器端实现的主要步骤:
Server
,实现Greeter
服务接口中定义的SayHello
方法。SayHello
方法中,从上下文ctx
中提取元数据,并打印出来。HelloReply
消息。main
函数中,创建 gRPC 服务器,注册服务实现,创建 TCP 监听器,并启动服务器客户端实现的主要步骤:
grpc.Dial
)。Greeter
服务的客户端(使用proto.NewGreeterClient
)。metadata.New
)。metadata.NewOutgoingContext
)。c.SayHello
)。Set-Cookie
),因此是 []string
类型。❗ 注意:gRPC 中没有直接的 Cookie 支持(HTTP 1.1 特性),但可以通过 Metadata 模拟。
metadata.AppendToMD
或 metadata.Pairs
来构造更复杂的 Metadata。Metadata 提供了一种灵活的方式,让开发者能够在不影响原有协议的前提下,增加额外的功能特性,比如安全性和可追踪性。