這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
Interceptor
grpc服務端提供了interceptor功能,可以在服務端接收到請求時優先對請求中的資料做一些處理後再轉交給指定的服務處理並響應,功能類似middleware,很適合在這裡處理驗證、日誌等流程。
在自訂Token認證的樣本中,認證資訊是由每個服務中的方法處理並認證的,如果有大量的介面方法,這種姿勢就太蛋疼了,每個介面實現都要先處理認證資訊。這個時候interceptor就站出來解決了這個問題,可以在請求被轉到具體介面之前處理認證資訊,一處認證,到處無憂,看代碼吧,修改hello-token項目的服務端實現:
server/main.go
package mainimport ( "net" pb "github.com/Jergoo/go-grpc-example/proto" "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 ...var HelloService = helloService{}func (h helloService) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { resp := new(pb.HelloReply) resp.Message = "Hello " + in.Name + "." return resp, nil}// auth 驗證Tokenfunc 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}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 var interceptor grpc.UnaryServerInterceptor interceptor = func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { err = auth(ctx) if err != nil { return } // 繼續處理請求 return handler(ctx, req) } 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)}
運行:
go run main.goListen on 50052 with TLS + Token
client/main.go
package mainimport ( pb "github.com/Jergoo/go-grpc-example/proto" // 引入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{}func (c customCredential) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { return map[string]string{ "appid": "101010", "appkey": "i am key", }, nil}func (c customCredential) RequireTransportSecurity() bool { if OpenTLS { return true } return false}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))) conn, err := grpc.Dial(Address, opts...) if err != nil { grpclog.Fatalln(err) } defer conn.Close() // 初始化用戶端 c := pb.NewHelloClient(conn) // 調用方法 reqBody := new(pb.HelloRequest) reqBody.Name = "gRPC" r, err := c.SayHello(context.Background(), reqBody) if err != nil { grpclog.Fatalln(err) } grpclog.Println(r.Message)}
運行用戶端程式 client/main.go:
go run main.go// 認證成功結果Hello gRPCToken info: appid=101010,appkey=i am key// 認證失敗結果:rpc error: code = 16 desc = Token認證資訊無效: appID=101010, appKey=i am not key
運行結果和hello-token項目一樣,簡單不,只需要在執行個體化server前註冊需要的interceptor,就可以輕鬆解決那個蛋疼的問題,想註冊幾個就註冊幾個。
項目推薦:
go-grpc-middleware
這個項目對interceptor進行了封裝,支援多個攔截器的鏈式組裝,對於需要多種處理的地方使用起來會更方便些。
參考
本系列範例程式碼