Interceptor 拦截器 grpc服务端和客户端都提供了interceptor功能,功能类似middleware,很适合在这里处理验证、日志等流程。
在自定义Token认证的示例中,认证信息是由每个服务中的方法处理并认证的,如果有大量的接口方法,这种姿势就太不优雅了,每个接口实现都要先处理认证信息。这个时候interceptor就可以用来解决了这个问题,在请求被转到具体接口之前处理认证信息,一处认证,到处无忧。 在客户端,我们增加一个请求日志,记录请求相关的参数和耗时等等。修改hello_token项目实现:
目录结构 1 2 3 4 5 6 7 8 9 10 11 12 |—— hello_interceptor/ |—— client/ |—— main.go // 客户端 |—— server/ |—— main.go // 服务端 |—— keys/ // 证书目录 |—— server.key |—— server.pem |—— proto/ |—— hello/ |—— hello.proto // proto描述文件 |—— hello.pb.go // proto编译后文件
示例代码 Step 1. 服务端interceptor:
hello_interceptor/server/main.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 package mainimport ( "fmt" "net" pb "hellogrpc/proto/hello" "context" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials" "log" "google.golang.org/grpc/metadata" ) const ( Address = "127.0.0.1:50052" ) type helloService struct {}var HelloService = helloService{}func (h helloService) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error ) { resp := new (pb.HelloResponse) resp.Message = fmt.Sprintf("Hello %s." , in.Name) return resp, nil } func main () { listen, err := net.Listen("tcp" , Address) if err != nil { log.Fatalf("Failed to listen: %v" , err) } var opts []grpc.ServerOption creds, err := credentials.NewServerTLSFromFile("../../keys/server.pem" , "../../keys/server.key" ) if err != nil { log.Fatalf("Failed to generate credentials %v" , err) } opts = append (opts, grpc.Creds(creds)) opts = append (opts, grpc.UnaryInterceptor(interceptor)) s := grpc.NewServer(opts...) pb.RegisterHelloServer(s, HelloService) log.Println("Listen on " + Address + " with TLS + Token + Interceptor" ) s.Serve(listen) } func auth (ctx context.Context) error { md, ok := metadata.FromIncomingContext(ctx) if !ok { return fmt.Errorf("无Token认证信息" ,codes.Unauthenticated) } 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 fmt.Errorf("Token认证信息无效: appid=%s, appkey=%s ,Unauthenticated=%d" , appid, appkey,codes.Unauthenticated) } return nil } func interceptor (ctx context.Context, req interface {}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface {}, error ) { err := auth(ctx) if err != nil { return nil , err } return handler(ctx, req) }
Step 2. 实现客户端interceptor:
hello_intercepror/client/main.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 package mainimport ( "time" pb "hellogrpc/proto/hello" "context" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "log" ) const ( Address = "127.0.0.1:50052" OpenTLS = true ) 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 OpenTLS } func main () { var err error var opts []grpc.DialOption if OpenTLS { creds, err := credentials.NewClientTLSFromFile("../../keys/server.pem" , "yyy" ) if err != nil { log.Fatalf("Failed to create TLS credentials %v" , err) } opts = append (opts, grpc.WithTransportCredentials(creds)) } else { opts = append (opts, grpc.WithInsecure()) } opts = append (opts, grpc.WithPerRPCCredentials(new (customCredential))) opts = append (opts, grpc.WithUnaryInterceptor(interceptor)) conn, err := grpc.Dial(Address, opts...) if err != nil { log.Fatalln(err) } defer conn.Close() c := pb.NewHelloClient(conn) req := &pb.HelloRequest{Name: "gRPC" } res, err := c.SayHello(context.Background(), req) if err != nil { log.Fatalln(err) } log.Println(res.Message) } func interceptor (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...) log.Printf("method=%s req=%v rep=%v duration=%s error=%v\n" , method, req, reply, time.Since(start), err) return err }
运行结果 1 2 $ cd hello_inteceptor/server && go run main.goListen on 127.0.0.1:50052 with TLS + Token + Interceptor
1 2 3 $ cd hello_inteceptor/client && go run main.gomethod=/hello.Hello/SayHello req=name:"gRPC" rep=message:"Hello gRPC." duration=33.879699ms error=<nil> Hello gRPC.
项目推荐: go-grpc-middleware
这个项目对interceptor进行了封装,支持多个拦截器的链式组装,对于需要多种处理的地方使用起来会更方便些。