Interceptor 拦截器

grpc服务端和客户端都提供了interceptor功能,功能类似middleware,很适合在这里处理验证、日志等流程。

在自定义Token认证的示例中,认证信息是由每个服务中的方法处理并认证的,如果有大量的接口方法,这种姿势就太不优雅了,每个接口实现都要先处理认证信息。这个时候interceptor就可以用来解决了这个问题,在请求被转到具体接口之前处理认证信息,一处认证,到处无忧。在客户端,我们增加一个请求日志,记录请求相关的参数和耗时等等。修改hello_token项目实现:

目录结构

  1. |—— hello_interceptor/
  2. |—— client/
  3. |—— main.go // 客户端
  4. |—— server/
  5. |—— main.go // 服务端
  6. |—— keys/ // 证书目录
  7. |—— server.key
  8. |—— server.pem
  9. |—— proto/
  10. |—— hello/
  11. |—— hello.proto // proto描述文件
  12. |—— hello.pb.go // proto编译后文件

示例代码

Step 1. 服务端interceptor:

hello_interceptor/server/main.go

  1. package main
  2. import (
  3. "fmt"
  4. "net"
  5. pb "github.com/jergoo/go-grpc-example/proto/hello"
  6. "golang.org/x/net/context"
  7. "google.golang.org/grpc"
  8. "google.golang.org/grpc/codes" // grpc 响应状态码
  9. "google.golang.org/grpc/credentials" // grpc认证包
  10. "google.golang.org/grpc/grpclog"
  11. "google.golang.org/grpc/metadata" // grpc metadata包
  12. )
  13. const (
  14. // Address gRPC服务地址
  15. Address = "127.0.0.1:50052"
  16. )
  17. // 定义helloService并实现约定的接口
  18. type helloService struct{}
  19. // HelloService Hello服务
  20. var HelloService = helloService{}
  21. // SayHello 实现Hello服务接口
  22. func (h helloService) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error) {
  23. resp := new(pb.HelloResponse)
  24. resp.Message = fmt.Sprintf("Hello %s.", in.Name)
  25. return resp, nil
  26. }
  27. func main() {
  28. listen, err := net.Listen("tcp", Address)
  29. if err != nil {
  30. grpclog.Fatalf("Failed to listen: %v", err)
  31. }
  32. var opts []grpc.ServerOption
  33. // TLS认证
  34. creds, err := credentials.NewServerTLSFromFile("../../keys/server.pem", "../../keys/server.key")
  35. if err != nil {
  36. grpclog.Fatalf("Failed to generate credentials %v", err)
  37. }
  38. opts = append(opts, grpc.Creds(creds))
  39. // 注册interceptor
  40. opts = append(opts, grpc.UnaryInterceptor(interceptor))
  41. // 实例化grpc Server
  42. s := grpc.NewServer(opts...)
  43. // 注册HelloService
  44. pb.RegisterHelloServer(s, HelloService)
  45. grpclog.Println("Listen on " + Address + " with TLS + Token + Interceptor")
  46. s.Serve(listen)
  47. }
  48. // auth 验证Token
  49. func auth(ctx context.Context) error {
  50. md, ok := metadata.FromContext(ctx)
  51. if !ok {
  52. return grpc.Errorf(codes.Unauthenticated, "无Token认证信息")
  53. }
  54. var (
  55. appid string
  56. appkey string
  57. )
  58. if val, ok := md["appid"]; ok {
  59. appid = val[0]
  60. }
  61. if val, ok := md["appkey"]; ok {
  62. appkey = val[0]
  63. }
  64. if appid != "101010" || appkey != "i am key" {
  65. return grpc.Errorf(codes.Unauthenticated, "Token认证信息无效: appid=%s, appkey=%s", appid, appkey)
  66. }
  67. return nil
  68. }
  69. // interceptor 拦截器
  70. func interceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
  71. err := auth(ctx)
  72. if err != nil {
  73. return nil, err
  74. }
  75. // 继续处理请求
  76. return handler(ctx, req)
  77. }

Step 2. 实现客户端interceptor:

hello_intercepror/client/main.go

  1. package main
  2. import (
  3. "time"
  4. pb "github.com/jergoo/go-grpc-example/proto/hello" // 引入proto包
  5. "golang.org/x/net/context"
  6. "google.golang.org/grpc"
  7. "google.golang.org/grpc/credentials" // 引入grpc认证包
  8. "google.golang.org/grpc/grpclog"
  9. )
  10. const (
  11. // Address gRPC服务地址
  12. Address = "127.0.0.1:50052"
  13. // OpenTLS 是否开启TLS认证
  14. OpenTLS = true
  15. )
  16. // customCredential 自定义认证
  17. type customCredential struct{}
  18. // GetRequestMetadata 实现自定义认证接口
  19. func (c customCredential) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
  20. return map[string]string{
  21. "appid": "101010",
  22. "appkey": "i am key",
  23. }, nil
  24. }
  25. // RequireTransportSecurity 自定义认证是否开启TLS
  26. func (c customCredential) RequireTransportSecurity() bool {
  27. return OpenTLS
  28. }
  29. func main() {
  30. var err error
  31. var opts []grpc.DialOption
  32. if OpenTLS {
  33. // TLS连接
  34. creds, err := credentials.NewClientTLSFromFile("../../keys/server.pem", "server name")
  35. if err != nil {
  36. grpclog.Fatalf("Failed to create TLS credentials %v", err)
  37. }
  38. opts = append(opts, grpc.WithTransportCredentials(creds))
  39. } else {
  40. opts = append(opts, grpc.WithInsecure())
  41. }
  42. // 指定自定义认证
  43. opts = append(opts, grpc.WithPerRPCCredentials(new(customCredential)))
  44. // 指定客户端interceptor
  45. opts = append(opts, grpc.WithUnaryInterceptor(interceptor))
  46. conn, err := grpc.Dial(Address, opts...)
  47. if err != nil {
  48. grpclog.Fatalln(err)
  49. }
  50. defer conn.Close()
  51. // 初始化客户端
  52. c := pb.NewHelloClient(conn)
  53. // 调用方法
  54. req := &pb.HelloRequest{Name: "gRPC"}
  55. res, err := c.SayHello(context.Background(), req)
  56. if err != nil {
  57. grpclog.Fatalln(err)
  58. }
  59. grpclog.Println(res.Message)
  60. }
  61. // interceptor 客户端拦截器
  62. func interceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
  63. start := time.Now()
  64. err := invoker(ctx, method, req, reply, cc, opts...)
  65. grpclog.Printf("method=%s req=%v rep=%v duration=%s error=%v\n", method, req, reply, time.Since(start), err)
  66. return err
  67. }

运行结果

  1. $ cd hello_inteceptor/server && go run main.go
  2. Listen on 127.0.0.1:50052 with TLS + Token + Interceptor
  1. $ cd hello_inteceptor/client && go run main.go
  2. method=/hello.Hello/SayHello req=name:"gRPC" rep=message:"Hello gRPC." duration=33.879699ms error=<nil>
  3. Hello gRPC.

项目推荐: go-grpc-middleware

这个项目对interceptor进行了封装,支持多个拦截器的链式组装,对于需要多种处理的地方使用起来会更方便些。