[TOC]

This article has participated in the weekend study program, click the link to see details: juejin.cn/post/696572…

Take a look at gRPC’s interceptor

There are four kinds of gRPC authentication, and two of them are commonly used and important:

  • The OpenSSL certificate can be used for authentication
  • The client can also place data into the metadata for authentication by the server

But my friends, have you ever thought, if every client and server communication interface to be authenticated, then this will be very redundant, and every interface implementation to be authenticated, this is really too uncomfortable

As programmers, we should explore efficient ways to solve some complicated and redundant things.

Today we will share the gRPC Interceptor, which is similar to the middleware in the Web framework.

What is middleware?

A class of computer software that provides a connection between system software and application software to facilitate communication between software components. It provides services beyond the operating system for software applications. It is vividly described as “software glue”

To put it bluntly, middleware is a communication bridge between system software and application software. For example, it can record the response time, record the request and response data log, and so on

The middleware can intercept the request sent to the handler and the response returned by the handler to the client

What is an interceptor?

Interceptors are the middleware in the gRPC ecosystem

RPC requests and responses can be intercepted on either the client or the server.

What can an interceptor do?

Haha, it can do a lot of things, the final point is that the interceptor can do the authentication work of the same interface, no longer need to do authentication for every interface, multiple interfaces access multiple times, just need to do authentication in the same place

This greatly improves the efficiency of interface usage and authentication, while reducing code redundancy

What are the categories of interceptors?

According to different emphasis, there are two categories as follows:

The focus is different, and the interceptors are classified differently, but the way they are used is pretty much the same.

How to use interceptors?

The method used by the server

UnaryServerInterceptor provides a hook to intercept the execution of a single RPC on the server. The interceptor is responsible for calling the handler to complete the RPC

UnaryHandler in the argument defines the handler called by UnaryServerInterceptor

The method used by the client

type UnaryClientInterceptor func(CTX context. context, // context methodstring// The name of the RPC. For example, here we use gRPC req, replyinterface{}, // the corresponding request and response message cc *ClientConn, // cc is calling RPC ClientConn Invoker UnaryInvoker, // Invoker is the completion of RPC handler, mainly calls it is the interceptor opts... CallOption) error	// optsContains all applicable invocation options, including fromClientConnAs well as each call option
Copy the code

Overall case code structure

The code structure is consistent with the structure shared in the last two articles. This interceptor is to do authentication uniformly and put the authentication place in the same place, instead of dispersing it to every interface

For detailed proto source code, check out my previous article, which shows the structure of the code

Start writing cases

  • Add interceptor functionality to the original code, and register an interceptor in the current case
  • gRPC + openssl + token + interceptor

server.go

  • Mainly to joinUnaryServerInterceptorTo apply interceptors
package main

import (
   "fmt"
   "google.golang.org/grpc/codes"
   "google.golang.org/grpc/metadata"
   "log"
   "net"

   pb "myserver/protoc/hi"

   "golang.org/x/net/context"
   "google.golang.org/grpc"
   "google.golang.org/grpc/credentials" // Introduce the GRPC authentication package
)

const (
   // Address gRPC service Address
   Address = "127.0.0.1:9999"
)

// Define the helloService interface and implement the convention
type HiService struct{}

// HiService Hello service
var HiSer = HiService{}

SayHello implements the Hello service interface
func (h HiService) SayHi(ctx context.Context, in *pb.HiRequest) (*pb.HiResponse, error) {

   // Parse the information in metada and verify
   md, ok := metadata.FromIncomingContext(ctx)
   if! ok {return nil, grpc.Errorf(codes.Unauthenticated, "no token ")}var (
      appId  string
      appKey string
   )

   // md is a map[string][]string
   if val, ok := md["appid"]; ok {
      appId = val[0]}if val, ok := md["appkey"]; ok {
      appKey = val[0]}ifappId ! ="myappid"|| appKey ! ="mykey" {
      return nil, grpc.Errorf(codes.Unauthenticated, "token invalide: appid=%s, appkey=%s", appId, appKey)
   }

   resp := new(pb.HiResponse)
   resp.Message = fmt.Sprintf("Hi %s.", in.Name)

   return resp, nil
}

/ / authentication token
func myAuth(ctx context.Context) error {
   md, ok := metadata.FromIncomingContext(ctx)
   if! ok {return grpc.Errorf(codes.Unauthenticated, "no token ")
   }

   log.Println("myAuth ...")

   var (
      appId  string
      appKey string
   )

   // md is a map[string][]string
   if val, ok := md["appid"]; ok {
      appId = val[0]}if val, ok := md["appkey"]; ok {
      appKey = val[0]}ifappId ! ="myappid"|| appKey ! ="mykey" {
      return grpc.Errorf(codes.Unauthenticated, "token invalide: appid=%s, appkey=%s", appId, appKey)
   }

   return nil
}

// Interceptor
func interceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
   // Perform certification
   log.Println("interceptor...")
   err := myAuth(ctx)
   iferr ! =nil {
      return nil, err
   }

   // Continue processing the request
   return handler(ctx, req)
}

func main(a) {
   log.SetFlags(log.Ltime | log.Llongfile)

   listen, err := net.Listen("tcp", Address)
   iferr ! =nil {
      log.Panicf("Failed to listen: %v", err)
   }

   var opts []grpc.ServerOption

   / / TLS authentication
   creds, err := credentials.NewServerTLSFromFile("./keys/server.pem"."./keys/server.key")
   iferr ! =nil {
      log.Panicf("Failed to generate credentials %v", err)
   }

   opts = append(opts, grpc.Creds(creds))

   // Register an interceptor
   opts = append(opts, grpc.UnaryInterceptor(interceptor))

   // Instantiate GRPC Server and enable TLS authentication, including interceptor
   s := grpc.NewServer(opts...)

   / / HelloService registration
   pb.RegisterHiServer(s, HiSer)

   log.Println("Listen on " + Address + " with TLS and interceptor")

   s.Serve(listen)
}
Copy the code

client.go

  • Mainly to joinUnaryClientInterceptorTo apply interceptors
package main

import (
   "log"
   pb "myclient/protoc/hi" // Introduce the proto package
   "time"

   "golang.org/x/net/context"
   "google.golang.org/grpc"
   "google.golang.org/grpc/credentials" // Introduce the GRPC authentication package
   "google.golang.org/grpc/grpclog"
)

const (
   // Address gRPC service Address
   Address = "127.0.0.1:9999"
)

var IsTls = true

// myCredential custom authentication
type myCredential struct{}

// GetRequestMetadata implements a custom authentication interface
func (c myCredential) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
   return map[string]string{
      "appid":  "myappid"."appkey": "mykey",},nil
}

// RequireTransportSecurity Specifies whether TLS is enabled for custom authentication
func (c myCredential) RequireTransportSecurity(a) bool {
   return IsTls
}

// Client interceptor
func Clientinterceptor(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
}

func main(a) {
   log.SetFlags(log.Ltime | log.Llongfile)
   // Remember to change XXX to the server address you wrote

   var err error
   var opts []grpc.DialOption

   if IsTls {
      // Enable TLS authentication
      creds, err := credentials.NewClientTLSFromFile("./keys/server.pem"."www.eline.com")
      iferr ! =nil {
         log.Panicf("Failed to create TLS mycredentials %v", err)
      }
      opts = append(opts, grpc.WithTransportCredentials(creds))
   } else {
      opts = append(opts, grpc.WithInsecure())
   }

   New (myCredential); new(myCredential); new(myCredential)
   opts = append(opts, grpc.WithPerRPCCredentials(new(myCredential)))

   // Add the interceptor
   opts = append(opts, grpc.WithUnaryInterceptor(Clientinterceptor))

   conn, err := grpc.Dial(Address, opts...)
   iferr ! =nil {
      grpclog.Fatalln(err)
   }

   defer conn.Close()

   // Initialize the client
   c := pb.NewHiClient(conn)

   // Call the method
   req := &pb.HiRequest{Name: "gRPC"}
   res, err := c.SayHi(context.Background(), req)
   iferr ! =nil {
      log.Panicln(err)
   }
   log.Println(res.Message)

   // Call again intentionally
   res, err = c.SayHi(context.Background(), req)
   iferr ! =nil {
      log.Panicln(err)
   }

   log.Println(res.Message)
}
Copy the code

Actual effect display

Note that the server can only be configured with one UnaryInterceptor and one StreamClientInterceptor, otherwise it will report an error. The client will also report an error, although it will not report an error, but only the last one will count. If you want to configure more than one, use a chain of interceptors, such as go-GrPC-Middleware, or implement them yourself.

  • Interceptors on the server
    • UnaryServerInterceptor— interceptor for one-way calls
    • StreamServerInterceptor— Interceptor called by stream
  • Interceptor for the client
    • UnaryClientInterceptor
    • StreamClientInterceptor

The above interceptors are used much the same for both one-way calls and stream calls

/ / the server
type UnaryServerInterceptor func(ctx context.Context, req interface{}, info *UnaryServerInfo, handler UnaryHandler) (resp interface{}, err error)

type StreamServerInterceptor func(srv interface{}, ss ServerStream, info *StreamServerInfo, handler StreamHandler) error

/ / the client
type UnaryClientInterceptor func(ctx context.Context, method string, req, reply interface{}, cc *ClientConn, invoker UnaryInvoker, opts ... CallOption) error

type StreamClientInterceptor func(ctx context.Context, desc *StreamDesc, cc *ClientConn, method string, streamer Streamer, opts ... CallOption) (ClientStream, error)
Copy the code

Finally share the interceptors used in the community (there should be more…)

Finally, I would like to share with you a few interceptors used in the community

Used to authenticate interceptors

  • grpc_auth: Github.com/grpc-ecosys…

Interceptor is a library of chained capabilities that combine unidirectional or streaming interceptors

  • grpc-multi-interceptor: Github.com/kazegusuri/…
  • go-grpc-middleware: Github.com/grpc-ecosys…

Add contextTagMap object

  • grpc_ctxtags: Github.com/grpc-ecosys…

The logging framework

  • grpc_zap: Github.com/grpc-ecosys…
  • Logrus:github.com/grpc-ecosys…

You can add retry functionality to the client

  • grpc_retry: Github.com/grpc-ecosys…

Okay, that’s enough for now. Next time I share the gRPC request tracking,

Technology is open, our mentality, should be more open. Embrace change, live in the sun, and strive to move forward.

I am nezha, welcome to like the collection, see you next time ~