Go gRPC Interceptor 攔截器
grpc 服務端和客戶端都提供了 interceptor 功能,功能類似 middleware,很適合在這裏處理驗證、日誌等流程。
在自定義 Token 認證的示例中,認證信息是由每個服務中的方法處理並認證的,如果有大量的接口方法,這種姿勢就太不優雅了,每個接口實現都要先處理認證信息。這個時候 interceptor 就可以用來解決了這個問題,在請求被轉到具體接口之前處理認證信息,一處認證,到處無憂。在客戶端,我們增加一個請求日誌,記錄請求相關的參數和耗時等等。修改 hello_token 項目實現:
目錄結構
|—— hello\_interceptor/
|—— client/
|—— main.go // 客戶端
|—— server/
|—— main.go // 服務端
|—— keys/ // 證書目錄
|—— server.key
|—— server.pem
|—— proto/
|—— hello/
|—— hello.proto // proto描述文件
|—— hello.pb.go // proto編譯後文件
示例代碼
Step 1. 服務端 interceptor:
hello_interceptor/server/main.go
package main
import (
"fmt"
"net"
pb "github.com/jergoo/go-grpc-example/proto/hello"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/codes" // grpc 響應狀態碼
"google.golang.org/grpc/credentials" // grpc認證包
"google.golang.org/grpc/grpclog"
"google.golang.org/grpc/metadata" // grpc metadata包
)
const (
// Address gRPC服務地址
Address = "127.0.0.1:50052"
)
// 定義helloService並實現約定的接口
type helloService struct{}
// HelloService Hello服務
var HelloService = helloService{}
// SayHello 實現Hello服務接口
func (h helloService) SayHello(ctx context.Context, in \*pb.HelloRequest) (\*pb.HelloResponse, error) {
resp := new(pb.HelloResponse)
resp.Message = fmt.Sprintf("Hello %s.", in.Name)
return resp, nil
}
func main() {
listen, err := net.Listen("tcp", Address)
if err != nil {
grpclog.Fatalf("Failed to listen: %v", err)
}
var opts \[\]grpc.ServerOption
// TLS認證
creds, err := credentials.NewServerTLSFromFile("../../keys/server.pem", "../../keys/server.key")
if err != nil {
grpclog.Fatalf("Failed to generate credentials %v", err)
}
opts = append(opts, grpc.Creds(creds))
// 註冊interceptor
opts = append(opts, grpc.UnaryInterceptor(interceptor))
// 實例化grpc Server
s := grpc.NewServer(opts...)
// 註冊HelloService
pb.RegisterHelloServer(s, HelloService)
grpclog.Println("Listen on " + Address + " with TLS + Token + Interceptor")
s.Serve(listen)
}
// auth 驗證Token
func auth(ctx context.Context) error {
md, ok := metadata.FromContext(ctx)
if !ok {
return grpc.Errorf(codes.Unauthenticated, "無Token認證信息")
}
var (
appid string
appkey string
)
if val, ok := md\["appid"\]; ok {
appid = val\[0\]
}
if val, ok := md\["appkey"\]; ok {
appkey = val\[0\]
}
if appid != "101010" || appkey != "i am key" {
return grpc.Errorf(codes.Unauthenticated, "Token認證信息無效: appid=%s, appkey=%s", appid, appkey)
}
return nil
}
// interceptor 攔截器
func interceptor(ctx context.Context, req interface{}, info \*grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
err := auth(ctx)
if err != nil {
return nil, err
}
// 繼續處理請求
return handler(ctx, req)
}
Step 2. 實現客戶端 interceptor:
hello_intercepror/client/main.go
package main
import (
"time"
pb "github.com/jergoo/go-grpc-example/proto/hello" // 引入proto包
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials" // 引入grpc認證包
"google.golang.org/grpc/grpclog"
)
const (
// Address gRPC服務地址
Address = "127.0.0.1:50052"
// OpenTLS 是否開啓TLS認證
OpenTLS = true
)
// customCredential 自定義認證
type customCredential struct{}
// GetRequestMetadata 實現自定義認證接口
func (c customCredential) GetRequestMetadata(ctx context.Context, uri ...string) (map\[string\]string, error) {
return map\[string\]string{
"appid": "101010",
"appkey": "i am key",
}, nil
}
// RequireTransportSecurity 自定義認證是否開啓TLS
func (c customCredential) RequireTransportSecurity() bool {
return OpenTLS
}
func main() {
var err error
var opts \[\]grpc.DialOption
if OpenTLS {
// TLS連接
creds, err := credentials.NewClientTLSFromFile("../../keys/server.pem", "server name")
if err != nil {
grpclog.Fatalf("Failed to create TLS credentials %v", err)
}
opts = append(opts, grpc.WithTransportCredentials(creds))
} else {
opts = append(opts, grpc.WithInsecure())
}
// 指定自定義認證
opts = append(opts, grpc.WithPerRPCCredentials(new(customCredential)))
// 指定客戶端interceptor
opts = append(opts, grpc.WithUnaryInterceptor(interceptor))
conn, err := grpc.Dial(Address, opts...)
if err != nil {
grpclog.Fatalln(err)
}
defer conn.Close()
// 初始化客戶端
c := pb.NewHelloClient(conn)
// 調用方法
req := &pb.HelloRequest{Name: "gRPC"}
res, err := c.SayHello(context.Background(), req)
if err != nil {
grpclog.Fatalln(err)
}
grpclog.Println(res.Message)
}
// interceptor 客戶端攔截器
func interceptor(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...)
grpclog.Printf("method=%s req=%v rep=%v duration=%s error=%v\\n", method, req, reply, time.Since(start), err)
return err
}
運行結果
$ cd hello\_inteceptor/server && go run main.go
Listen on 127.0.0.1:50052 with TLS + Token + Interceptor
$ cd hello\_inteceptor/client && go run main.go
method=/hello.Hello/SayHello req=name:"gRPC" rep=message:"Hello gRPC." duration=33.879699ms error=<nil>
Hello gRPC.
項目推薦:go-grpc-middleware
這個項目對 interceptor 進行了封裝,支持多個攔截器的鏈式組裝,對於需要多種處理的地方使用起來會更方便些。
轉自:jergoo
jergoo.gitbooks.io/go-grpc-practice-guide/content/chapter2/interceptor.html
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。