Golang gRPC實踐 連載五 攔截器 Interceptor

Interceptor

grpc服務端提供了interceptor功能,能夠在服務端接收到請求時優先對請求中的數據作一些處理後再轉交給指定的服務處理並響應,功能相似middleware,很適合在這裏處理驗證、日誌等流程。git

在自定義Token認證的示例中,認證信息是由每一個服務中的方法處理並認證的,若是有大量的接口方法,這種姿式就太蛋疼了,每一個接口實現都要先處理認證信息。這個時候interceptor就站出來解決了這個問題,能夠在請求被轉到具體接口以前處理認證信息,一處認證,處處無憂,看代碼吧,修改hello-token項目的服務端實現:github

server/main.gogolang

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)
}

運行:app

go run main.go

Listen on 50052 with TLS + Token

client/main.gotcp

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:ui

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,就能夠輕鬆解決那個蛋疼的問題,想註冊幾個就註冊幾個。google

項目推薦: 日誌

go-grpc-middlewarecode

這個項目對interceptor進行了封裝,支持多個攔截器的鏈式組裝,對於須要多種處理的地方使用起來會更方便些。server

參考

本系列示例代碼

相關文章
相關標籤/搜索