我正在学习酷酷的 Golang,可点此查看帖子Golang学习笔记汇总。
在 http 请求当中我们可以设置 header 用来传递数据,grpc 底层采用 http2 协议也是支持传递数据的,采用的是 metadata。 Metadata 对于 gRPC 本身来说透明, 它使得 client 和 server 能为对方提供本次调用的信息。就像一次 http 请求的 RequestHeader 和 ResponseHeader,http header 的生命周期是一次 http 请求, Metadata 的生命周期则是一次 RPC 调用。
方法介绍
在 go 语言中,可以用 grpc.WithPerRPCCredentials 方法来实现。
func WithPerRPCCredentials(creds credentials.PerRPCCredentials) DialOption
WithPerRPCCredentials returns a DialOption which sets credentials and places auth state on each outbound RPC.
通常来说,认证信息是需要每次都携带,但如果需要单次携带 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...)
从 RPC 消息的上下文中获取 metadata
func (s *server) SomeRPC(ctx context.Context, in *pb.SomeRequest) (*pb.SomeResponse, err) {
md, ok := metadata.FromIncomingContext(ctx)
// do something with metadata
}
// customCredential 自定义认证
type customCredential struct{}
func (c customCredential) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
return map[string]string{
"appid": "101010",
"appkey": "i am key",
}, nil
}
func (c customCredential) RequireTransportSecurity() bool {
return false
}
// 上面是第1步,实例准备
func main() {
var opts []grpc.DialOption
opts = append(opts, grpc.WithInsecure())
opts = append(opts, grpc.WithBlock())
// 使用自定义认证
opts = append(opts, grpc.WithPerRPCCredentials(new(customCredential)))
// Set up a connection to the server.
conn, err := grpc.Dial(address, opts...)
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := pb.NewGreeterClient(conn)
// Contact the server and print out its response.
name := defaultName
if len(os.Args) > 1 {
name = os.Args[1]
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})
if err != nil {
log.Fatalf("could not greet: %v", err)
}
log.Printf("Greeting: %s", r.GetMessage())
}
服务端使用了 metadata 一个的关键函数,它可从 context 中取出 metadata (map 类型):
// FromIncomingContext returns the incoming metadata in ctx if it exists. The
// returned MD should not be modified. Writing to it may cause races.
// Modification should be made to copies of the returned MD.
func FromIncomingContext(ctx context.Context) (md MD, ok bool) {
md, ok = ctx.Value(mdIncomingKey{}).(MD)
return
}
具体处理如下:
// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
// 解析metada中的信息并验证
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, grpc.Errorf(codes.Unauthenticated, "无Token认证信息")
}
var (
appid string
appkey string
)
if val, ok := md["appid"]; ok {
appid = val[0]
}
if val, ok := md["appkey"]; ok {
appkey = val[0]
}
if appid != "101010" || appkey != "i am key" {
return nil, grpc.Errorf(codes.Unauthenticated, "Token认证信息无效: appid=%s, appkey=%s", appid, appkey)
}
log.Printf("Received: %v.\nToken info: appid=%s,appkey=%s", in.GetName(), appid, appkey)
return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}
Server 端打印如下信息:
# go run greeter_server/main.go
2019/11/13 12:03:21 Received: world.
Token info: appid=101010,appkey=i am key
gRPC 可用 metadata 自定义认证信息。客户端使用 WithPerRPCCredentials 方法,服务端使用 metadata.FromIncomingContext 方法从 RPC 消息的上下文中获取 metadata。