1. metadata 介绍
在 HTTP/1.1 中,我们常常通过直接操纵 Header 来传递数据,而对于 gRPC 来讲,因为它是基于 HTTP/2 的,所以本质上也可以通过 Header 来传递数据,但不是直接去操纵它,而是通过 gRPC 中的 metadata 来调用过程中的数据进行传递和操纵的。
metadata 是以 key-value 的形式存储数据的,其中 key 是 string 类型,而 value 是 []string 类型,即一个字符串数组。metadata 使得 client 和 server 能够为对方提供关于本次调用的一些信息,就像一次 http 请求的 Request Header 和 Response Header 一样。http 中Header 的声明周期是一次 http 请求,那么 metadata 的生命周期就是一次 RPC 调用。
在 gRPC 中,metadata 实际上就是一个 map 结构,原型如下:
type MD map[string][]string
2. 创建 metadata
google.golang.org/grpc/metadata 源码中提供了两个方法来创建 metadata。
- metadata.New
- metadata.Pairs
2.1 metadata.New
metadata.New(map[string]string{"name":"bobo", "password":"123456"})
使用 New 方法创建的 metadata 会直接被转换为对应的 MD 结构,并且所有的 key 默认都被小写,参考结果如下:
name: []string{"bobo"}
password: []string{"123456"}
我们也可以看一下 metadata.New 的源码。
func New(m map[string]string) MD {
md := MD{}
for k, val := range m {
key := strings.ToLower(k)
md[key] = append(md[key], val)
}
return md
}
2.2 metadata.Pairs
metadata.Pairs(
"name", "bobo",
"password", "123456",
"name", "张三",
)
使用 Pairs 方法创建的 metadata 会以奇数来配对,并且所有的 key 默认都被小写,若出现同名的 key,则会追加到对应 key 的切片上,参考结果如下:
name: []string{"bobo", "张三"}
password: []string{"123456"}
我们也可以看一下 metadata.Pairs 的源码。
func Pairs(kv ...string) MD {
if len(kv)%2 == 1 {
panic(fmt.Sprintf("metadata: Pairs got the odd number of input pairs for metadata: %d", len(kv)))
}
md := MD{}
for i := 0; i < len(kv); i += 2 {
key := strings.ToLower(kv[i])
md[key] = append(md[key], kv[i+1])
}
return md
}
3. 设置/获取 metadata
3.1 设置 metadata
md := metadata.Pairs(
"name", "bobo",
"password", "123456",
)
//新建一个有 metadata 的context
ctx := metadata.NewOutgoingContext(context.Background(), md)
//单项 RPC
response, err := client.SayHello(ctx, &proto.HelloRequest{
Name: "AI",
})
3.2 获取 metadata
func (s *Server) SayHello(ctx context.Context, request *proto.HelloRequest) (*proto.HelloReply, error) {
//获取 metadata
md, ok := metadata.FromIncomingContext(ctx)
return &proto.HelloReply{
Message: "hello " + request.Name,
}, nil
}
4. metadata 使用
4.1 Proto
syntax = "proto3";
//设置 pb.go 文件的保存目录和包名
option go_package = "proto;proto";
service Greeter {
rpc SayHello(HelloRequest) returns (HelloReply);
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
4.2 Server 服务端
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"net"
"protobuf-demo/proto"
)
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 names := md.Get("name"); len(names) > 0 {
fmt.Printf("name=%s\n", names[0])
}
if ps := md.Get("password"); len(ps) > 0 {
fmt.Printf("password=%s\n", ps[0])
}
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:8080")
if err != nil {
panic("failed to listen:" + err.Error())
}
err = g.Serve(lis)
if err != nil {
panic("failed to start grpc:" + err.Error())
}
}
4.3 Client 客户端
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"protobuf-demo/proto"
)
func main() {
var opts = []grpc.DialOption{
grpc.WithInsecure(),
}
conn, err := grpc.Dial("127.0.0.1:8080", opts...)
defer conn.Close()
if err != nil {
panic(err)
}
md := metadata.Pairs("name", "bobo", "password", "123456")
ctx := metadata.NewOutgoingContext(context.Background(), md)
client := proto.NewGreeterClient(conn)
r, err := client.SayHello(ctx, &proto.HelloRequest{
Name: "AI",
})
if err != nil {
panic(err)
}
fmt.Println(r.Message)
}