Golang Web應用六 攔截器 Interceptor
阿新 • • 發佈:2019-01-24
Interceptor
grpc服務端提供了interceptor功能,可以在服務端接收到請求時優先對請求中的資料做一些處理後再轉交給指定的服務處理並響應,功能類似middleware,很適合在這裡處理驗證、日誌等流程。
在自定義Token認證的示例中,認證資訊是由每個服務中的方法處理並認證的,如果有大量的介面方法,這種姿勢就太蛋疼了,每個介面實現都要先處理認證資訊。這個時候interceptor就站出來解決了這個問題,可以在請求被轉到具體介面之前處理認證資訊,一處認證,到處無憂,看程式碼吧,修改hello-token專案的服務端實現:
server/main.go
package main
import (
"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 驗證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
}
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.go
Listen on 50052 with TLS + Token
client/main.go
package main
import (
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 gRPC
Token info: appid=101010,appkey=i am key
// 認證失敗結果:
rpc error: code = 16 desc = Token認證資訊無效: appID=101010, appKey=i am not key
執行結果和hello-token專案一樣,簡單不,只需要在例項化server前註冊需要的interceptor,就可以輕鬆解決那個蛋疼的問題,想註冊幾個就註冊幾個。
專案推薦:
這個專案對interceptor進行了封裝,支援多個攔截器的鏈式組裝,對於需要多種處理的地方使用起來會更方便些。