在http请求当中我们可以设置header用来传递数据,grpc底层采用http2协议也是支持传递数据的,采用的是metadata。
Metadata 对于 gRPC 本身来说透明, 它使得 client 和 server 能为对方提供本次调用的信息。
就像一次 http 请求的 RequestHeader 和 ResponseHeader,http header 的生命周期是一次 http 请求, Metadata 的生命周期则是一次 RPC 调用。
1、创建metadata
MD 类型实际上是map,key是string,value是string类型的slice。
type MD map[string][]string
创建的时候可以像创建普通的map类型一样使用new关键字进行创建:
md := metadata.New(map[string]string{"key1": "val1", "key2": "val2"})
或者使用Pairs创建,相同的key值会被组合成slice。
md := metadata.Pairs(
"key1", "val1",
"key1", "val1-2", // "key1" will have map value []string{"val1", "val1-2"}
"key2", "val2",
)
key不区分大小写,会被统一转成小写。
2、发送metadata
md := metadata.Pairs("key", "val")
// 新建一个有 metadata 的 context
ctx := metadata.NewOutgoingContext(context.Background(), md)
// 单向 RPC
response, err := client.SomeRPC(ctx, someRequest)
3、接收metadata
利用函数 FromIncomingContext
从context中获取metadata:
func (s *server) SomeRPC(ctx context.Context, in *pb.SomeRequest) (*pb.SomeResponse, err) {
md, ok := metadata.FromIncomingContext(ctx)
// do something with metadata
}
1.客户端请求中携带 metadata
在 go 语言中,可以用 grpc.WithPerRPCCredentials
方法来实现。
func WithPerRPCCredentials(creds credentials.PerRPCCredentials) DialOption
通常来说,认证信息是需要每次都携带,但如果需要单次携带 metadata,可以使用 metadata.NewOutgoingContext
方法来创建一个携带 metadata 的 context。
Step1. 实例准备
方法的入参是 PerRPCCredentials 接口,因此需要准备一个实例,实现接口的方法。
type PerRPCCredentials interface {
GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error)
RequireTransportSecurity() bool
}
Step2. 通过 DialOption 新建客户端
opts = append(opts, grpc.WithPerRPCCredentials(new(customCredential)))
conn, err := grpc.Dial(address, opts...)
2.服务端中解析 metadata
从 RPC 消息的上下文中获取 metadata
func (s *server) SomeRPC(ctx context.Context, in *pb.SomeRequest) (*pb.SomeResponse, err) {
md, ok := metadata.FromIncomingContext(ctx)
// do something with metadata
}
Demo:
1.proto 文件编写:
demo.proto
syntax="proto3";
package protos;
service Greeter {
rpc SayHello(HelloRequest) returns(HelloReply){}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
执行:$ protoc --go_out=plugins=grpc:. demo.proto
生成demo.pb.go 文件
2.server端
server.go
package main
import (
"awesomeProject1/metadata/protos"
"context"
"flag"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"log"
"net"
)
var host = "127.0.0.1"
var (
ServiceName = flag.String("ServiceName","hello_service","service name")
Port = flag.Int("Port",50001,"listening port")
)
type server struct {
}
func main() {
flag.Parse()
lis,err := net.Listen("tcp",fmt.Sprintf("127.0.0.1: %d",*Port))
if err != nil {
log.Fatalf("failed to listen:%s",err)
} else {
fmt.Printf("listen at :%d\n",*Port)
}
defer lis.Close()
s := grpc.NewServer()
defer s.GracefulStop()
protos.RegisterGreeterServer(s, &server{})
addr := fmt.Sprintf("%s:%d",host,*Port)
fmt.Printf("server add:%s\n",addr)
if err := s.Serve(lis); err != nil {
fmt.Printf("failed to server: %s",err)
}
}
func (s *server) SayHello (ctx context.Context,in *protos.HelloRequest) (*protos.HelloReply, error) {
md,ok := metadata.FromIncomingContext(ctx)
if !ok {
fmt.Printf("get metadata error")
}
if t,ok := md["timestamp"]; ok {
fmt.Printf("timestamp from metadata:\n")
for i,e := range t {
fmt.Printf(" %d. %s\n",i,e)
}
}
if t1,ok1 := md["key1"]; ok1 {
fmt.Printf("key1 from metadata:\n")
for i,e := range t1 {
fmt.Printf(" %d . %s\n",i,e)
}
}
if len(md) > 0 {
for k,v := range md {
fmt.Printf("%v:%v\n",k,v)
}
}
return &protos.HelloReply{Message:"server: "+ in.Name},nil
}
3.client端
client.go
package main
import (
"awesomeProject1/metadata/protos"
"context"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
"time"
)
const (
timestampFormat = time.StampNano
)
func main() {
conn,err := grpc.Dial("127.0.0.1:50001",grpc.WithInsecure())
if err != nil {
panic(err)
}
client := protos.NewGreeterClient(conn)
md := metadata.Pairs("timestamp",time.Now().Format(timestampFormat))
md = metadata.New(map[string]string{"key1":"val1","key2":"val2"})
ctx := metadata.NewOutgoingContext(context.Background(),md)
resp,err := client.SayHello(ctx,&protos.HelloRequest{Name:"Hello"})
if err == nil {
fmt.Printf("Reply is : %s\n",resp.Message)
} else {
fmt.Printf("call server error:%s\n",err)
}
}
4.运行
进入文件目录
执行:
go run server.go
go run client.go