gRPC(3):攔截器

Assassin007發表於2021-07-08

在 gRPC 呼叫過程中,我們可以攔截 RPC 的執行,在 RPC 服務執行前或執行後執行一些自定義邏輯,這在某些場景下很有用,例如身份驗證、日誌等,我們可以在 RPC 服務執行前檢查呼叫方的身份資訊,若未通過驗證,則拒絕執行,也可以在執行前後記錄下詳細的請求響應資訊到日誌。這種攔截機制與 Gin 中的中介軟體技術類似,在 gRPC 中被稱為 攔截器,它是 gRPC 核心擴充套件機制之一

攔截器不止可以作用在服務端上,客戶端同樣可以攔截,在請求發出之前和收到響應之後執行一些自定義邏輯,根據攔截的 RPC 型別,可分為 一元攔截器流攔截器

服務端攔截器

在 gRPC 服務端,可以插入一個或多個攔截器,收到的請求按註冊順序通過各個攔截器,返回響應時則倒序通過:

未命名檔案

一元攔截器

通過以下步驟實現一元攔截器:

  • 定義一元攔截器方法:
// 函式名無特殊要求,引數需一致
// req包含請求的所有資訊,info包含一元RPC服務的所有資訊
func orderUnaryServerInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo,
	handler grpc.UnaryHandler) (interface{}, error) {
        // 前置處理邏輯
        log.Printf("[unary interceptor request] %s", info.FullMethod)
        // 完成RPC服務的正常執行
        m, err := handler(ctx, req)
        // 後置處理邏輯
        log.Printf("[unary interceptor resonse] %s", m)
        // 返回響應
        return m, err
}
  • 註冊定義的一元攔截器
func main() {
    ...
	// 建立gRPC伺服器例項的時候註冊攔截器
    // NewServer 可傳入多個攔截器
	s := grpc.NewServer(grpc.UnaryInterceptor(orderUnaryServerInterceptor))
    ...
}

流攔截器

流攔截器包括前置處理階段和流操作階段,前置處理階段可以在流 RPC 進入具體服務實現之前進行攔截,而在流操作階段,可以對流中的每一條訊息進行攔截,通過以下步驟實現流攔截器:

  • 自定義一個嵌入grpc.ServerStream的包裝器
type wrappedStream struct {
	grpc.ServerStream
}
  • 實現包裝器的 RecvMsg 和 SendMsg 方法
// 自定義RecvMsg和SendMsg方法實現對每一個流訊息的攔截
func (w *wrappedStream) RecvMsg(m interface{}) error {
	log.Printf("[stream interceptor recv] type: %T", m)
	return w.ServerStream.RecvMsg(m)
}
func (w *wrappedStream) SendMsg(m interface{}) error {
	log.Printf("[stream interceptor send] %s", m)
	return w.ServerStream.SendMsg(m)
}
  • 實現流攔截器
func orderServerStreamInterceptor(srv interface{}, ss grpc.ServerStream,
	info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
    // 前置處理階段
	log.Printf("[stream interceptor request] %s", info.FullMethod)
	// 使用自定義包裝器處理流
	err := handler(srv, &wrappedStream{ss})
	if err != nil {
		log.Printf("[stream Intercept error] %v", err)
	}
	return err
}
  • 註冊流攔截器
func main() {
    ...
	s := grpc.NewServer(grpc.StreamInterceptor(orderServerStreamInterceptor))
    ...
}

客戶端攔截器

在服務端可以攔截收到的 RPC 呼叫,客戶端同樣可以攔截髮出去的 RPC 請求以及收到的響應,同樣可以實現一元攔截器以及流攔截器:

未命名檔案 (1)

一元攔截器

和服務端一元攔截器一樣的方法,只是方法引數略微有所差別,此外在建立連線的時候註冊攔截器,同樣可以註冊多個攔截器:

// method請求方法字串,req包含請求的所有資訊引數等,reply在實際RPC呼叫後儲存響應資訊,通過invoker實際呼叫
func orderUnaryClientInterceptor(ctx context.Context, method string, req, reply interface{},
	cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
	// 前置處理階段
	log.Println("method: " + method)
	// 實際的RPC呼叫
	err := invoker(ctx, method, req, reply, cc, opts...)
	// 後置處理
	log.Println(reply)
	return err
}

func main() {
    ...
	conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithUnaryInterceptor(orderUnaryClientInterceptor))
    ...
}

流攔截器

流攔截器也是和服務端一樣的步驟:

type wrappedStream struct {
	grpc.ClientStream
}

func (w *wrappedStream) SendMsg(m interface{}) error {
	log.Printf("[stream interceptor send] %s", m)
	return w.ClientStream.SendMsg(m)
}
func (w *wrappedStream) RecvMsg(m interface{}) error {
	log.Printf("[stream interceptor recv] type: %T", m)
	return w.ClientStream.RecvMsg(m)
}

func orderClientStreamInterceptor(ctx context.Context, desc *grpc.StreamDesc,
	cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
    // 前置處理階段,RPC請求發出之前攔截
	log.Printf("[client interceptor send] %s", method)
    // 發出RPC請求
	s, err := streamer(ctx, desc, cc, method, opts...)
	if err != nil {
		return nil, err
	}
	return &wrappedStream{s}, nil
}

func main() {
    ...
	conn, err := grpc.Dial(address, grpc.WithInsecure(),
		grpc.WithStreamInterceptor(orderClientStreamInterceptor))
    ...
}

相關文章