为客户端和服务器端的Golang应用程序创建gRPC单选中间件/拦截器

152 阅读2分钟

在这个例子中,我们将为客户端和服务器端的Golang应用程序创建gRPC单选中间件/拦截器。这个概念与为HTTP路由器和HTTP服务器创建中间件相同。

这个例子很简单。客户端将使用timer 拦截器,这样我们就能知道需要多长时间才能得到响应。然后,它还将使用identity 拦截器来设置其身份。这将被服务器用它的拦截器verify 来检查。服务器也有一个叫做log 的拦截器,它只是记录了一些东西。

结构

├── Makefile
├── client
│   └── main.go
├── go.mod
├── hello.pb.go
├── hello.proto
├── interceptor
│   ├── Identity.go
│   ├── log.go
│   ├── timer.go
│   └── verify.go
├── message
│   ├── client.go
│   └── server.go
└── server
    └── main.go

文件

制作文件

运行make compile 来生成hello.pb.go 文件:

.PHONY: compile
compile:
	protoc --go_out=plugins=grpc:. --go_opt=paths=source_relative hello.proto

.PHONY: client
client:
	go run --race client/main.go

.PHONY: server
server:
	go run --race server/main.go

hello.proto

syntax = "proto3";

package message;

option go_package = "generated;generated";

message MessageRequest {
    string text = 1;
}

message MessageResponse {
    string text = 1;
}

service MessageService {
    rpc SendMessage (MessageRequest) returns (MessageResponse) {}
}

client/main.go

package main

import (
	"context"
	"log"
	"time"

	"google.golang.org/grpc"
	"sport/interceptor"
	"sport/message"
)

func main() {
	log.Println("client")

	opts := []grpc.DialOption{
		grpc.WithInsecure(),
		grpc.WithChainUnaryInterceptor(
			interceptor.TimerUnaryClient,
			interceptor.Identity{ID: "client-1"}.UnaryClient,
		),
	}

	conn, err := grpc.Dial(":50051", opts...)
	if err != nil {
		log.Fatalln(err)
	}
	defer conn.Close()

	messageClient := message.NewClient(conn, time.Second*10)
	res, err := messageClient.SendMessage(context.Background(), "Hello")

	log.Println("ERRR:", err)
	log.Println("RESP:", res)
}

拦截器/计时器.go

package interceptor

import (
	"context"
	"log"
	"time"

	"google.golang.org/grpc"
)

func TimerUnaryClient(
	ctx context.Context,
	method string,
	req interface{},
	reply interface{},
	cc *grpc.ClientConn,
	invoker grpc.UnaryInvoker,
	opts ...grpc.CallOption,
) error {
	start := time.Now()

	log.Println("Timer: 1")
	time.Sleep(time.Second)

	err := invoker(ctx, method, req, reply, cc, opts...)

	log.Println("Timer: 2")
	time.Sleep(time.Second)

	end := time.Since(start)

	log.Printf("%s method call took %s\n", method, end)

	return err
}

interceptor/Identity.go

package interceptor

import (
	"context"
	"log"
	"time"

	"google.golang.org/grpc"
	"google.golang.org/grpc/metadata"
)

type Identity struct {
	ID string
}

func (i Identity) UnaryClient(
	ctx context.Context,
	method string,
	req interface{},
	reply interface{},
	cc *grpc.ClientConn,
	invoker grpc.UnaryInvoker,
	opts ...grpc.CallOption,
) error {
	md := metadata.Pairs()
	md.Set("client-id", i.ID)

	ctx = metadata.NewOutgoingContext(ctx, md)

	log.Println("Identity: 1")
	time.Sleep(time.Second)

	err := invoker(ctx, method, req, reply, cc, opts...)

	log.Println("Identity: 2")
	time.Sleep(time.Second)

	return err
}

message/client.go

package message

import (
	"context"
	"time"

	"google.golang.org/grpc"

	generated "sport"
)

type Client struct {
	messageClient generated.MessageServiceClient
	timeout       time.Duration
}

func NewClient(conn grpc.ClientConnInterface, timeout time.Duration) Client {
	return Client{
		messageClient: generated.NewMessageServiceClient(conn),
		timeout:       timeout,
	}
}

func (c Client) SendMessage(ctx context.Context, message string) (*generated.MessageResponse, error) {
	ctx, cancel := context.WithDeadline(ctx, time.Now().Add(c.timeout))
	defer cancel()

	return c.messageClient.SendMessage(ctx, &generated.MessageRequest{Text: message})
}

server/main.go

package main

import (
	"log"
	"net"

	"google.golang.org/grpc"
	"sport/interceptor"
	"sport/message"

	generated "sport"
)

func main() {
	log.Println("server")

	listener, err := net.Listen("tcp", ":50051")
	if err != nil {
		log.Fatalln(err)
	}

	opts := []grpc.ServerOption{
		grpc.ChainUnaryInterceptor(
			interceptor.VerifyUnaryServer,
			interceptor.LogUnaryServer,
		),
	}

	grpcServer := grpc.NewServer(opts...)

	messageServer := message.NewServer()

	generated.RegisterMessageServiceServer(grpcServer, messageServer)

	log.Fatalln(grpcServer.Serve(listener))
}

拦截器/验证.go

package interceptor

import (
	"context"
	"log"
	"time"

	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc/status"
)

func VerifyUnaryServer(
	ctx context.Context,
	req interface{},
	info *grpc.UnaryServerInfo,
	handler grpc.UnaryHandler,
) (resp interface{}, err error) {
	log.Println("Verify: 1")
	time.Sleep(time.Second)

	md, ok := metadata.FromIncomingContext(ctx)
	if !ok || len(md["client-id"]) == 0 {
		return nil, status.Errorf(codes.InvalidArgument, "missing metadata")
	}
	if md["client-id"][0] != "client-1" {
		return nil, status.Error(codes.PermissionDenied, "unexpected client")
	}

	res, err := handler(ctx, req)

	log.Println("Verify: 2")
	time.Sleep(time.Second)

	return res, err
}

拦截器/日志.go

package interceptor

import (
	"context"
	"log"
	"time"

	"google.golang.org/grpc"
)

func LogUnaryServer(
	ctx context.Context,
	req interface{},
	info *grpc.UnaryServerInfo,
	handler grpc.UnaryHandler,
) (resp interface{}, err error) {
	log.Println("Log: 1")
	time.Sleep(time.Second)

	log.Println("Logging")

	res, err := handler(ctx, req)

	log.Println("Log: 2")
	time.Sleep(time.Second)

	return res, err
}

message/server.go

package message

import (
	"context"

	generated "sport"
)

type Server struct {
	generated.UnimplementedMessageServiceServer
}

func NewServer() Server {
	return Server{}
}

func (s Server) SendMessage(ctx context.Context, req *generated.MessageRequest) (*generated.MessageResponse, error) {
	return &generated.MessageResponse{Text: req.GetText()}, nil
}

测试

首先运行make server ,然后运行make client 命令。两条命令的组合输出会如下图所示。这告诉你拦截器是如何按顺序处理的:

                   CLIENT       SERVER
2020/09/14 19:11:46 Timer: 1
2020/09/14 19:11:47 Identity: 1
2020/09/14 19:11:48              Verify: 1
2020/09/14 19:11:49              Log: 1
2020/09/14 19:11:50              Log: 2
2020/09/14 19:11:51              Verify: 2
2020/09/14 19:11:52 Identity: 2
2020/09/14 19:11:53 Timer: 2