Into grpc: unamry and streaminterceptor

  golang, grpc, php, rpc, tls

Into grpc: unamry and streaminterceptor

Original address:Into grpc: unamry and streaminterceptor
Project address:https://github.com/EDDYCJY/go …

Preface

I want to do something before or after each RPC method, how?

Interceptor, which will be introduced in this chapter, can help you realize these functions in the right place.

There are several ways

In gRPC, the major classes can be divided into two RPC methods. The corresponding relationship with interceptors is:

  • Common method: grpc.UnaryInterceptor
  • Stream method: stream interceptor (grpc.StreamInterceptor)

Have a look

grpc.UnaryInterceptor

func UnaryInterceptor(i UnaryServerInterceptor) ServerOption {
    return func(o *options) {
        if o.unaryInt != nil {
            panic("The unary server interceptor was already set and may not be reset.")
        }
        o.unaryInt = i
    }
}
type UnaryServerInterceptor func(ctx context.Context, req interface{}, info *UnaryServerInfo, handler UnaryHandler) (resp interface{}, err error)

By looking at the source code, we can know that to complete an interceptor needs to be implementedUnaryServerInterceptorMethods. The formal parameters are as follows:

  • Context: request context
  • Reqinterface {}: request parameters for rpc methods
  • Info * unaryserInfo: all information for rpc methods
  • HandlerUnaryHandler: rpc method itself

grpc.StreamInterceptor

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

StreamServerInterceptor and UnaryServerInterceptor parameters have the same meaning and will not be repeated here.

How to Realize Multiple Interceptors

In addition, it can be found that gRPC itself can only set up one interceptor. Can all logic be written together?

On this point, you can rest assured. Adoption of Open Source Projectsgo-grpc-middlewareThis problem can be solved and will be used in this chapter.

import "github.com/grpc-ecosystem/go-grpc-middleware"

myServer := grpc.NewServer(
    grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(
        ...
    )),
    grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
       ...
    )),
)

gRPC

Starting from this section, we will write the code of gRPC interceptor, and we will implement the following interceptors:

  • Logging: Log Output of Input and Output Parameters of RPC Method
  • Recover: Exception Protection and Log Output of RPC Method

Implementation of interceptor

logging

func LoggingInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
    log.Printf("gRPC method: %s, %v", info.FullMethod, req)
    resp, err := handler(ctx, req)
    log.Printf("gRPC method: %s, %v", info.FullMethod, resp)
    return resp, err
}

recover

func RecoveryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
    defer func() {
        if e := recover(); e != nil {
            debug.PrintStack()
            err = status.Errorf(codes.Internal, "Panic err: %v", e)
        }
    }()

    return handler(ctx, req)
}

Server

import (
    "context"
    "crypto/tls"
    "crypto/x509"
    "errors"
    "io/ioutil"
    "log"
    "net"
    "runtime/debug"

    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"
    "google.golang.org/grpc/status"
    "google.golang.org/grpc/codes"
    "github.com/grpc-ecosystem/go-grpc-middleware"

    pb "github.com/EDDYCJY/go-grpc-example/proto"
)

...

func main() {
    c, err := GetTLSCredentialsByCA()
    if err != nil {
        log.Fatalf("GetTLSCredentialsByCA err: %v", err)
    }

    opts := []grpc.ServerOption{
        grpc.Creds(c),
        grpc_middleware.WithUnaryServerChain(
            RecoveryInterceptor,
            LoggingInterceptor,
        ),
    }

    server := grpc.NewServer(opts...)
    pb.RegisterSearchServiceServer(server, &SearchService{})

    lis, err := net.Listen("tcp", ":"+PORT)
    if err != nil {
        log.Fatalf("net.Listen err: %v", err)
    }

    server.Serve(lis)
}

Verification

logging

Start simple_server/server.go, execute the request initiated by simple_client/client.go, and get the result:

$ go run server.go
2018/10/02 13:46:35 gRPC method: /proto.SearchService/Search, request:"gRPC" 
2018/10/02 13:46:35 gRPC method: /proto.SearchService/Search, response:"gRPC Server"

recover

In RPC method, run-time errors are artificially created, and then server/client.go is repeatedly started, resulting in the following results:

client

$ go run client.go
2018/10/02 13:19:03 client.Search err: rpc error: code = Internal desc = Panic err: assignment to entry in nil map
exit status 1

server

$ go run server.go
goroutine 23 [running]:
runtime/debug.Stack(0xc420223588, 0x1033da9, 0xc420001980)
    /usr/local/Cellar/go/1.10.1/libexec/src/runtime/debug/stack.go:24 +0xa7
runtime/debug.PrintStack()
    /usr/local/Cellar/go/1.10.1/libexec/src/runtime/debug/stack.go:16 +0x22
main.RecoveryInterceptor.func1(0xc420223a10)
...

Check whether the service is still running, and you will know whether Recovery has taken effect successfully.

Summary

Through this chapter, you can learn the most common methods of using interceptors. Next, other “new” requirements need only be extrapolated

References

This series of sample codes

Series catalog