Go語(yǔ)言程序開(kāi)發(fā)gRPC服務(wù)
前言
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)文章
Go使用Google?Gemini?Pro?API創(chuàng)建簡(jiǎn)單聊天機(jī)器人
這篇文章主要為大家介紹了Go使用Google?Gemini?Pro?API創(chuàng)建簡(jiǎn)單聊天機(jī)器人實(shí)現(xiàn)過(guò)程詳解,本文將通過(guò)最新的gemini?go?sdk來(lái)實(shí)現(xiàn)命令行聊天機(jī)器人2023-12-12go內(nèi)存緩存BigCache使用入門(mén)詳解
這篇文章主要為大家介紹了go內(nèi)存緩存BigCache使用入門(mén)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09Go1.18新特性使用Generics泛型進(jìn)行流式處理
這篇文章主要為大家介紹了Go1.18新特性使用Generics泛型進(jìn)行流式處理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06Go并發(fā):使用sync.WaitGroup實(shí)現(xiàn)協(xié)程同步方式
這篇文章主要介紹了Go并發(fā):使用sync.WaitGroup實(shí)現(xiàn)協(xié)程同步方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-05-05go語(yǔ)言定義零值可用的類(lèi)型學(xué)習(xí)教程
這篇文章主要為大家介紹了go語(yǔ)言定義零值可用的類(lèi)型教程學(xué)習(xí),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06使用Golang的channel交叉打印兩個(gè)數(shù)組的操作
這篇文章主要介紹了使用Golang的channel交叉打印兩個(gè)數(shù)組的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-04-04淺談beego默認(rèn)處理靜態(tài)文件性能低下的問(wèn)題
下面小編就為大家?guī)?lái)一篇淺談beego默認(rèn)處理靜態(tài)文件性能低下的問(wèn)題。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-06-06Golang?流水線(xiàn)設(shè)計(jì)模式實(shí)踐示例詳解
這篇文章主要為大家介紹了Golang?流水線(xiàn)設(shè)計(jì)模式實(shí)踐示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12Golang 使用接口實(shí)現(xiàn)泛型的方法示例
這篇文章主要介紹了Golang 使用接口實(shí)現(xiàn)泛型的方法示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03go語(yǔ)言調(diào)用其他包中的函數(shù)簡(jiǎn)單示例
這篇文章主要給大家介紹了關(guān)于go語(yǔ)言調(diào)用其他包中的函數(shù)的相關(guān)資料,文中還介紹了Go語(yǔ)言同一個(gè)包中不同文件之間函數(shù)調(diào)用的相關(guān)問(wèn)題,需要的朋友可以參考下2023-01-01