gRPC應用golang


1.    gRPC簡述

gRPC 是一個高性能、開源和通用的 RPC 框架,面向移動和 HTTP/2 設計。目前提供 C、Java 和 Go 語言版本,分別是:grpc, grpc-java, grpc-go. 其中 C 版本支持 C, C++, Node.js, Python, Ruby, Objective-C, PHP 和 C# 支持.

gRPC 基於 HTTP/2 標准設計,帶來諸如雙向流、流控、頭部壓縮、單 TCP 連接上的多復用請求等特。這些特性使得其在移動設備上表現更好,更省電和節省空間占用。

gRPC默認使用protocol buffers作為交換數據序列化的機制,即gRPC底層依賴protobuf。

gRPC官方文檔:https://grpc.io/docs/languages/go/quickstart/

服務非對象,消息非引用,促進微服務的系統間粗粒度消息交互設計理念。

2.    gRPC-go安裝

1)安裝protobuf編譯器protoc,可直接從https://github.com/protocolbuffers/protobuf/releases安裝穩定版本。

2)安裝golang插件protoc-gen-go。

$ go get -u github.com/golang/protobuf/protoc-gen-go

3)下載grpc-go倉庫

grpc-go倉庫原地址為:google.golang.org/grpc,由於原地址屏蔽,需要從github拉下來后拷貝到$GOPATH/src/google.golang.org/grpc。

git clone https://github.com/grpc/grpc-go.git $GOPATH/src/google.golang.org/grpc

4)配置goproxy,在/etc/profile最后加上如下代碼后source。

export GO111MODULE=on
export GOPROXY="https://goproxy.cn"

5)直接編譯例程

$ go get google.golang.org/grpc/examples/helloworld/greeter_client
$ go get google.golang.org/grpc/examples/helloworld/greeter_server

編譯成功后可執行文件位於$GOPATH/bin下。

3.    gRPC-go應用

1 定義服務

一個 RPC 服務通過參數和返回類型來指定可以遠程調用的方法。gRPC通過protobuf來實現。

使用 protocol buffers 接口定義語言來定義服務方法,用 protocol buffer 來定義參數和返回類型。客戶端和服務端均使用服務定義生成的接口代碼。

syntax = "proto3";

option go_package = "io.grpc.examples";

package helloworld;

// The greeter service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}

2. 生成gRPC代碼

使用protoc來生成創建應用所需的特定客戶端和服務端的代碼,文件*.pb.go。生成的代碼同時包括客戶端的存根和服務端要實現的抽象接口,均包含Greeter所定義的方法。

進入helloworld.proto所在目錄:

protoc -I . helloworld.proto --go_out=plugins=grpc:.

注意:生成兼容gRPC代碼。

若protoc文件指定了RPC services,protoc-gen-go可以生成兼容gRPC的代碼,需要指定go_out的plugins參數,常用方法如下:

If a proto file specifies RPC services, protoc-gen-go can be instructed to generate code compatible with gRPC (http://www.grpc.io/). To do this, pass the plugins parameter to protoc-gen-go; the usual way is to insert it into the --go_out argument to protoc:

protoc --go_out=plugins=grpc:. *.proto

若不指定plugins=grpc參數,則生成的*.pb.go僅包含protobuf相關的代碼,不包含grpc相關代碼。

注意:插件參數列表

插件參數列表用逗號(,)分割,插件參數與輸出路徑用冒號(:)分隔。

To pass extra parameters to the plugin, use a comma-separated parameter list separated from the output directory by a colon:

protoc --go_out=plugins=grpc,import_path=mypackage:. *.proto
  • paths=(import | source_relative) - specifies how the paths of generated files are structured. See the "Packages and imports paths" section above. The default is import.
  • plugins=plugin1+plugin2 - specifies the list of sub-plugins to load. The only plugin in this repo is grpc.
  • Mfoo/bar.proto=quux/shme - declares that foo/bar.proto is associated with Go package quux/shme. This is subject to the import_prefix parameter.

The following parameters are deprecated and should not be used:

  • import_prefix=xxx - a prefix that is added onto the beginning of all imports.
  • import_path=foo/bar - used as the package if no input files declare go_package. If it contains slashes, everything up to the rightmost slash is ignored.

注:新版本的protoc會報錯:

protoc-gen-go: unable to determine Go import path for "helloworld.proto"

Please specify either:
    • a "go_package" option in the .proto source file, or
    • a "M" argument on the command line.

可在proto文件中定義go_package為當前包:

option go_package=".";

protoc --go_out=plugins=grpc:./proto proto/helloworld.proto

如此在proto目錄下生成helloworld.pb.go文件,並且該目錄下存放helloworld.proto。

// enhance
protoc --go_out=. --go_opt=paths=source_relative \
    --go-grpc_out=. --go-grpc_opt=paths=source_relative \
    helloworld/helloworld.proto

3. server端

服務器有一個server結構,通過實現SayHello()方法,實現了從proto服務定義生成的GreeterServer接口。

// server is used to implement helloworld.GreeterServer. 
type server struct{} 
// SayHello implements helloworld.GreeterServer 
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { 
    return &pb.HelloReply{Message: "Hello " + in.Name}, nil 
}

需要提供一個gRPC服務的另一個主要功能是讓這個服務在網絡上可用。

const ( 
    port = ":50051" 
)
 ... 
func main() {
   lis, err := net.Listen("tcp", port) 
   if err != nil { 
        log.Fatalf("failed to listen: %v", err) 
   } 
   s := grpc.NewServer() 
pb.RegisterGreeterServer(s, &server{}) s.Serve(lis) }

詳細代碼如下:

package main

import (
    "context"
    "log"
    "net"

    "google.golang.org/grpc"
    pb "google.golang.org/grpc/examples/helloworld/helloworld"
)

const (
    port = ":50051"
)

// server is used to implement helloworld.GreeterServer.
type server struct {
    pb.UnimplementedGreeterServer
}

// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
    log.Printf("Received: %v", in.GetName())
    return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}

func main() {
    lis, err := net.Listen("tcp", port)
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    s := grpc.NewServer()
    pb.RegisterGreeterServer(s, &server{})
    if err := s.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}

4. client端

連接服務:如何連接 Greeter 服務器,需要創建一個 gRPC 頻道,指定我們要連接的主機名和服務器端口。然后我們用這個頻道創建存根實例。

const (
  address     = "localhost:50051"
  defaultName = "world"
)
func main() {
  // Set up a connection to the server.
  conn, err := grpc.Dial(address)
  if err != nil {
      log.Fatalf("did not connect: %v", err)
  }
  defer conn.Close()
  c := pb.NewGreeterClient(conn)
...
}

詳細代碼如下:

// Package main implements a client for Greeter service.
package main

import (
    "context"
    "log"
    "os"
    "time"

    "google.golang.org/grpc"
    pb "google.golang.org/grpc/examples/helloworld/helloworld"
)

const (
    address     = "localhost:50051"
    defaultName = "world"
)

func main() {
    // Set up a connection to the server.
    conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock())
    if err != nil {
        log.Fatalf("did not connect: %v", err)
    }
    defer conn.Close()
    c := pb.NewGreeterClient(conn)

    // Contact the server and print out its response.
    name := defaultName
    if len(os.Args) > 1 {
        name = os.Args[1]
    }
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()
    r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})
    if err != nil {
        log.Fatalf("could not greet: %v", err)
    }
    log.Printf("Greeting: %s", r.GetMessage())
}

GRPC非加密(22數據幀)

4. gRPC認證

Grpc單向認證示例google.golang.org/grpc/examples/features/encryption

Grpc雙向認證示例opennessinterfaceservice

https://github.com/open-ness/edgenode/tree/master/pkg/interfaceservice

https://github.com/open-ness/edgecontroller/tree/master/cmd/interfaceservicecli

4.1 grpc相關函數

https://godoc.org/google.golang.org/grpc/credentials

google.golang.org/grpc/credentials

func NewClientTLSFromCert(cp *x509.CertPool, serverNameOverride string) TransportCredentials

func NewClientTLSFromFile(certFile, serverNameOverride string) (TransportCredentials, error)

func NewServerTLSFromCert(cert *tls.Certificate) TransportCredentials

func NewServerTLSFromFile(certFile, keyFile string) (TransportCredentials, error)

func NewTLS(c *tls.Config) TransportCredentials

google.golang.org/grpc

func Dial(target string, opts ...DialOption) (*ClientConn, error)

func DialContext(ctx context.Context, target string, opts ...DialOption) (conn *ClientConn, err error)

func (cc *ClientConn) Close() error

func WithTransportCredentials(creds credentials.TransportCredentials) DialOption

func NewServer(opt ...ServerOption) *Server

func Creds(c credentials.TransportCredentials) ServerOption

4.2 單向認證

服務端

導入證書,創建server時包含證書。

package main

import (
    "context"
    "log"
    "net"
    "flag"
    "fmt"

    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"
    pb "example/helloworld"
)

var port = flag.Int("port", 8088, "the port to serve on")

// server is used to implement helloworld.GreeterServer.
type server struct {
    pb.UnimplementedGreeterServer
}

// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
    log.Printf("Received: %v", in.GetName())
    return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}

func main() {
    flag.Parse()

    lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }

    creds, err := credentials.NewServerTLSFromFile("cert/server.crt", "cert/server.key")
    if err != nil {
        log.Fatalf("failed to create credentials: %v", err)
    }

    s := grpc.NewServer(grpc.Creds(creds))
    pb.RegisterGreeterServer(s, &server{})
    if err := s.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}

客戶端

grpc.Dial()時包含CA證書撥號。

package main

import (
    "context"
    "log"
    "time"
    "flag"

    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"
    pb "example/helloworld"
)

var addr = flag.String("addr", "server:8088", "the address to connect to")
var name = flag.String("name", "world", "the greeter's name")

func main() {
    flag.Parse()

    creds, err := credentials.NewClientTLSFromFile("cert/ca.crt", "")
    if err != nil {
        log.Fatalf("failed to load credentials: %v", err)
    }

    // Set up a connection to the server.
    conn, err := grpc.Dial(*addr, grpc.WithTransportCredentials(creds), grpc.WithBlock())
    if err != nil {
        log.Fatalf("did not connect: %v", err)
    }
    defer conn.Close()
    c := pb.NewGreeterClient(conn)

    // Contact the server and print out its response.
    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()
    r, err := c.SayHello(ctx, &pb.HelloRequest{Name: *name})
    if err != nil {
        log.Fatalf("could not greet: %v", err)
    }
    log.Printf("Greeting: %s", r.GetMessage())
}

GRPC單向認證(26數據幀)

4.3 雙向認證

服務端

需要定制tls.Config導入CAclient證書,生成credentials.TransportCredentials傳遞給grpc.Creds()后生成ServerOption,傳入grpc.NewServer()

package main

import (
    "context"
    "log"
    "net"
    "crypto/tls"
    "crypto/x509"
    "fmt"
    "io/ioutil"
    "path/filepath"

    "github.com/pkg/errors"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"
    pb "example/helloworld"
)

const (
    port = ":8088"
    certsDir = "cert/"
)

// server is used to implement helloworld.GreeterServer.
type server struct {
    pb.UnimplementedGreeterServer
}

// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
    log.Printf("Received: %v", in.GetName())
    return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}

func getTransportCredentials() (*credentials.TransportCredentials, error) {
    crtPath := filepath.Clean(filepath.Join(certsDir, "server.crt"))
    keyPath := filepath.Clean(filepath.Join(certsDir, "server.key"))
    caPath := filepath.Clean(filepath.Join(certsDir, "ca.crt"))

    cert, err := tls.LoadX509KeyPair(crtPath, keyPath)
    if err != nil {
        return nil, fmt.Errorf("Failed load server key pair: %v", err)
    }

    certPool := x509.NewCertPool()
    ca, err := ioutil.ReadFile(caPath)
    if err != nil {
        return nil, fmt.Errorf("Failed appends CA certs from %s: %s", caPath, err)
    }

    if ok := certPool.AppendCertsFromPEM(ca); !ok {
        return nil, errors.Errorf("Failed append CA certs from %s", caPath)
    }

    creds := credentials.NewTLS(&tls.Config{
        ClientAuth:   tls.RequireAndVerifyClientCert,
        Certificates: []tls.Certificate{cert},
        ClientCAs:      certPool,
    })

    fmt.Println("crtPath: "+crtPath + ", key: "+ keyPath + ", ca: " + caPath)

    return &creds, nil
}

func main() {
    tc, err := getTransportCredentials()
    if err != nil {
        log.Fatalf("fialed to read certificates: %s", err)
    }

    lis, err := net.Listen("tcp", port)
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    defer lis.Close()

    s := grpc.NewServer(grpc.Creds(*tc))
    pb.RegisterGreeterServer(s, &server{})
    fmt.Println("listen ", port)
    if err := s.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}

客戶端

同樣需要定制tls.Config,導入CA、客戶端證書生成credentials.TransportCredentials,傳入grpc.WithTransportCredentials()生成DialOption,傳入grpc.Dial()

package main

import (
    "context"
    "log"
    "os"
    "time"
    "path/filepath"
    "crypto/tls"
    "crypto/x509"
    "io/ioutil"
    "fmt"

    "github.com/pkg/errors"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"
    pb "example/helloworld"
)

const (
    //  address     = "https://server:8088"
    address     = "server:8088"
    defaultName = "world"
    certsDir = "cert/"
)

func getTransportCredentials() (*credentials.TransportCredentials, error) {
    crtPath := filepath.Clean(filepath.Join(certsDir, "client.crt"))
    keyPath := filepath.Clean(filepath.Join(certsDir, "client.key"))
    caPath := filepath.Clean(filepath.Join(certsDir, "ca.crt"))

    cert, err := tls.LoadX509KeyPair(crtPath, keyPath)
    if err != nil {
        return nil, err
    }

    certPool := x509.NewCertPool()
    ca, err := ioutil.ReadFile(caPath)
    if err != nil {
        return nil, err
    }

    if ok := certPool.AppendCertsFromPEM(ca); !ok {
        return nil, errors.Errorf("Failed append CA certs from %s", caPath)
    }

    creds := credentials.NewTLS(&tls.Config{
//      ServerName: "server",
//      ClientAuth: tls.RequireAndVerifyClientCert,
        Certificates: []tls.Certificate{cert},
        RootCAs:      certPool,
    })

    fmt.Println("crtPath: "+crtPath + ", key: "+ keyPath + ", ca: " + caPath)
    return &creds, nil
}

func createConnection(ctx context.Context, address string) *grpc.ClientConn {
    tc, err := getTransportCredentials()
    if err != nil {
        fmt.Println("Error when creating transport credentials: " + err.Error())
        os.Exit(1)
    }

    conn, err := grpc.DialContext(ctx, address,
        grpc.WithTransportCredentials(*tc), grpc.WithBlock())

    if err != nil {
        fmt.Println("Error when dialing: " + address + " err:" + err.Error())
        os.Exit(1)
    }

    return conn
}

func main() {
    name := defaultName
    if len(os.Args) > 1 {
        name = os.Args[1]
    }

    // Set up a connection to the server.
    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    fmt.Println("conn...")
    conn  := createConnection(ctx, address)
    defer conn.Close()
    c := pb.NewGreeterClient(conn)

    fmt.Println("call...")
    // Contact the server and print out its response.
    r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})
    if err != nil {
        log.Fatalf("could not greet: %v", err)
    }
    log.Printf("Greeting: %s", r.GetMessage())
}

GRPC雙向認證(25數據幀)

5. 流式RPC

gRPC 允許你定義四類服務方法:單項RPC、服務端流式RPC、客戶端流式RPC和雙向流式RPC。上面介紹的RPC都是單項RPC,下面重點介紹流式RPC。

服務端流式 RPC,即客戶端發送一個請求給服務端,可獲取一個數據流用來讀取一系列消息。客戶端從返回的數據流里一直讀取直到沒有更多消息為止。

rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse){ }

客戶端流式 RPC,即客戶端用提供的一個數據流寫入並發送一系列消息給服務端。一旦客戶端完成消息寫入,就等待服務端讀取這些消息並返回應答。

rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse) { }

雙向流式 RPC,即兩邊都可以分別通過一個讀寫數據流來發送一系列消息。這兩個數據流操作是相互獨立的,所以客戶端和服務端能按其希望的任意順序讀寫,例如:服務端可以在寫應答前等待所有的客戶端消息,或者它可以先讀一個消息再寫一個消息,或者是讀寫相結合的其他方式。每個數據流里消息的順序會被保持。

rpc BidiHello(stream HelloRequest) returns (stream HelloResponse){ }

注意關鍵字 stream,聲明其為一個流方法。

// client.go
package main

import (
    "context"
    "io"
    "log"

    "google.golang.org/grpc"

    pb "helloworld.grpc/proto"
)

const (
    PORT = "9002"
)

func main() {
    conn, err := grpc.Dial(":"+PORT, grpc.WithInsecure())
    if err != nil {
        log.Fatalf("grpc.Dial err: %v", err)
    }

    defer conn.Close()

    client := pb.NewStreamServiceClient(conn)

    err = printLists(client, &pb.StreamRequest{Pt: &pb.StreamPoint{Name: "gRPC Stream Client: List", Value: 2018}})
    if err != nil {
        log.Fatalf("printLists.err: %v", err)
    }

    err = printRecord(client, &pb.StreamRequest{Pt: &pb.StreamPoint{Name: "gRPC Stream Client: Record", Value: 2018}})
    if err != nil {
        log.Fatalf("printRecord.err: %v", err)
    }

    err = printRoute(client, &pb.StreamRequest{Pt: &pb.StreamPoint{Name: "gRPC Stream Client: Route", Value: 2018}})
    if err != nil {
        log.Fatalf("printRoute.err: %v", err)
    }
}

func printLists(client pb.StreamServiceClient, r *pb.StreamRequest) error {
    stream, err := client.List(context.Background(), r)
    if err != nil {
        return err
    }

    for {
        resp, err := stream.Recv()
        if err == io.EOF {
            break
        }
        if err != nil {
            return err
        }

        log.Printf("resp: pj.name: %s, pt.value: %d", resp.Pt.Name, resp.Pt.Value)
    }

    return nil
}

func printRecord(client pb.StreamServiceClient, r *pb.StreamRequest) error {
    stream, err := client.Record(context.Background())
    if err != nil {
        return err
    }

    for n := 0; n < 6; n++ {
        err := stream.Send(r)
        if err != nil {
            return err
        }
    }

    resp, err := stream.CloseAndRecv()
    if err != nil {
        return err
    }

    log.Printf("resp: pt.name: %s, pt.Value: %d", resp.Pt.Name, resp.Pt.Value)
    return nil
}

func printRoute(client pb.StreamServiceClient, r *pb.StreamRequest) error {
    stream, err := client.Route(context.Background())
    if err != nil {
        return err
    }

    for n := 0; n <= 6; n++ {
        err = stream.Send(r)
        if err != nil {
            return err
        }

        resp, err := stream.Recv()
        if err == io.EOF {
            break
        }
        if err != nil {
            return err
        }

        log.Printf("resp: pj.name: %s, pt.value: %d", resp.Pt.Name, resp.Pt.Value)
    }

    stream.CloseSend()
    return nil
}
// server.go
package main

import (
    "context"
    "io"
    "log"

    "google.golang.org/grpc"

    pb "helloworld.grpc/proto"
)

const (
    PORT = "9002"
)

func main() {
    conn, err := grpc.Dial(":"+PORT, grpc.WithInsecure())
    if err != nil {
        log.Fatalf("grpc.Dial err: %v", err)
    }

    defer conn.Close()

    client := pb.NewStreamServiceClient(conn)

    err = printLists(client, &pb.StreamRequest{Pt: &pb.StreamPoint{Name: "gRPC Stream Client: List", Value: 2018}})
    if err != nil {
        log.Fatalf("printLists.err: %v", err)
    }

    err = printRecord(client, &pb.StreamRequest{Pt: &pb.StreamPoint{Name: "gRPC Stream Client: Record", Value: 2018}})
    if err != nil {
        log.Fatalf("printRecord.err: %v", err)
    }

    err = printRoute(client, &pb.StreamRequest{Pt: &pb.StreamPoint{Name: "gRPC Stream Client: Route", Value: 2018}})
    if err != nil {
        log.Fatalf("printRoute.err: %v", err)
    }
}

func printLists(client pb.StreamServiceClient, r *pb.StreamRequest) error {
    stream, err := client.List(context.Background(), r)
    if err != nil {
        return err
    }

    for {
        resp, err := stream.Recv()
        if err == io.EOF {
            break
        }
        if err != nil {
            return err
        }

        log.Printf("resp: pj.name: %s, pt.value: %d", resp.Pt.Name, resp.Pt.Value)
    }

    return nil
}

func printRecord(client pb.StreamServiceClient, r *pb.StreamRequest) error {
    stream, err := client.Record(context.Background())
    if err != nil {
        return err
    }

    for n := 0; n < 6; n++ {
        err := stream.Send(r)
        if err != nil {
            return err
        }
    }

    resp, err := stream.CloseAndRecv()
    if err != nil {
        return err
    }

    log.Printf("resp: pt.name: %s, pt.Value: %d", resp.Pt.Name, resp.Pt.Value)
    return nil
}

func printRoute(client pb.StreamServiceClient, r *pb.StreamRequest) error {
    stream, err := client.Route(context.Background())
    if err != nil {
        return err
    }

    for n := 0; n <= 6; n++ {
        err = stream.Send(r)
        if err != nil {
            return err
        }

        resp, err := stream.Recv()
        if err == io.EOF {
            break
        }
        if err != nil {
            return err
        }

        log.Printf("resp: pj.name: %s, pt.value: %d", resp.Pt.Name, resp.Pt.Value)
    }

    stream.CloseSend()
    return nil
}

6. 攔截器interceptor

在每個 RPC 方法的前或后做某些事情,需要使用攔截器(interceptor)。grpc中有兩類攔截器:

普通方法:一元攔截器(grpc.UnaryInterceptor)

流方法:流攔截器(grpc.StreamInterceptor)

type UnaryServerInterceptor func(ctx context.Context, req interface{}, info *UnaryServerInfo, handler UnaryHandler) (resp interface{}, err error)

通過查看源碼可得知,要完成一個攔截器需要實現 UnaryServerInterceptor 方法。

type StreamServerInterceptor func(srv interface{}, ss ServerStream, info *StreamServerInfo, handler StreamHandler) error

采用開源項目 go-grpc-middleware 可以實現多個攔截器:

import "github.com/grpc-ecosystem/go-grpc-middleware"

myServer := grpc.NewServer(
    grpc.StreamInterceptor(grpc_middleware.ChainStreamServer(
        ...
    )),
    grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
       ...
    )),
)

 7. grpc傳輸json

參考:使用 JSON 協議的 gRPC      https://github.com/johanbrandhorst/grpc-json-example

gRPC 是基於 Google Protocol Buffers payload 格式的,默認格式是 Protobuf,但是 gRPC-Go 的實現中也對外暴露了 Codec interface ,它支持任意的 payload 編碼。我們可以使用任何一種格式,包括你自己定義的二進制格式、flatbuffers、或者使用我們今天要討論的 JSON ,作為請求和響應。

type Codec interface {
    // Marshal returns the wire format of v.
    Marshal(v interface{}) ([]byte, error)
    // Unmarshal parses the wire format into v.
    Unmarshal(data []byte, v interface{}) error
    // Name returns the name of the Codec implementation. The returned string
    // will be used as part of content type in transmission.  The result must be
    // static; the result cannot change between calls.
    Name() string
}
func RegisterCodec(codec Codec)

Codec defines the interface gRPC uses to encode and decode messages. Note that implementations of this interface must be thread safe; a Codec's methods can be called from concurrent goroutines.

The Codec will be stored and looked up by result of its Name() method, which should match the content-subtype of the encoding handled by the Codec. This is case-insensitive, and is stored and looked up as lowercase. If the result of calling Name() is an empty string, RegisterCodec will panic. See Content-Type on https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md#requests for more details.

server端修改

import _ "github.com/johanbrandhorst/grpc-json-example/codec"

client端修改

使用 gRPC 客戶端,你只需要使用合適的內容子類型作為 grpc.DialOption 來初始化:

import "github.com/johanbrandhorst/grpc-json-example/codec"
func main() {
    conn := grpc.Dial("localhost:1000",
        grpc.WithDefaultCallOptions(grpc.CallContentSubtype(codec.JSON{}.Name())),
    )
}

 

參考:

1.   gRPC 官方文檔中文版  http://doc.oschina.net/grpc?t=56831

2.   gRPC in 3 minutes (Go)  https://github.com/grpc/grpc-go/tree/master/examples

3.   https://github.com/golang/protobuf

4.   gRPC應用C++

5.      grpc-go基於雙向認證安全通信

6.      https://github.com/open-ness/edgenode/blob/master/pkg/interfaceservice/interface_service.go

7.      https://github.com/open-ness/edgecontroller/tree/master/cmd/interfaceservicecli

8.      Grpc+Grpc Gateway實踐一 介紹與環境安裝

9.  使用 JSON 協議的 gRPC  go語言中文網


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM