一、grpc中使用metadata
什么是metadata :
gRPC让我们可以像本地调用一样实现远程调用,对于每一次的RPC调用中,都可能会有一些有用的数据,而这些数据就可以通过metadata来传递。 metadata是以key-value的形式存储数据的,其中key是string类型,而value是[]string,即一个字符串切片。 metadata使得client和server能够为对方提供关于本次调用的一些信息,就像一次http请求的RequestHeader和ResponseHeader一样。 http中header的生命周周期是一次http请求,那么metadata的生命周期就是一次RPC调用。 metadata的两种新建方法
md := metadata. New ( map [ string ] string { "key1" : "val1" , "key2" : "val2" } )
md := metadata. Pairs (
"key1" , "val1" ,
"key1" , "val1-2" ,
"key2" , "val2" ,
)
md := metadata. Pairs ( "key" , "val" )
ctx := metadata. NewOutgoingContext ( context. Background ( ) , md)
response, err := client. SomeRpc ( ctx, someRequest)
func ( s * server) SomeRPC ( ctx context. Context, in * pb. SomeRequest) ( * pb. SomeResponse, error ) {
md, ok := metadata. FromIncomingContext ( ctx)
}
二、metadata实例
syntax = "proto3" ;
option go_package = ".;proto" ;
service Greeter {
rpc SayHello ( HelloRequest) returns ( HelloReply) ;
}
message HelloRequest {
string name = 1 ;
}
message HelloReply {
string message = 1 ;
}
package main
import (
"context"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/metadata"
"test_project/metadata_test/proto"
)
func main ( ) {
conn, err := grpc. Dial ( "127.0.0.1:50051" , grpc. WithTransportCredentials ( insecure. NewCredentials ( ) ) )
if err != nil {
return
}
defer conn. Close ( )
c := proto. NewGreeterClient ( conn)
md := metadata. New ( map [ string ] string {
"name" : "test" ,
"pasword" : "hello" ,
} )
ctx := metadata. NewOutgoingContext ( context. Background ( ) , md)
r, err := c. SayHello ( ctx, & proto. HelloRequest{ Name: "kelly" } )
if err != nil {
return
}
fmt. Println ( r. Message)
}
package main
import (
"context"
"fmt"
"google.golang.org/grpc/metadata"
"net"
"google.golang.org/grpc"
"test_project/metadata_test/proto"
)
type Server struct {
proto. UnimplementedGreeterServer
}
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" )
}
for key, val := range md {
fmt. Println ( key, val)
}
fmt. Println ( "================" )
if nameSlice, ok := md[ "name" ] ; ok {
fmt. Println ( nameSlice)
for i, e := range nameSlice {
fmt. Println ( i, e)
}
}
fmt. Println ( "================" )
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 {
return
}
err = g. Serve ( lis)
if err != nil {
return
}
}
三、grpc拦截器
grpc拦截器 :grpc服务端和客户端都提供了拦截器interceptor功能,功能类似中间件go-grpc-middleware,很适合在这里处理验证、日志等流程server.go
package main
import (
"MyTestProject/grpc_interpretor/proto"
"context"
"fmt"
"net"
"time"
"google.golang.org/grpc"
)
type Server struct {
proto. UnimplementedGreeterServer
}
func ( s * Server) SayHello ( ctx context. Context, request * proto. HelloRequest) ( * proto. HelloReply,
error ) {
time. Sleep ( 2 * time. Second)
return & proto. HelloReply{
Message: "hello " + request. Name,
} , nil
}
func main ( ) {
interceptor := func ( ctx context. Context, req interface { } , info * grpc. UnaryServerInfo, handler grpc. UnaryHandler) ( resp interface { } , err error ) {
fmt. Println ( "接收到了一个新的请求" )
res, err := handler ( ctx, req)
fmt. Println ( "请求已经完成" )
return res, err
}
opt := grpc. UnaryInterceptor ( interceptor)
g := grpc. NewServer ( opt)
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 ( ) )
}
}
package main
import (
"MyTestProject/grpc_interpretor/proto"
"context"
"fmt"
"google.golang.org/grpc/credentials/insecure"
"time"
"google.golang.org/grpc"
)
func main ( ) {
interceptor := func ( ctx context. Context, method string , req, reply interface { } , cc * grpc. ClientConn, invoker grpc. UnaryInvoker, opts ... grpc. CallOption) error {
start := time. Now ( )
err := invoker ( ctx, method, req, reply, cc, opts... )
fmt. Printf ( "耗时:%s\n" , time. Since ( start) )
return err
}
var opts [ ] grpc. DialOption
opts = append ( opts, grpc. WithTransportCredentials ( insecure. NewCredentials ( ) ) )
opts = append ( opts, grpc. WithUnaryInterceptor ( interceptor) )
conn, err := grpc. Dial ( "127.0.0.1:50051" , opts... )
if err != nil {
panic ( err)
}
defer conn. Close ( )
c := proto. NewGreeterClient ( conn)
r, err := c. SayHello ( context. Background ( ) , & proto. HelloRequest{ Name: "bobby" } )
if err != nil {
panic ( err)
}
fmt. Println ( r. Message)
}
四、grpc的auth认证
package main
import (
"MyTestProject/grpc_token_auth_test/proto"
"context"
"fmt"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
"net"
"time"
"google.golang.org/grpc"
)
type Server struct {
proto. UnimplementedGreeterServer
}
func ( s * Server) SayHello ( ctx context. Context, request * proto. HelloRequest) ( * proto. HelloReply,
error ) {
time. Sleep ( 2 * time. Second)
return & proto. HelloReply{
Message: "hello " + request. Name,
} , nil
}
func main ( ) {
interceptor := func ( ctx context. Context, req interface { } , info * grpc. UnaryServerInfo, handler grpc. UnaryHandler) ( resp interface { } , err error ) {
fmt. Println ( "接收到了一个新的请求" )
md, ok := metadata. FromIncomingContext ( ctx)
fmt. Println ( md)
if ! ok {
return resp, status. Error ( codes. Unauthenticated, "无token认证信息" )
}
var (
appid string
appkey string
)
if va1, ok := md[ "appid" ] ; ok {
appid = va1[ 0 ]
}
if va1, ok := md[ "appkey" ] ; ok {
appkey = va1[ 0 ]
}
if appid != "101010" || appkey != "i am key" {
return resp, status. Error ( codes. Unauthenticated, "无token认证信息" )
}
res, err := handler ( ctx, req)
fmt. Println ( "请求已经完成" )
return res, err
}
opt := grpc. UnaryInterceptor ( interceptor)
g := grpc. NewServer ( opt)
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 ( ) )
}
}
package main
import (
"MyTestProject/grpc_token_auth_test/proto"
"context"
"fmt"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/metadata"
"time"
"google.golang.org/grpc"
)
func main ( ) {
interceptor := func ( ctx context. Context, method string , req, reply interface { } , cc * grpc. ClientConn, invoker grpc. UnaryInvoker, opts ... grpc. CallOption) error {
start := time. Now ( )
md := metadata. New ( map [ string ] string {
"appid" : "10101" ,
"appkey" : "i am key" ,
} )
ctx = metadata. NewOutgoingContext ( context. Background ( ) , md)
err := invoker ( ctx, method, req, reply, cc, opts... )
fmt. Printf ( "耗时:%s\n" , time. Since ( start) )
return err
}
var opts [ ] grpc. DialOption
opts = append ( opts, grpc. WithTransportCredentials ( insecure. NewCredentials ( ) ) )
opts = append ( opts, grpc. WithUnaryInterceptor ( interceptor) )
conn, err := grpc. Dial ( "127.0.0.1:50051" , opts... )
if err != nil {
panic ( err)
}
defer conn. Close ( )
c := proto. NewGreeterClient ( conn)
r, err := c. SayHello ( context. Background ( ) , & proto. HelloRequest{ Name: "bobby" } )
if err != nil {
panic ( err)
}
fmt. Println ( r. Message)
}
第二种方法使用grpc的WithPerRPCCredentials
package main
import (
"MyTestProject/grpc_token_auth_test/proto"
"context"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
type customCredential struct { }
func ( c customCredential) GetRequestMetadata ( ctx context. Context, uri ... string ) ( map [ string ] string , error ) {
return map [ string ] string {
"appid" : "10101" ,
"appkey" : "i am key" ,
} , nil
}
func ( c customCredential) RequireTransportSecurity ( ) bool {
return false
}
func main ( ) {
var opts [ ] grpc. DialOption
opts = append ( opts, grpc. WithTransportCredentials ( insecure. NewCredentials ( ) ) )
opts = append ( opts, grpc. WithPerRPCCredentials ( customCredential{ } ) )
conn, err := grpc. Dial ( "127.0.0.1:50051" , opts... )
if err != nil {
panic ( err)
}
defer conn. Close ( )
c := proto. NewGreeterClient ( conn)
r, err := c. SayHello ( context. Background ( ) , & proto. HelloRequest{ Name: "bobby" } )
if err != nil {
panic ( err)
}
fmt. Println ( r. Message)
}
五、grpc验证器
六、grpc的状态码与异常处理
package main
import (
"MyTestProject/grpc_token_auth_test/proto"
"context"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"net"
"google.golang.org/grpc"
)
type Server struct {
proto. UnimplementedGreeterServer
}
func ( s * Server) SayHello ( ctx context. Context, request * proto. HelloRequest) ( * proto. HelloReply,
error ) {
return nil , status. Errorf ( codes. NotFound, "记录未找到:%s" , request. Name)
}
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 ( ) )
}
}
package main
import (
"MyTestProject/grpc_token_auth_test/proto"
"context"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/status"
"time"
)
func main ( ) {
conn, err := grpc. Dial ( "127.0.0.1:50051" , grpc. WithTransportCredentials ( insecure. NewCredentials ( ) ) )
if err != nil {
panic ( err)
}
defer conn. Close ( )
c := proto. NewGreeterClient ( conn)
_ , err = c. SayHello ( context. Background ( ) , & proto. HelloRequest{ Name: "bobby" } )
if err != nil {
st, ok := status. FromError ( err)
if ! ok {
panic ( "解析error失败" )
}
fmt. Println ( st. Message ( ) )
fmt. Println ( st. Code ( ) )
}
}
七、grpc的超时机制
grpc超时机制的实现 :主要就是在client添加context的超时设置ctx, _ := context.WithTimeout(context.Background(), time.Second*3)
client.go
package main
import (
"MyTestProject/grpc_token_auth_test/proto"
"context"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/status"
"time"
)
func main ( ) {
conn, err := grpc. Dial ( "127.0.0.1:50051" , grpc. WithTransportCredentials ( insecure. NewCredentials ( ) ) )
if err != nil {
panic ( err)
}
defer conn. Close ( )
c := proto. NewGreeterClient ( conn)
ctx, _ := context. WithTimeout ( context. Background ( ) , time. Second* 3 )
_ , err = c. SayHello ( ctx, & proto. HelloRequest{ Name: "bobby" } )
if err != nil {
st, ok := status. FromError ( err)
if ! ok {
panic ( "解析error失败" )
}
fmt. Println ( st. Message ( ) )
fmt. Println ( st. Code ( ) )
}
}
server.go :为了模拟超时,server在return前增加一个sleeptime.Sleep(time.Second * 5)
package main
import (
"MyTestProject/grpc_token_auth_test/proto"
"context"
"net"
"time"
"google.golang.org/grpc"
)
type Server struct {
proto. UnimplementedGreeterServer
}
func ( s * Server) SayHello ( ctx context. Context, request * proto. HelloRequest) ( * proto. HelloReply,
error ) {
time. Sleep ( time. Second * 5 )
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 ( ) )
}
}
八、proto生成的go源文件
syntax = "proto3" ;
option go_package = ".;proto" ;
package proto;
service Greeter {
rpc SayHello ( HelloRequest) returns ( HelloReply) ;
}
message HelloRequest {
string name = 1 ;
}
message HelloReply {
string message = 1 ;
}
从proto中我们看有2个message :分别是用于请求的HelloRequest和用于响应的HelloReplyHelloRequest和HelloReply对应pb.go的源码结构体
我们更关注结构体中Name和Message字段:可以看到后面的字段既支持protobuf也支持json
type HelloRequest struct {
state protoimpl. MessageState
sizeCache protoimpl. SizeCache
unknownFields protoimpl. UnknownFields
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
}
type HelloReply struct {
state protoimpl. MessageState
sizeCache protoimpl. SizeCache
unknownFields protoimpl. UnknownFields
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
}
service Greeter {
rpc SayHello ( HelloRequest) returns ( HelloReply) ;
}
helloworld_grpc.pb.go中关于server的部分
自动帮我们改名为GreeterServer的接口 生成注册方法:RegisterGreeterServer
type GreeterServer interface {
SayHello ( context. Context, * HelloRequest) ( * HelloReply, error )
mustEmbedUnimplementedGreeterServer ( )
}
func RegisterGreeterServer ( s grpc. ServiceRegistrar, srv GreeterServer) {
s. RegisterService ( & Greeter_ServiceDesc, srv)
}
helloworld_grpc.pb.go中关于client的部分
生成了client的创建方法:NewGreeterClient;这个方法返回的是GreeterClient的接口 同时我们看到NewGreeterClient返回的是greeterClient的这个struct greeterClient的这个struct实现了方法SayHello,这也就是go中的鸭子类型的体现 err := c.cc.Invoke(ctx, "/proto.Greeter/SayHello", in, out, opts...)
:这个也就是实现了之前我们分析的CallID的需求
type GreeterClient interface {
SayHello ( ctx context. Context, in * HelloRequest, opts ... grpc. CallOption) ( * HelloReply, error )
}
type greeterClient struct {
cc grpc. ClientConnInterface
}
func NewGreeterClient ( cc grpc. ClientConnInterface) GreeterClient {
return & greeterClient{ cc}
}
func ( c * greeterClient) SayHello ( ctx context. Context, in * HelloRequest, opts ... grpc. CallOption) ( * HelloReply, error ) {
out := new ( HelloReply)
err := c. cc. Invoke ( ctx, "/proto.Greeter/SayHello" , in, out, opts... )
if err != nil {
return nil , err
}
return out, nil
}