99re热这里只有精品视频,7777色鬼xxxx欧美色妇,国产成人精品一区二三区在线观看,内射爽无广熟女亚洲,精品人妻av一区二区三区

Go 語(yǔ)言 gRPC 和 Protobuf 擴(kuò)展

2023-03-22 15:03 更新

原文鏈接:https://chai2010.cn/advanced-go-programming-book/ch4-rpc/ch4-06-grpc-ext.html


4.6 gRPC 和 Protobuf 擴(kuò)展

目前開(kāi)源社區(qū)已經(jīng)圍繞 Protobuf 和 gRPC 開(kāi)發(fā)出眾多擴(kuò)展,形成了龐大的生態(tài)。本節(jié)我們將簡(jiǎn)單介紹驗(yàn)證器和 REST 接口擴(kuò)展。

4.6.1 驗(yàn)證器

到目前為止,我們接觸的全部是第三版的 Protobuf 語(yǔ)法。第二版的 Protobuf 有個(gè)默認(rèn)值特性,可以為字符串或數(shù)值類(lèi)型的成員定義默認(rèn)值。

我們采用第二版的 Protobuf 語(yǔ)法創(chuàng)建文件:

syntax = "proto2";

package main;

message Message {
	optional string name = 1 [default = "gopher"];
	optional int32 age = 2 [default = 10];
}

內(nèi)置的默認(rèn)值語(yǔ)法其實(shí)是通過(guò) Protobuf 的擴(kuò)展選項(xiàng)特性實(shí)現(xiàn)。在第三版的 Protobuf 中不再支持默認(rèn)值特性,但是我們可以通過(guò)擴(kuò)展選項(xiàng)自己模擬默認(rèn)值特性。

下面是用 proto3 語(yǔ)法的擴(kuò)展特性重新改寫(xiě)上述的 proto 文件:

syntax = "proto3";

package main;

import "google/protobuf/descriptor.proto";

extend google.protobuf.FieldOptions {
	string default_string = 50000;
	int32 default_int = 50001;
}

message Message {
	string name = 1 [(default_string) = "gopher"];
	int32 age = 2[(default_int) = 10];
}

其中成員后面的方括號(hào)內(nèi)部的就是擴(kuò)展語(yǔ)法。重新生成 Go 語(yǔ)言代碼,里面會(huì)包含擴(kuò)展選項(xiàng)相關(guān)的元信息:

var E_DefaultString = &proto.ExtensionDesc{
    ExtendedType:  (*descriptor.FieldOptions)(nil),
    ExtensionType: (*string)(nil),
    Field:         50000,
    Name:          "main.default_string",
    Tag:           "bytes,50000,opt,name=default_string,json=defaultString",
    Filename:      "helloworld.proto",
}

var E_DefaultInt = &proto.ExtensionDesc{
    ExtendedType:  (*descriptor.FieldOptions)(nil),
    ExtensionType: (*int32)(nil),
    Field:         50001,
    Name:          "main.default_int",
    Tag:           "varint,50001,opt,name=default_int,json=defaultInt",
    Filename:      "helloworld.proto",
}

我們可以在運(yùn)行時(shí)通過(guò)類(lèi)似反射的技術(shù)解析出 Message 每個(gè)成員定義的擴(kuò)展選項(xiàng),然后從每個(gè)擴(kuò)展的相關(guān)聯(lián)的信息中解析出我們定義的默認(rèn)值。

在開(kāi)源社區(qū)中,github.com/mwitkow/go-proto-validators 已經(jīng)基于 Protobuf 的擴(kuò)展特性實(shí)現(xiàn)了功能較為強(qiáng)大的驗(yàn)證器功能。要使用該驗(yàn)證器首先需要下載其提供的代碼生成插件:

$ go get github.com/mwitkow/go-proto-validators/protoc-gen-govalidators

然后基于 go-proto-validators 驗(yàn)證器的規(guī)則為 Message 成員增加驗(yàn)證規(guī)則:

syntax = "proto3";

package main;

import "github.com/mwitkow/go-proto-validators/validator.proto";

message Message {
	string important_string = 1 [
		(validator.field) = {regex: "^[a-z]{2,5}$"}
	];
	int32 age = 2 [
		(validator.field) = {int_gt: 0, int_lt: 100}
	];
}

在方括弧表示的成員擴(kuò)展中,validator.field 表示擴(kuò)展是 validator 包中定義的名為 field 擴(kuò)展選項(xiàng)。validator.field 的類(lèi)型是 FieldValidator 結(jié)構(gòu)體,在導(dǎo)入的 validator.proto 文件中定義。

所有的驗(yàn)證規(guī)則都由 validator.proto 文件中的 FieldValidator 定義:

syntax = "proto2";
package validator;

import "google/protobuf/descriptor.proto";

extend google.protobuf.FieldOptions {
	optional FieldValidator field = 65020;
}

message FieldValidator {
	// Uses a Golang RE2-syntax regex to match the field contents.
	optional string regex = 1;
	// Field value of integer strictly greater than this value.
	optional int64 int_gt = 2;
	// Field value of integer strictly smaller than this value.
	optional int64 int_lt = 3;

	// ... more ...
}

從 FieldValidator 定義的注釋中我們可以看到驗(yàn)證器擴(kuò)展的一些語(yǔ)法:其中 regex 表示用于字符串驗(yàn)證的正則表達(dá)式,int_gt 和 int_lt 表示數(shù)值的范圍。

然后采用以下的命令生成驗(yàn)證函數(shù)代碼:

protoc  \
    --proto_path=${GOPATH}/src \
    --proto_path=${GOPATH}/src/github.com/google/protobuf/src \
    --proto_path=. \
    --govalidators_out=. --go_out=plugins=grpc:.\
    hello.proto

windows: 替換 ?${GOPATH}? 為 ?%GOPATH%? 即可.

以上的命令會(huì)調(diào)用 protoc-gen-govalidators 程序,生成一個(gè)獨(dú)立的名為 hello.validator.pb.go 的文件:

var _regex_Message_ImportantString = regexp.MustCompile("^[a-z]{2,5}$")

func (this *Message) Validate() error {
    if !_regex_Message_ImportantString.MatchString(this.ImportantString) {
        return go_proto_validators.FieldError("ImportantString", fmt.Errorf(
            `value '%v' must be a string conforming to regex "^[a-z]{2,5}$"`,
            this.ImportantString,
        ))
    }
    if !(this.Age> 0) {
        return go_proto_validators.FieldError("Age", fmt.Errorf(
            `value '%v' must be greater than '0'`, this.Age,
        ))
    }
    if !(this.Age < 100) {
        return go_proto_validators.FieldError("Age", fmt.Errorf(
            `value '%v' must be less than '100'`, this.Age,
        ))
    }
    return nil
}

生成的代碼為 Message 結(jié)構(gòu)體增加了一個(gè) Validate 方法,用于驗(yàn)證該成員是否滿足 Protobuf 中定義的條件約束。無(wú)論采用何種類(lèi)型,所有的 Validate 方法都用相同的簽名,因此可以滿足相同的驗(yàn)證接口。

通過(guò)生成的驗(yàn)證函數(shù),并結(jié)合 gRPC 的截取器,我們可以很容易為每個(gè)方法的輸入?yún)?shù)和返回值進(jìn)行驗(yàn)證。

4.6.2 REST 接口

gRPC 服務(wù)一般用于集群內(nèi)部通信,如果需要對(duì)外暴露服務(wù)一般會(huì)提供等價(jià)的 REST 接口。通過(guò) REST 接口比較方便前端 JavaScript 和后端交互。開(kāi)源社區(qū)中的 grpc-gateway 項(xiàng)目就實(shí)現(xiàn)了將 gRPC 服務(wù)轉(zhuǎn)為 REST 服務(wù)的能力。

grpc-gateway 的工作原理如下圖:


圖 4-2 gRPC-Gateway 工作流程

通過(guò)在 Protobuf 文件中添加路由相關(guān)的元信息,通過(guò)自定義的代碼插件生成路由相關(guān)的處理代碼,最終將 REST 請(qǐng)求轉(zhuǎn)給更后端的 gRPC 服務(wù)處理。

路由擴(kuò)展元信息也是通過(guò) Protobuf 的元數(shù)據(jù)擴(kuò)展用法提供:

syntax = "proto3";

package main;

import "google/api/annotations.proto";

message StringMessage {
  string value = 1;
}

service RestService {
	rpc Get(StringMessage) returns (StringMessage) {
		option (google.api.http) = {
			get: "/get/{value}"
		};
	}
	rpc Post(StringMessage) returns (StringMessage) {
		option (google.api.http) = {
			post: "/post"
			body: "*"
		};
	}
}

我們首先為 gRPC 定義了 Get 和 Post 方法,然后通過(guò)元擴(kuò)展語(yǔ)法在對(duì)應(yīng)的方法后添加路由信息。其中 “/get/{value}” 路徑對(duì)應(yīng)的是 Get 方法,{value} 部分對(duì)應(yīng)參數(shù)中的 value 成員,結(jié)果通過(guò) json 格式返回。Post 方法對(duì)應(yīng) “/post” 路徑,body 中包含 json 格式的請(qǐng)求信息。

然后通過(guò)以下命令安裝 protoc-gen-grpc-gateway 插件:

go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway

再通過(guò)插件生成 grpc-gateway 必須的路由處理代碼:

$ protoc -I/usr/local/include -I. \
    -I$GOPATH/src \
    -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
    --grpc-gateway_out=. --go_out=plugins=grpc:.\
    hello.proto

windows: 替換 ?${GOPATH}? 為 ?%GOPATH%? 即可.

插件會(huì)為 RestService 服務(wù)生成對(duì)應(yīng)的 RegisterRestServiceHandlerFromEndpoint 函數(shù):

func RegisterRestServiceHandlerFromEndpoint(
    ctx context.Context, mux *runtime.ServeMux, endpoint string,
    opts []grpc.DialOption,
) (err error) {
    ...
}

RegisterRestServiceHandlerFromEndpoint 函數(shù)用于將定義了 Rest 接口的請(qǐng)求轉(zhuǎn)發(fā)到真正的 gRPC 服務(wù)。注冊(cè)路由處理函數(shù)之后就可以啟動(dòng) Web 服務(wù)了:

func main() {
    ctx := context.Background()
    ctx, cancel := context.WithCancel(ctx)
    defer cancel()

    mux := runtime.NewServeMux()

    err := RegisterRestServiceHandlerFromEndpoint(
        ctx, mux, "localhost:5000",
        []grpc.DialOption{grpc.WithInsecure()},
    )
    if err != nil {
        log.Fatal(err)
    }

    http.ListenAndServe(":8080", mux)
}

啟動(dòng) grpc 服務(wù) , 端口 5000

type RestServiceImpl struct{}

func (r *RestServiceImpl) Get(ctx context.Context, message *StringMessage) (*StringMessage, error) {
    return &StringMessage{Value: "Get hi:" + message.Value + "#"}, nil
}

func (r *RestServiceImpl) Post(ctx context.Context, message *StringMessage) (*StringMessage, error) {
    return &StringMessage{Value: "Post hi:" + message.Value + "@"}, nil
}
func main() {
    grpcServer := grpc.NewServer()
    RegisterRestServiceServer(grpcServer, new(RestServiceImpl))
    lis, _ := net.Listen("tcp", ":5000")
    grpcServer.Serve(lis)
}

首先通過(guò) runtime.NewServeMux() 函數(shù)創(chuàng)建路由處理器,然后通過(guò) RegisterRestServiceHandlerFromEndpoint 函數(shù)將 RestService 服務(wù)相關(guān)的 REST 接口中轉(zhuǎn)到后面的 gRPC 服務(wù)。grpc-gateway 提供的 runtime.ServeMux 類(lèi)也實(shí)現(xiàn)了 http.Handler 接口,因此可以和標(biāo)準(zhǔn)庫(kù)中的相關(guān)函數(shù)配合使用。

當(dāng) gRPC 和 REST 服務(wù)全部啟動(dòng)之后,就可以用 curl 請(qǐng)求 REST 服務(wù)了:

$ curl localhost:8080/get/gopher
{"value":"Get: gopher"}

$ curl localhost:8080/post -X POST --data '{"value":"grpc"}'
{"value":"Post: grpc"}

在對(duì)外公布 REST 接口時(shí),我們一般還會(huì)提供一個(gè) Swagger 格式的文件用于描述這個(gè)接口規(guī)范。

$ go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger

$ protoc -I. \
  -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
  --swagger_out=. \
  hello.proto

然后會(huì)生成一個(gè) hello.swagger.json 文件。這樣的話就可以通過(guò) swagger-ui 這個(gè)項(xiàng)目,在網(wǎng)頁(yè)中提供 REST 接口的文檔和測(cè)試等功能。

4.6.3 Nginx

最新的 Nginx 對(duì) gRPC 提供了深度支持??梢酝ㄟ^(guò) Nginx 將后端多個(gè) gRPC 服務(wù)聚合到一個(gè) Nginx 服務(wù)。同時(shí) Nginx 也提供了為同一種 gRPC 服務(wù)注冊(cè)多個(gè)后端的功能,這樣可以輕松實(shí)現(xiàn) gRPC 負(fù)載均衡的支持。Nginx 的 gRPC 擴(kuò)展是一個(gè)較大的主題,感興趣的讀者可以自行參考相關(guān)文檔。



以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)