gRPC 提供了一些简单的 API 在 ClientConn/Server
实现和安装拦截器,拦截器拦截每一次 RPC 调用的执行。
用户可以使用拦截器用于日志、认证、指标/监控等。
在 gRPC 中,拦截器分为 2 种,一种是 unary interceptor ,用于拦截 unary RPC 调用。另一种是 stream interceptor ,用于处理 stream RPC 调用。 每一个客户端和服务端都有 unary interceptor 和 stream interceptor ,因此在 gRPC 中4种类型的拦截器。
客户端
Unary Interceptor
UnaryClientInterceptor
是一个客户端类型的拦截器,方法签名为
func(ctx context.Context, method string, req, reply interface{}, cc *ClientConn, invoker UnaryInvoker, opts ...CallOption) error
实现 unary interceptor 通常分为三部分,pre-processing, invoke RPC method 和 post-processing。
对于安装 unary interceptor 在 ClientConn 上,配置Dail
和DialOption
WithUnaryInterceptor
。
Stream Interceptor
StreamClientInterceptor
是一个客户端流类型的拦截器,方法签名为
func(ctx context.Context, desc *StreamDesc, cc *ClientConn, method string, streamer Streamer, opts ...CallOption) (ClientStream, error)
一个流拦截器的实现通常包含处理前和流操作拦截。
对于安装 unary interceptor 在 ClientConn 上,配置 Dail
和 DialOption
WithStreamInterceptor
。
服务端
服务端和客户端拦截器有些类似,稍微有一些不同。
Unary Interceptor
UnaryServerInterceptor
是服务端一种拦截器,方法签名为
func(ctx context.Context, req interface{}, info *UnaryServerInfo, handler UnaryHandler) (resp interface{}, err error)
对于安装 unary interceptor 在服务端,配置 NewServer
和 NewServer
。
Stream Interceptor
StreamServerInterceptor
是一种服务端流类型的拦截器,方法签名为
func(srv interface{}, ss ServerStream, info *StreamServerInfo, handler StreamHandler) error
对于安装 stream interceptor 在服务端,配置 NewServer
和 NewServer
。
示例代码
开发环境:
golang 1.20.1
protoc libprotoc 3.20.3
代码目录结构
└──com
└── example
├── echo
│ ├── echo.go
│ ├── echo.pb.go
│ ├── echo.proto
│ └── echo_grpc.pb.go
└──interceptor
├── client.go
└── server.go
echo.proto
syntax = "proto3";
package echo;
option go_package = "com/example/echo";
service EchoService{
rpc UnaryEcho(EchoRequest) returns(EchoResponse){}
rpc ServerStreamingEcho(EchoRequest) returns(stream EchoResponse){}
rpc ClientStreamingEcho(stream EchoRequest) returns(EchoResponse){}
rpc BidirectionalStreamingEcho(stream EchoRequest) returns(stream EchoResponse){}
}
message EchoRequest{
string message = 1;
}
message EchoResponse{
string message = 1;
}
业务 echo.go
package echo
import (
"context"
"fmt"
"io"
"log"
)
type EchoService struct {
EchoServiceServer
}
func (s *EchoService) UnaryEcho(ctx context.Context, req *EchoRequest) (*EchoResponse, error) {
log.Print("unary echoing message=", req.Message)
return &EchoResponse{
Message: req.Message,
}, nil
}
func (s *EchoService) BidirectionalStreamingEcho(stream EchoService_BidirectionalStreamingEchoServer) error {
for {
in, err := stream.Recv()
if err != nil {
if err == io.EOF {
return nil
}
fmt.Printf("server: error receiving from stream: %v\n", err)
return err
}
fmt.Printf("bidi echoing message %q\n", in.Message)
stream.Send(&EchoResponse{Message: in.Message})
}
}
服务端(server.go)
package main
import (
"context"
"example.com/com/example/echo"
"google.golang.org/grpc"
"log"
"net"
"time"
)
// 服务端 unary 拦截器
func echoUnaryServerInterceptor(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) {
start := time.Now()
resp, err = handler(ctx, req)
end := time.Now()
log.Print("RPC unary server interceptor:", end.Sub(start))
return resp, err
}
// 服务端 stream 拦截器
func echoStreamServerInterceptor(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
log.Print("RPC stream server interceptor:", info.FullMethod)
err := handler(srv, ss)
if err != nil {
log.Print(err)
}
return err
}
func main() {
server := grpc.NewServer(grpc.ChainUnaryInterceptor(echoUnaryServerInterceptor), grpc.ChainStreamInterceptor(echoStreamServerInterceptor))
echo.RegisterEchoServiceServer(server, &echo.EchoService{})
listen, err := net.Listen("tcp", ":8090")
if err != nil {
log.Print(err.Error())
}
log.Print("listen on port:8090 >>>>")
if er := server.Serve(listen); er != nil {
log.Print(er.Error())
}
}
客户端实现(client.go)
package main
import (
"context"
"example.com/com/example/echo"
"fmt"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"io"
"log"
"time"
)
// 客户端 unary 拦截器
func echoUnaryClientInterceptor(ctx context.Context, method string, req, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
start := time.Now()
err := invoker(ctx, method, req, reply, cc, opts...)
end := time.Now()
log.Print("RPC unary client interceptor:", method, "\n start time:", start, "\n end time:", end, "\n err:", err)
return err
}
// 客户端 stream 拦截器
func echoStreamInterceptor(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
log.Print("RPC stream client interceptor :", method)
s, err := streamer(ctx, desc, cc, method, opts...)
if err != nil {
return nil, err
}
return s, nil
}
func callUnaryEcho(client echo.EchoServiceClient) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
resp, err := client.UnaryEcho(ctx, &echo.EchoRequest{
Message: "hello",
})
if err != nil {
log.Print(err.Error())
return
}
log.Print("Unary Echo:", resp.Message)
}
func callBidiStreamEcho(client echo.EchoServiceClient) {
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
c, err := client.BidirectionalStreamingEcho(ctx)
if err != nil {
return
}
for i := 0; i < 5; i++ {
if err := c.Send(&echo.EchoRequest{Message: fmt.Sprintf("Request %d", i+1)}); err != nil {
log.Fatalf("failed to send request due to error: %v", err)
}
}
c.CloseSend()
for {
resp, err := c.Recv()
if err == io.EOF {
break
}
if err != nil {
log.Fatalf("failed to receive response due to error: %v", err)
}
fmt.Println("BidiStreaming Echo: ", resp.Message)
}
}
func main() {
conn, err := grpc.Dial("127.0.0.1:8090", grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithChainUnaryInterceptor(echoUnaryClientInterceptor),
grpc.WithChainStreamInterceptor(echoStreamInterceptor))
if err != nil {
panic(err)
}
defer conn.Close()
client := echo.NewEchoServiceClient(conn)
callUnaryEcho(client)
callBidiStreamEcho(client)
}
总结,对 gRPC 客户端和服务端的 unary 和 stream 的拦截器的了解以及代码实现。