亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Go語(yǔ)言程序開(kāi)發(fā)gRPC服務(wù)

 更新時(shí)間:2022年06月15日 15:39:45   作者:yongxinz  
這篇文章主要為大家介紹了Go語(yǔ)言程序開(kāi)發(fā)gRPC服務(wù),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

前言

gRPC 這項(xiàng)技術(shù)真是太棒了,接口約束嚴(yán)格,性能還高,在 k8s 和很多微服務(wù)框架中都有應(yīng)用。

作為一名程序員,學(xué)就對(duì)了。

之前用 Python 寫(xiě)過(guò)一些 gRPC 服務(wù),現(xiàn)在準(zhǔn)備用 Go 來(lái)感受一下原汁原味的 gRPC 程序開(kāi)發(fā)。

本文的特點(diǎn)是直接用代碼說(shuō)話(huà),通過(guò)開(kāi)箱即用的完整代碼,來(lái)介紹 gRPC 的各種使用方法。

代碼已經(jīng)上傳到 GitHub,下面正式開(kāi)始。

介紹

gRPC 是 Google 公司基于 Protobuf 開(kāi)發(fā)的跨語(yǔ)言的開(kāi)源 RPC 框架。gRPC 基于 HTTP/2 協(xié)議設(shè)計(jì),可以基于一個(gè) HTTP/2 鏈接提供多個(gè)服務(wù),對(duì)于移動(dòng)設(shè)備更加友好。

入門(mén)

首先來(lái)看一個(gè)最簡(jiǎn)單的 gRPC 服務(wù),第一步是定義 proto 文件,因?yàn)?gRPC 也是 C/S 架構(gòu),這一步相當(dāng)于明確接口規(guī)范。

proto

syntax?=?"proto3";
package?proto;
//?The?greeting?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;
}

使用 protoc-gen-go 內(nèi)置的 gRPC 插件生成 gRPC 代碼:

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

執(zhí)行完這個(gè)命令之后,會(huì)在當(dāng)前目錄生成一個(gè) helloworld.pb.go 文件,文件中分別定義了服務(wù)端和客戶(hù)端的接口:

//?For?semantics?around?ctx?use?and?closing/ending?streaming?RPCs,?please?refer?to?https://godoc.org/google.golang.org/grpc#ClientConn.NewStream.
type?GreeterClient?interface?{
????//?Sends?a?greeting
????SayHello(ctx?context.Context,?in?*HelloRequest,?opts?...grpc.CallOption)?(*HelloReply,?error)
}
//?GreeterServer?is?the?server?API?for?Greeter?service.
type?GreeterServer?interface?{
????//?Sends?a?greeting
????SayHello(context.Context,?*HelloRequest)?(*HelloReply,?error)
}

接下來(lái)就是寫(xiě)服務(wù)端和客戶(hù)端的代碼,分別實(shí)現(xiàn)對(duì)應(yīng)的接口。

server

package?main
import?(
????"context"
????"fmt"
????"grpc-server/proto"
????"log"
????"net"
????"google.golang.org/grpc"
????"google.golang.org/grpc/reflection"
)
type?greeter?struct?{
}
func?(*greeter)?SayHello(ctx?context.Context,?req?*proto.HelloRequest)?(*proto.HelloReply,?error)?{
????fmt.Println(req)
????reply?:=?&proto.HelloReply{Message:?"hello"}
????return?reply,?nil
}
func?main()?{
????lis,?err?:=?net.Listen("tcp",?":50051")
????if?err?!=?nil?{
????????log.Fatalf("failed?to?listen:?%v",?err)
????}
????server?:=?grpc.NewServer()
????//?注冊(cè)?grpcurl?所需的?reflection?服務(wù)
????reflection.Register(server)
????//?注冊(cè)業(yè)務(wù)服務(wù)
????proto.RegisterGreeterServer(server,?&greeter{})
????fmt.Println("grpc?server?start?...")
????if?err?:=?server.Serve(lis);?err?!=?nil?{
????????log.Fatalf("failed?to?serve:?%v",?err)
????}
}

client

package?main
import?(
????"context"
????"fmt"
????"grpc-client/proto"
????"log"
????"google.golang.org/grpc"
)
func?main()?{
????conn,?err?:=?grpc.Dial("localhost:50051",?grpc.WithInsecure())
????if?err?!=?nil?{
????????log.Fatal(err)
????}
????defer?conn.Close()
????client?:=?proto.NewGreeterClient(conn)
????reply,?err?:=?client.SayHello(context.Background(),?&proto.HelloRequest{Name:?"zhangsan"})
????if?err?!=?nil?{
????????log.Fatal(err)
????}
????fmt.Println(reply.Message)
}

這樣就完成了最基礎(chǔ)的 gRPC 服務(wù)的開(kāi)發(fā),接下來(lái)我們就在這個(gè)「基礎(chǔ)模板」上不斷豐富,學(xué)習(xí)更多特性。

流方式

接下來(lái)看看流的方式,顧名思義,數(shù)據(jù)可以源源不斷的發(fā)送和接收。

流的話(huà)分單向流和雙向流,這里我們直接通過(guò)雙向流來(lái)舉例。

proto

service?Greeter?{
????//?Sends?a?greeting
????rpc?SayHello?(HelloRequest)?returns?(HelloReply)?{}
????//?Sends?stream?message
????rpc?SayHelloStream?(stream?HelloRequest)?returns?(stream?HelloReply)?{}
}

增加一個(gè)流函數(shù) SayHelloStream,通過(guò) stream 關(guān)鍵詞來(lái)指定流特性。

需要重新生成 helloworld.pb.go 文件,這里不再多說(shuō)。

server

func?(*greeter)?SayHelloStream(stream?proto.Greeter_SayHelloStreamServer)?error?{
????for?{
????????args,?err?:=?stream.Recv()
????????if?err?!=?nil?{
????????????if?err?==?io.EOF?{
????????????????return?nil
????????????}
????????????return?err
????????}
????????fmt.Println("Recv:?"?+?args.Name)
????????reply?:=?&proto.HelloReply{Message:?"hi?"?+?args.Name}
????????err?=?stream.Send(reply)
????????if?err?!=?nil?{
????????????return?err
????????}
????}
}

在「基礎(chǔ)模板」上增加 SayHelloStream 函數(shù),其他都不需要變。

client

client?:=?proto.NewGreeterClient(conn)
//?流處理
stream,?err?:=?client.SayHelloStream(context.Background())
if?err?!=?nil?{
????log.Fatal(err)
}
//?發(fā)送消息
go?func()?{
????for?{
????????if?err?:=?stream.Send(&proto.HelloRequest{Name:?"zhangsan"});?err?!=?nil?{
????????????log.Fatal(err)
????????}
????????time.Sleep(time.Second)
????}
}()
//?接收消息
for?{
????reply,?err?:=?stream.Recv()
????if?err?!=?nil?{
????????if?err?==?io.EOF?{
????????????break
????????}
????????log.Fatal(err)
????}
????fmt.Println(reply.Message)
}

通過(guò)一個(gè) goroutine 發(fā)送消息,主程序的 for 循環(huán)接收消息。

執(zhí)行程序會(huì)發(fā)現(xiàn),服務(wù)端和客戶(hù)端都不斷有打印輸出。

驗(yàn)證器

接下來(lái)是驗(yàn)證器,這個(gè)需求是很自然會(huì)想到的,因?yàn)樯婕暗浇涌谥g的請(qǐng)求,那么對(duì)參數(shù)進(jìn)行適當(dāng)?shù)男r?yàn)是很有必要的。

在這里我們使用 protoc-gen-govalidators 和 go-grpc-middleware 來(lái)實(shí)現(xiàn)。

先安裝:

go?get?github.com/mwitkow/go-proto-validators/protoc-gen-govalidators
go?get?github.com/grpc-ecosystem/go-grpc-middleware

接下來(lái)修改 proto 文件:

proto

import?"github.com/mwitkow/go-proto-validators@v0.3.2/validator.proto";
message?HelloRequest?{
????string?name?=?1?[
????????(validator.field)?=?{regex:?"^[z]{2,5}$"}
????];
}

在這里對(duì) name 參數(shù)進(jìn)行校驗(yàn),需要符合正則的要求才可以正常請(qǐng)求。

還有其他驗(yàn)證規(guī)則,比如對(duì)數(shù)字大小進(jìn)行驗(yàn)證等,這里不做過(guò)多介紹。

接下來(lái)生成 *.pb.go 文件:

protoc??\
????--proto_path=${GOPATH}/pkg/mod?\
????--proto_path=${GOPATH}/pkg/mod/github.com/gogo/protobuf@v1.3.2?\
????--proto_path=.?\
????--govalidators_out=.?--go_out=plugins=grpc:.\
????*.proto

執(zhí)行成功之后,目錄下會(huì)多一個(gè) helloworld.validator.pb.go 文件。

這里需要特別注意一下,使用之前的簡(jiǎn)單命令是不行的,需要使用多個(gè) proto_path 參數(shù)指定導(dǎo)入 proto 文件的目錄。

官方給了兩種依賴(lài)情況,一個(gè)是 google protobuf,一個(gè)是 gogo protobuf。我這里使用的是第二種。

即使使用上面的命令,也有可能會(huì)遇到這個(gè)報(bào)錯(cuò):

Import?"github.com/mwitkow/go-proto-validators/validator.proto"?was?not?found?or?had?errors

但不要慌,大概率是引用路徑的問(wèn)題,一定要看好自己的安裝版本,以及在 GOPATH 中的具體路徑。

最后是服務(wù)端代碼改造:

引入包:

grpc_middleware?"github.com/grpc-ecosystem/go-grpc-middleware"
grpc_validator?"github.com/grpc-ecosystem/go-grpc-middleware/validator"

然后在初始化的時(shí)候增加驗(yàn)證器功能:

server?:=?grpc.NewServer(
????grpc.UnaryInterceptor(
????????grpc_middleware.ChainUnaryServer(
????????????grpc_validator.UnaryServerInterceptor(),
????????),
????),
????grpc.StreamInterceptor(
????????grpc_middleware.ChainStreamServer(
????????????grpc_validator.StreamServerInterceptor(),
????????),
????),
)

啟動(dòng)程序之后,我們?cè)儆弥暗目蛻?hù)端代碼來(lái)請(qǐng)求,會(huì)收到報(bào)錯(cuò):

2021/10/11?18:32:59?rpc?error:?code?=?InvalidArgument?desc?=?invalid?field?Name:?value?'zhangsan'?must?be?a?string?conforming?to?regex?"^[z]{2,5}$"
exit?status?1

因?yàn)?nbsp;name: zhangsan 是不符合服務(wù)端正則要求的,但是如果傳參 name: zzz,就可以正常返回了。

Token 認(rèn)證

終于到認(rèn)證環(huán)節(jié)了,先看 Token 認(rèn)證方式,然后再介紹證書(shū)認(rèn)證。

先改造服務(wù)端,有了上文驗(yàn)證器的經(jīng)驗(yàn),那么可以采用同樣的方式,寫(xiě)一個(gè)攔截器,然后在初始化 server 時(shí)候注入。

認(rèn)證函數(shù):

func?Auth(ctx?context.Context)?error?{
????md,?ok?:=?metadata.FromIncomingContext(ctx)
????if?!ok?{
????????return?fmt.Errorf("missing?credentials")
????}
????var?user?string
????var?password?string
????if?val,?ok?:=?md["user"];?ok?{
????????user?=?val[0]
????}
????if?val,?ok?:=?md["password"];?ok?{
????????password?=?val[0]
????}
????if?user?!=?"admin"?||?password?!=?"admin"?{
????????return?grpc.Errorf(codes.Unauthenticated,?"invalid?token")
????}
????return?nil
}

metadata.FromIncomingContext 從上下文讀取用戶(hù)名和密碼,然后和實(shí)際數(shù)據(jù)進(jìn)行比較,判斷是否通過(guò)認(rèn)證。

攔截器:

var?authInterceptor?grpc.UnaryServerInterceptor
authInterceptor?=?func(
????ctx?context.Context,?req?interface{},?info?*grpc.UnaryServerInfo,?handler?grpc.UnaryHandler,
)?(resp?interface{},?err?error)?{
????//攔截普通方法請(qǐng)求,驗(yàn)證?Token
????err?=?Auth(ctx)
????if?err?!=?nil?{
????????return
????}
????//?繼續(xù)處理請(qǐng)求
????return?handler(ctx,?req)
}

初始化:

server?:=?grpc.NewServer(
????grpc.UnaryInterceptor(
????????grpc_middleware.ChainUnaryServer(
????????????authInterceptor,
????????????grpc_validator.UnaryServerInterceptor(),
????????),
????),
????grpc.StreamInterceptor(
????????grpc_middleware.ChainStreamServer(
????????????grpc_validator.StreamServerInterceptor(),
????????),
????),
)

除了上文的驗(yàn)證器,又多了 Token 認(rèn)證攔截器 authInterceptor。

最后是客戶(hù)端改造,客戶(hù)端需要實(shí)現(xiàn) PerRPCCredentials 接口。

type?PerRPCCredentials?interface?{
????//?GetRequestMetadata?gets?the?current?request?metadata,?refreshing
????//?tokens?if?required.?This?should?be?called?by?the?transport?layer?on
????//?each?request,?and?the?data?should?be?populated?in?headers?or?other
????//?context.?If?a?status?code?is?returned,?it?will?be?used?as?the?status
????//?for?the?RPC.?uri?is?the?URI?of?the?entry?point?for?the?request.
????//?When?supported?by?the?underlying?implementation,?ctx?can?be?used?for
????//?timeout?and?cancellation.
????//?TODO(zhaoq):?Define?the?set?of?the?qualified?keys?instead?of?leaving
????//?it?as?an?arbitrary?string.
????GetRequestMetadata(ctx?context.Context,?uri?...string)?(
????????map[string]string,????error,
????)
????//?RequireTransportSecurity?indicates?whether?the?credentials?requires
????//?transport?security.
????RequireTransportSecurity()?bool
}

GetRequestMetadata 方法返回認(rèn)證需要的必要信息,RequireTransportSecurity 方法表示是否啟用安全鏈接,在生產(chǎn)環(huán)境中,一般都是啟用的,但為了測(cè)試方便,暫時(shí)這里不啟用了。

實(shí)現(xiàn)接口:

type?Authentication?struct?{
????User?????string
????Password?string
}
func?(a?*Authentication)?GetRequestMetadata(context.Context,?...string)?(
????map[string]string,?error,
)?{
????return?map[string]string{"user":?a.User,?"password":?a.Password},?nil
}
func?(a?*Authentication)?RequireTransportSecurity()?bool?{
????return?false
}

連接:

conn,?err?:=?grpc.Dial("localhost:50051",?grpc.WithInsecure(),?grpc.WithPerRPCCredentials(&auth))

好了,現(xiàn)在我們的服務(wù)就有 Token 認(rèn)證功能了。如果用戶(hù)名或密碼錯(cuò)誤,客戶(hù)端就會(huì)收到:

2021/10/11?20:39:35?rpc?error:?code?=?Unauthenticated?desc?=?invalid?token
exit?status?1

如果用戶(hù)名和密碼正確,則可以正常返回。

單向證書(shū)認(rèn)證

證書(shū)認(rèn)證分兩種方式:

  • 單向認(rèn)證
  • 雙向認(rèn)證

先看一下單向認(rèn)證方式:

生成證書(shū)

首先通過(guò) openssl 工具生成自簽名的 SSL 證書(shū)。

1、生成私鑰:

openssl?genrsa?-des3?-out?server.pass.key?2048

2、去除私鑰中密碼:

openssl?rsa?-in?server.pass.key?-out?server.key

3、生成 csr 文件:

openssl?req?-new?-key?server.key?-out?server.csr?-subj?"/C=CN/ST=beijing/L=beijing/O=grpcdev/OU=grpcdev/CN=example.grpcdev.cn"

4、生成證書(shū):

openssl?x509?-req?-days?365?-in?server.csr?-signkey?server.key?-out?server.crt

再多說(shuō)一句,分別介紹一下 X.509 證書(shū)包含的三個(gè)文件:key,csr 和 crt。

  • key: 服務(wù)器上的私鑰文件,用于對(duì)發(fā)送給客戶(hù)端數(shù)據(jù)的加密,以及對(duì)從客戶(hù)端接收到數(shù)據(jù)的解密。
  • csr: 證書(shū)簽名請(qǐng)求文件,用于提交給證書(shū)頒發(fā)機(jī)構(gòu)(CA)對(duì)證書(shū)簽名。
  • crt: 由證書(shū)頒發(fā)機(jī)構(gòu)(CA)簽名后的證書(shū),或者是開(kāi)發(fā)者自簽名的證書(shū),包含證書(shū)持有人的信息,持有人的公鑰,以及簽署者的簽名等信息。

gRPC 代碼

證書(shū)有了之后,剩下的就是改造程序了,首先是服務(wù)端代碼。

//?證書(shū)認(rèn)證-單向認(rèn)證
creds,?err?:=?credentials.NewServerTLSFromFile("keys/server.crt",?"keys/server.key")
if?err?!=?nil?{
????log.Fatal(err)
????return
}
server?:=?grpc.NewServer(grpc.Creds(creds))

只有幾行代碼需要修改,很簡(jiǎn)單,接下來(lái)是客戶(hù)端。

由于是單向認(rèn)證,不需要為客戶(hù)端單獨(dú)生成證書(shū),只需要把服務(wù)端的 crt 文件拷貝到客戶(hù)端對(duì)應(yīng)目錄下即可。

//?證書(shū)認(rèn)證-單向認(rèn)證
creds,?err?:=?credentials.NewClientTLSFromFile("keys/server.crt",?"example.grpcdev.cn")
if?err?!=?nil?{
????log.Fatal(err)
????return
}
conn,?err?:=?grpc.Dial("localhost:50051",?grpc.WithTransportCredentials(creds))

好了,現(xiàn)在我們的服務(wù)就支持單向證書(shū)認(rèn)證了。

但是還沒(méi)完,這里可能會(huì)遇到一個(gè)問(wèn)題:

2021/10/11?21:32:37?rpc?error:?code?=?Unavailable?desc?=?connection?error:?desc?=?"transport:?authentication?handshake?failed:?x509:?certificate?relies?on?legacy?Common?Name?field,?use?SANs?or?temporarily?enable?Common?Name?matching?with?GODEBUG=x509ignoreCN=0"
exit?status?1

原因是 Go 1.15 開(kāi)始廢棄了 CommonName,推薦使用 SAN 證書(shū)。如果想要兼容之前的方式,可以通過(guò)設(shè)置環(huán)境變量的方式支持,如下:

export?GODEBUG="x509ignoreCN=0"

但是需要注意,從 Go 1.17 開(kāi)始,環(huán)境變量就不再生效了,必須通過(guò) SAN 方式才行。所以,為了后續(xù)的 Go 版本升級(jí),還是早日支持為好。

雙向證書(shū)認(rèn)證

最后來(lái)看看雙向證書(shū)認(rèn)證。

生成帶 SAN 的證書(shū)

還是先生成證書(shū),但這次有一點(diǎn)不一樣,我們需要生成帶 SAN 擴(kuò)展的證書(shū)。

什么是 SAN?

SAN(Subject Alternative Name)是 SSL 標(biāo)準(zhǔn) x509 中定義的一個(gè)擴(kuò)展。使用了 SAN 字段的 SSL 證書(shū),可以擴(kuò)展此證書(shū)支持的域名,使得一個(gè)證書(shū)可以支持多個(gè)不同域名的解析。

將默認(rèn)的 OpenSSL 配置文件拷貝到當(dāng)前目錄。

Linux 系統(tǒng)在:

/etc/pki/tls/openssl.cnf

Mac 系統(tǒng)在:

/System/Library/OpenSSL/openssl.cnf

修改臨時(shí)配置文件,找到 [ req ] 段落,然后將下面語(yǔ)句的注釋去掉。

req_extensions?=?v3_req?#?The?extensions?to?add?to?a?certificate?request

接著添加以下配置:

[?v3_req?]
#?Extensions?to?add?to?a?certificate?request
basicConstraints?=?CA:FALSE
keyUsage?=?nonRepudiation,?digitalSignature,?keyEncipherment
subjectAltName?=?@alt_names
[?alt_names?]
DNS.1?=?www.example.grpcdev.cn

[ alt_names ] 位置可以配置多個(gè)域名,比如:

[?alt_names?]
DNS.1?=?www.example.grpcdev.cn
DNS.2?=?www.test.grpcdev.cn

為了測(cè)試方便,這里只配置一個(gè)域名。

1、生成 ca 證書(shū):

openssl?genrsa?-out?ca.key?2048
openssl?req?-x509?-new?-nodes?-key?ca.key?-subj?"/CN=example.grpcdev.com"?-days?5000?-out?ca.pem

2、生成服務(wù)端證書(shū):

#?生成證書(shū)
openssl?req?-new?-nodes?\
????-subj?"/C=CN/ST=Beijing/L=Beijing/O=grpcdev/OU=grpcdev/CN=www.example.grpcdev.cn"?\
????-config?<(cat?openssl.cnf?\
????????<(printf?"[SAN]\nsubjectAltName=DNS:www.example.grpcdev.cn"))?\
????-keyout?server.key?\
????-out?server.csr
#?簽名證書(shū)
openssl?x509?-req?-days?365000?\
????-in?server.csr?-CA?ca.pem?-CAkey?ca.key?-CAcreateserial?\
????-extfile?<(printf?"subjectAltName=DNS:www.example.grpcdev.cn")?\
????-out?server.pem

3、生成客戶(hù)端證書(shū):

#?生成證書(shū)
openssl?req?-new?-nodes?\
????-subj?"/C=CN/ST=Beijing/L=Beijing/O=grpcdev/OU=grpcdev/CN=www.example.grpcdev.cn"?\
????-config?<(cat?openssl.cnf?\
????????<(printf?"[SAN]\nsubjectAltName=DNS:www.example.grpcdev.cn"))?\
????-keyout?client.key?\
????-out?client.csr
#?簽名證書(shū)
openssl?x509?-req?-days?365000?\
????-in?client.csr?-CA?ca.pem?-CAkey?ca.key?-CAcreateserial?\
????-extfile?<(printf?"subjectAltName=DNS:www.example.grpcdev.cn")?\
????-out?client.pem

gRPC 代碼

接下來(lái)開(kāi)始修改代碼,先看服務(wù)端:

//?證書(shū)認(rèn)證-雙向認(rèn)證
//?從證書(shū)相關(guān)文件中讀取和解析信息,得到證書(shū)公鑰、密鑰對(duì)
cert,?_?:=?tls.LoadX509KeyPair("cert/server.pem",?"cert/server.key")
//?創(chuàng)建一個(gè)新的、空的?CertPool
certPool?:=?x509.NewCertPool()
ca,?_?:=?ioutil.ReadFile("cert/ca.pem")
//?嘗試解析所傳入的 PEM 編碼的證書(shū)。如果解析成功會(huì)將其加到 CertPool 中,便于后面的使用
certPool.AppendCertsFromPEM(ca)
//?構(gòu)建基于?TLS?的?TransportCredentials?選項(xiàng)
creds?:=?credentials.NewTLS(&tls.Config{
????//?設(shè)置證書(shū)鏈,允許包含一個(gè)或多個(gè)
????Certificates:?[]tls.Certificate{cert},
????//?要求必須校驗(yàn)客戶(hù)端的證書(shū)??梢愿鶕?jù)實(shí)際情況選用以下參數(shù)
????ClientAuth:?tls.RequireAndVerifyClientCert,
????//?設(shè)置根證書(shū)的集合,校驗(yàn)方式使用?ClientAuth?中設(shè)定的模式
????ClientCAs:?certPool,
})

再看客戶(hù)端:

//?證書(shū)認(rèn)證-雙向認(rèn)證
//?從證書(shū)相關(guān)文件中讀取和解析信息,得到證書(shū)公鑰、密鑰對(duì)
cert,?_?:=?tls.LoadX509KeyPair("cert/client.pem",?"cert/client.key")
//?創(chuàng)建一個(gè)新的、空的?CertPool
certPool?:=?x509.NewCertPool()
ca,?_?:=?ioutil.ReadFile("cert/ca.pem")
//?嘗試解析所傳入的 PEM 編碼的證書(shū)。如果解析成功會(huì)將其加到 CertPool 中,便于后面的使用
certPool.AppendCertsFromPEM(ca)
//?構(gòu)建基于?TLS?的?TransportCredentials?選項(xiàng)
creds?:=?credentials.NewTLS(&tls.Config{
????//?設(shè)置證書(shū)鏈,允許包含一個(gè)或多個(gè)
????Certificates:?[]tls.Certificate{cert},
????//?要求必須校驗(yàn)客戶(hù)端的證書(shū)??梢愿鶕?jù)實(shí)際情況選用以下參數(shù)
????ServerName:?"www.example.grpcdev.cn",
????RootCAs:????certPool,
})

大功告成。

Python 客戶(hù)端

前面已經(jīng)說(shuō)了,gRPC 是跨語(yǔ)言的,那么,本文最后我們用 Python 寫(xiě)一個(gè)客戶(hù)端,來(lái)請(qǐng)求 Go 服務(wù)端。

使用最簡(jiǎn)單的方式來(lái)實(shí)現(xiàn):

proto 文件就使用最開(kāi)始的「基礎(chǔ)模板」的 proto 文件:

syntax?=?"proto3";
package?proto;
//?The?greeting?service?definition.
service?Greeter?{
????//?Sends?a?greeting
????rpc?SayHello?(HelloRequest)?returns?(HelloReply)?{}
????//?Sends?stream?message
????rpc?SayHelloStream?(stream?HelloRequest)?returns?(stream?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;
}

同樣的,也需要通過(guò)命令行的方式生成 pb.py 文件:

python3?-m?grpc_tools.protoc?-I?.?--python_out=.?--grpc_python_out=.?./*.proto

執(zhí)行成功之后會(huì)在目錄下生成 helloworld_pb2.py 和 helloworld_pb2_grpc.py 兩個(gè)文件。

這個(gè)過(guò)程也可能會(huì)報(bào)錯(cuò):

ModuleNotFoundError:?No?module?named?'grpc_tools'

別慌,是缺少包,安裝就好:

pip3?install?grpcio
pip3?install?grpcio-tools

最后看一下 Python 客戶(hù)端代碼:

import?grpc
import?helloworld_pb2
import?helloworld_pb2_grpc
def?main():
????channel?=?grpc.insecure_channel("127.0.0.1:50051")
????stub?=?helloworld_pb2_grpc.GreeterStub(channel)
????response?=?stub.SayHello(helloworld_pb2.HelloRequest(name="zhangsan"))
????print(response.message)
if?__name__?==?'__main__':
????main()

這樣,就可以通過(guò) Python 客戶(hù)端請(qǐng)求 Go 啟的服務(wù)端服務(wù)了。

總結(jié)

本文通過(guò)實(shí)戰(zhàn)角度出發(fā),直接用代碼說(shuō)話(huà),來(lái)說(shuō)明 gRPC 的一些應(yīng)用。

內(nèi)容包括簡(jiǎn)單的 gRPC 服務(wù),流處理模式,驗(yàn)證器,Token 認(rèn)證和證書(shū)認(rèn)證。

除此之外,還有其他值得研究的內(nèi)容,比如超時(shí)控制,REST 接口和負(fù)載均衡等。以后還會(huì)抽時(shí)間繼續(xù)完善剩下這部分內(nèi)容。

本文中的代碼都經(jīng)過(guò)測(cè)試驗(yàn)證,可以直接執(zhí)行,并且已經(jīng)上傳到 GitHub,小伙伴們可以一遍看源碼,一遍對(duì)照文章內(nèi)容來(lái)學(xué)習(xí)。

源碼地址:

https://github.com/yongxinz/go-example/tree/main/grpc-example

https://github.com/yongxinz/gopher/tree/main/blog

以上就是Go語(yǔ)言程序開(kāi)發(fā)gRPC服務(wù)的詳細(xì)內(nèi)容,更多關(guān)于Go語(yǔ)言開(kāi)發(fā)gRPC服務(wù)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評(píng)論