gRPC學(xué)習(xí)之五:gRPC-Gateway實(shí)戰(zhàn)

歡迎訪問(wèn)我的GitHub

https://github.com/zq2599/blog_demos

內(nèi)容:所有原創(chuàng)文章分類(lèi)匯總及配套源碼,涉及Java、Docker、Kubernetes、DevOPS等;

gRPC學(xué)習(xí)系列文章鏈接

  1. 在CentOS7部署和設(shè)置GO
  2. GO的gRPC開(kāi)發(fā)環(huán)境準(zhǔn)備
  3. 初試GO版gRPC開(kāi)發(fā)
  4. 實(shí)戰(zhàn)四類(lèi)服務(wù)方法
  5. gRPC-Gateway實(shí)戰(zhàn)
  6. gRPC-Gateway集成swagger

本篇概覽

  • 本文《gRPC學(xué)習(xí)》系列的第五篇,gRPC常用于服務(wù)端之間的相互調(diào)用,如果想把服務(wù)暴露給前端,雖然動(dòng)手修改服務(wù)端也能實(shí)現(xiàn),但似乎增加了不少工作量,此時(shí)還可以選擇gRPC-Gateway方式來(lái)快速將gRPC服務(wù)以http的方式暴露出來(lái);
  • gRPC-Gateway原理如下圖,借助grpc-gateway插件,可以基于proto文件生成反向代理(Reverse Proxy)的代碼,這個(gè)反向代理運(yùn)行起來(lái)后,對(duì)外提供RESTful服務(wù),收到RESTful請(qǐng)求后通過(guò)gRPC調(diào)用原來(lái)的gRPC服務(wù):
在這里插入圖片描述
  • 本文展示了gRPC-Gateway環(huán)境搭建、開(kāi)發(fā)、驗(yàn)證的整個(gè)過(guò)程,由以下步驟組成:
  1. 極速搭建gRPC-Gateway環(huán)境;
  2. 編寫(xiě)proto文件;
  3. 根據(jù)proto文件生成gRPC、gRPC-Gateway源碼;
  4. 添加業(yè)務(wù)代碼;
  5. 編譯、運(yùn)行、驗(yàn)證;

提前說(shuō)明文件和目錄

  • 本次實(shí)戰(zhàn)在<font color="blue">$GOPATH/src</font>目錄下新增文件夾<font color="red">helloworld</font>,里面總共有以下內(nèi)容:
[golang@centos7 src]$ tree helloworld/
helloworld/
├── gateway
│   └── helloworld.gw.go
├── helloworld.pb.go
├── helloworld.pb.gw.go
├── helloworld.proto
├── helloworld.swagger.json
└── server
    └── server.go
  • 準(zhǔn)備工作完成,接下來(lái)正式開(kāi)始開(kāi)發(fā);

前提條件

  • 本文的所有操作都沒(méi)有用到<font color="blue">root</font>賬號(hào),而是前文創(chuàng)建的<font color="red">golang</font>賬號(hào);
  • 請(qǐng)參照以下兩篇文章將GO環(huán)境和gRPC環(huán)境搭建好:
  1. 在CentOS7部署和設(shè)置GO
  2. GO的gRPC開(kāi)發(fā)環(huán)境準(zhǔn)備

極速搭建gRPC-Gateway環(huán)境

  • 所謂的<font color="blue">搭建gRPC-Gateway環(huán)境</font>,其實(shí)是完成以下三件事:
在這里插入圖片描述
  1. 在搭建環(huán)境時(shí)參考了一些網(wǎng)上的文章,結(jié)果遇到了各種問(wèn)題一直沒(méi)有成功(我當(dāng)然不會(huì)認(rèn)為文章有問(wèn)題,必須認(rèn)識(shí)到是自己能力不足的原因所致);
  2. 經(jīng)過(guò)反復(fù)折騰后終于成功后,我把所有操作做成一個(gè)shell腳本,執(zhí)行以下命令即可完成上圖中的所有操作:
curl -o install-grpc-gateway.sh \
https://raw.githubusercontent.com/zq2599/blog_demos/master/files/install-grpc-gateway.sh \
&& chmod a+x ./install-grpc-gateway.sh \
&& ./install-grpc-gateway.sh
  1. 進(jìn)入<font color="blue">$GOPATH/bin</font>目錄,可見(jiàn)新增兩個(gè)文件<font color="red">protoc-gen-grpc-gateway</font>和<font color="blue">protoc-gen-swagger</font>:
[golang@centos7 ~]$ cd $GOPATH/bin
[golang@centos7 bin]$ ls -al
總用量 26708
drwxrwxr-x. 2 golang golang      98 12月 19 08:59 .
drwxrwxr-x. 5 golang golang      39 12月 19 08:21 ..
-rwxr-x---. 1 golang golang 5253272 12月 19 08:20 protoc
-rwxrwxr-x. 1 golang golang 8461147 12月 19 08:21 protoc-gen-go
-rwxrwxr-x. 1 golang golang 6717463 12月 19 08:59 protoc-gen-grpc-gateway
-rwxrwxr-x. 1 golang golang 6908535 12月 19 08:59 protoc-gen-swagger
  • 現(xiàn)在環(huán)境準(zhǔn)備好了,開(kāi)始開(kāi)發(fā);

編寫(xiě)proto文件

  • 在<font color="blue">$GOPATH/src</font>目錄下,新建文件夾<font color="blue">helloworld</font>,里面新建文件<font color="red">helloworld.proto</font>,內(nèi)容如下,有幾處要注意的地方稍后會(huì)說(shuō):
// 協(xié)議類(lèi)型
syntax = "proto3";

// 包名
package helloworld;

import "google/api/annotations.proto";

// 定義的服務(wù)名
service Greeter {
  // 具體的遠(yuǎn)程服務(wù)方法
  rpc SayHello (HelloRequest) returns (HelloReply) {
    option (google.api.http) = {
      post: "/helloworld"
      body: "*"
    };
  }
}

// SayHello方法的入?yún)ⅲ挥幸粋€(gè)字符串字段
message HelloRequest {
  string name = 1;
}

// SayHello方法的返回值,只有一個(gè)字符串字段
message HelloReply {
  string message = 1;
}
  • 上述proto文件有以下幾處要注意的地方:
  1. 整個(gè)文件其實(shí)就是以 《初試GO版gRPC開(kāi)發(fā)》一文中的helloworld.proto為基礎(chǔ),增加了兩處內(nèi)容;
  2. 增加的第一處,是用<font color="blue">import</font>關(guān)鍵詞導(dǎo)入<font color="red">google/api/annotations.proto</font>;
  3. 增加的第二處,是<font color="blue">SayHello</font>方法的聲明處,增加了<font color="red">option</font>配置,作用是配置<font color="blue">SayHello</font>方法對(duì)外暴露的RESTful接口的信息;
  4. 在使用<font color="blue">protoc-gen-grpc-gateway</font>的時(shí)候,上述兩處配置會(huì)被識(shí)別到并生成對(duì)應(yīng)的代碼;

根據(jù)proto文件生成gRPC、gRPC-Gateway源碼

  1. proto文件編寫(xiě)完成,接下來(lái)是生成gRPC、gRPC-Gateway的源碼;
  2. 生成gRPC源碼的命令咱們前面的文章中已經(jīng)用過(guò),如下:
protoc -I. \
-I$GOPATH/src \
-I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
--go_out=plugins=grpc:. \
helloworld.proto
  1. 執(zhí)行完成后會(huì)在當(dāng)前目錄生成<font color="blue">helloworld.pb.go</font>文件;
  2. 執(zhí)行生成gRPC-Gateway源碼的命令:
protoc -I. \
-I$GOPATH/src \
-I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
--grpc-gateway_out=logtostderr=true:. \
helloworld.proto
  1. 執(zhí)行完成后會(huì)在當(dāng)前目錄生成<font color="blue">helloworld.pb.gw.go</font>文件;
  2. 執(zhí)行生成swagger文件的命令:
protoc -I. \
-I$GOPATH/src \
-I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
--swagger_out=logtostderr=true:. \
helloworld.proto
  1. 執(zhí)行完成后會(huì)在當(dāng)前目錄生成<font color="blue">helloworld.swagger.json</font>文件;
  2. 至此,helloworld目錄下一共有這些內(nèi)容:
[golang@centos7 src]$ tree helloworld/
helloworld/
├── helloworld.pb.go
├── helloworld.pb.gw.go
├── helloworld.proto
└── helloworld.swagger.json

0 directories, 4 files
  1. 接下來(lái)開(kāi)始編碼,把運(yùn)行整個(gè)服務(wù)所需的代碼補(bǔ)全;
  2. 由于篇幅限制,本文暫不提及swagger相關(guān)的開(kāi)發(fā)和驗(yàn)證,因此生成的<font color="blue">helloworld.swagger.json</font>文件本篇用不上,留待下一篇文章使用;

編寫(xiě)服務(wù)端代碼server.go并啟動(dòng)

  1. 接下來(lái)編寫(xiě)服務(wù)端代碼server.go,這個(gè)和《初試GO版gRPC開(kāi)發(fā)》中的server.go內(nèi)容一樣;
  2. 在<font color="blue">$GOPATH/src/helloworld</font>目錄下新建文件夾<font color="red">server</font>,在此文件夾下新建<font color="red">server.go</font>,內(nèi)容如下,已經(jīng)添加詳細(xì)注釋:
package main

import (
    "context"
    "log"
    "net"

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

const (
    port = ":50051"
)

// 定義結(jié)構(gòu)體,在調(diào)用注冊(cè)api的時(shí)候作為入?yún)ⅲ?// 該結(jié)構(gòu)體會(huì)帶上SayHello方法,里面是業(yè)務(wù)代碼
// 這樣遠(yuǎn)程調(diào)用時(shí)就執(zhí)行了業(yè)務(wù)代碼了
type server struct {
    // pb.go中自動(dòng)生成的,是個(gè)空結(jié)構(gòu)體
    pb.UnimplementedGreeterServer
}

// 業(yè)務(wù)代碼在此寫(xiě),客戶端遠(yuǎn)程調(diào)用SayHello時(shí),
// 會(huì)執(zhí)行這里的代碼
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
    // 打印請(qǐng)求參數(shù)
    log.Printf("Received: %v", in.GetName())
    // 實(shí)例化結(jié)構(gòu)體HelloReply,作為返回值
    return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}

func main() {
    // 要監(jiān)聽(tīng)的協(xié)議和端口
    lis, err := net.Listen("tcp", port)
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }

    // 實(shí)例化gRPC server結(jié)構(gòu)體
    s := grpc.NewServer()

    // 服務(wù)注冊(cè)
    pb.RegisterGreeterServer(s, &server{})

    log.Println("開(kāi)始監(jiān)聽(tīng),等待遠(yuǎn)程調(diào)用...")

    if err := s.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}
  1. 在server.go所在目錄執(zhí)行<font color="blue">go run server.go</font>,控制臺(tái)提示如下:
[golang@centos7 server]$ go run server.go 
2020/12/13 08:20:32 開(kāi)始監(jiān)聽(tīng),等待遠(yuǎn)程調(diào)用...
  1. 此時(shí)gRPC的服務(wù)端已啟動(dòng),可以響應(yīng)遠(yuǎn)程調(diào)用,接下來(lái)開(kāi)發(fā)反向代理(Reverse Proxy);

編寫(xiě)反向代理(Reverse Proxy)代碼helloworld.gw.go并啟動(dòng)

  • 接下來(lái)編反向代理(Reverse Proxy)代碼<font color="blue">helloworld.gw.go</font>;
  • 在<font color="blue">$GOPATH/src/helloworld</font>目錄下新建文件夾<font color="red">gateway</font>,在此文件夾下新建<font color="red">helloworld.gw.go</font>,內(nèi)容如下,有幾處要注意的地方稍后會(huì)說(shuō)明:
package main

import (
    "flag"
    "fmt"
    "net/http"
    gw "helloworld"

    "github.com/grpc-ecosystem/grpc-gateway/runtime"
    "golang.org/x/net/context"
    "google.golang.org/grpc"
)

var (
    echoEndpoint = flag.String("echo_endpoint", "localhost:50051", "endpoint of YourService")
)

func run() error {

    ctx := context.Background()
    ctx, cancel := context.WithCancel(ctx)
    defer cancel()
    mux := runtime.NewServeMux()
    opts := []grpc.DialOption{grpc.WithInsecure()}
    err := gw.RegisterGreeterHandlerFromEndpoint(ctx, mux, *echoEndpoint, opts)

    if err != nil {
        return err
    }

    return http.ListenAndServe(":9090", mux)
}

func main() {
    if err := run(); err != nil {
        fmt.Print(err.Error())
    }
}
  1. 第一處要注意的地方,是調(diào)用<font color="blue">http.ListenAndServe</font>監(jiān)聽(tīng)9090<font color="blue">端口</font>,這是對(duì)外提供RESTful服務(wù)的端口;
  2. 第二處要注意的地方,是<font color="blue">echoEndpoint</font>配置了將外部RESTful請(qǐng)求轉(zhuǎn)發(fā)到server.go提供gRPC服務(wù)的入口處;
  3. 第三處要注意的地方,是調(diào)用了自動(dòng)生成代碼中的RegisterGreeterHandlerFromEndpoint方法完成上下游調(diào)用的綁定;
  • 在<font color="blue">hellowworld.gw.go</font>所在目錄執(zhí)行<font color="blue">go run hellowworld.gw.go</font>,開(kāi)始監(jiān)聽(tīng)9090端口的web請(qǐng)求;

驗(yàn)證

  1. 在本機(jī)上驗(yàn)證,用curl發(fā)送請(qǐng)求:
curl \
-X POST \
-d '{"name": "will"}' \
192.168.133.203:9090/helloworld
  1. 收到響應(yīng)如下,這是來(lái)自server.go的內(nèi)容,可見(jiàn)http請(qǐng)求通過(guò)Reserve Proxy到達(dá)了真實(shí)的gRPC服務(wù)提供者,并順利返回給調(diào)用方:
{"message":"Hello will"}
  1. 去看server.go的日志如下:
[golang@centos7 server]$ go run server.go 
2020/12/19 14:16:47 開(kāi)始監(jiān)聽(tīng),等待遠(yuǎn)程調(diào)用...
2020/12/19 14:24:35 Received: will
  1. 還可以在其他機(jī)器上通過(guò)<font color="blue">postman</font>驗(yàn)證,記得關(guān)閉服務(wù)所在機(jī)器的防火墻,請(qǐng)求和響應(yīng)如下,注意按數(shù)字順序設(shè)置和觀察:
在這里插入圖片描述
  • 至此,將gRPC服務(wù)快速暴露為RESTful服務(wù)的實(shí)戰(zhàn)就完成了,如果您正在做這方面的嘗試,希望本文能給您一些參考,接下來(lái)的文章咱們一起把swagger補(bǔ)全,讓開(kāi)發(fā)和聯(lián)調(diào)更加高效;

你不孤單,欣宸原創(chuàng)一路相伴

  1. Java系列
  2. Spring系列
  3. Docker系列
  4. kubernetes系列
  5. 數(shù)據(jù)庫(kù)+中間件系列
  6. DevOps系列

歡迎關(guān)注公眾號(hào):程序員欣宸

微信搜索「程序員欣宸」,我是欣宸,期待與您一同暢游Java世界...
https://github.com/zq2599/blog_demos

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容