gRPC學(xué)習(xí)之六:gRPC-Gateway集成swagger

歡迎訪問(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í)》系列的第六篇,前文咱們實(shí)戰(zhàn)了gRPC-Gateway,將gRPC服務(wù)以RESTful形式對(duì)外暴露,當(dāng)時(shí)由于篇幅所限沒(méi)有完成swagger集成,本篇來(lái)完成這個(gè)工作:<font color="blue">開(kāi)發(fā)gRPC服務(wù),為其提供gRPC-Gateway,并提供在線swagger服務(wù)</font>;
  • 本文由以下章節(jié)構(gòu)成,這也是gRPC-Gateway集成swagger的常規(guī)流程:
  1. 提前預(yù)覽關(guān)鍵知識(shí)點(diǎn);
  2. 新建工程文件夾;
  3. 安裝必要的go包;
  4. 編寫(xiě)proto文件,使swagger支持http(默認(rèn)是https);
  5. 生成gRPC、gRPC-Gateway所需的go源碼;
  6. 生成swagger所需的json文件;
  7. 下載swagger-ui的源碼,以此生成go源碼;
  8. 編寫(xiě)gRPC的服務(wù)端代碼;
  9. 編寫(xiě)gRPC-Gateway服務(wù)端的代碼;
  10. 驗(yàn)證;
  • 注意,本文的所有操作都沒(méi)有用到<font color="blue">root</font>賬號(hào),而是前文創(chuàng)建的<font color="red">golang</font>賬號(hào);

源碼下載

名稱(chēng) 鏈接 備注
項(xiàng)目主頁(yè) https://github.com/zq2599/blog_demos 該項(xiàng)目在GitHub上的主頁(yè)
git倉(cāng)庫(kù)地址(https) https://github.com/zq2599/blog_demos.git 該項(xiàng)目源碼的倉(cāng)庫(kù)地址,https協(xié)議
git倉(cāng)庫(kù)地址(ssh) git@github.com:zq2599/blog_demos.git 該項(xiàng)目源碼的倉(cāng)庫(kù)地址,ssh協(xié)議
  • 這個(gè)git項(xiàng)目中有多個(gè)文件夾,本章的應(yīng)用在<font color="blue">go-source</font>文件夾下,如下圖紅框所示:
在這里插入圖片描述
  • <font color="blue">go-source</font>里面有多個(gè)子文件夾,本篇的源碼在<font color="red">swaggerdemo</font>中,如下圖紅框:
在這里插入圖片描述

提前預(yù)覽關(guān)鍵知識(shí)點(diǎn)

在gRPC-Gateway集成swagger服務(wù)的過(guò)程并不簡(jiǎn)單,咱們將其中的重點(diǎn)提前看一下,做到心里有數(shù):

  1. 為了簡(jiǎn)化實(shí)戰(zhàn)過(guò)程,gRPC-Gateway暴露的服務(wù)并未使用<font color="blue">https</font>,而是<font color="red">http</font>,但是swagger-ui提供的調(diào)用服務(wù)卻是https的,因此要在proto文件中指定swagger以http調(diào)用服務(wù),指定的時(shí)候會(huì)用到文件<font color="red">protoc-gen-swagger/options/annotations.proto</font>,因此需要找到這個(gè)文件對(duì)應(yīng)的包,放在合適的位置;
  2. swaggerdemo.swagger.json:這是swagger-ui要用的json文件,依據(jù)此文件,swagger才能正確的展現(xiàn)出gRPC-Gateway暴露的服務(wù)和參數(shù)定義,可以在頁(yè)面上發(fā)起請(qǐng)求,此文件由插件<font color="blue">protoc-gen-swagger</font>生成,該插件是上一篇《gRPC-Gateway實(shí)戰(zhàn)》中安裝好的;
  3. 在gRPC-Gateway的代碼中集成swagger-ui的代碼:swagger-ui的代碼由多個(gè)png、html、js文件組成,需要用工具<font color="blue">go-bindata</font>轉(zhuǎn)換成go源碼并放入合適的位置,流程如下圖:
在這里插入圖片描述
  1. 要將swaggerdemo.swagger.json文件通過(guò)web暴露出來(lái),需要工具go-bindata-assetfs;
  2. 使用swagger的方式:打開(kāi)swagger-ui頁(yè)面后,將swaggerdemo.swagger.json輸入給swagger-ui頁(yè)面,令其解析后,生成對(duì)應(yīng)的在線接口服務(wù);

前提條件

提前展示文件結(jié)構(gòu)

  • 本次實(shí)戰(zhàn)涉及到多個(gè)文件,在此先將最終的文件內(nèi)容全部展示出來(lái),以便您在開(kāi)發(fā)過(guò)程中作為參考,所有內(nèi)容都在<font color="blue">$GOPATH/src/swaggerdemo</font>目錄下:
[golang@centos7 src]$ tree swaggerdemo/
swaggerdemo/
├── gateway
│   └── gateway.go
├── pkg
│   └── ui
│       └── data
│           └── swagger
│               └── datafile.go
├── server
│   └── server.go
├── swaggerdemo.pb.go
├── swaggerdemo.pb.gw.go
├── swaggerdemo.proto
├── swaggerdemo.swagger.json
└── third_party
    └── swagger-ui
        ├── favicon-16x16.png
        ├── favicon-32x32.png
        ├── index.html
        ├── oauth2-redirect.html
        ├── swagger-ui-bundle.js
        ├── swagger-ui-bundle.js.map
        ├── swagger-ui.css
        ├── swagger-ui.css.map
        ├── swagger-ui-es-bundle-core.js
        ├── swagger-ui-es-bundle-core.js.map
        ├── swagger-ui-es-bundle.js
        ├── swagger-ui-es-bundle.js.map
        ├── swagger-ui.js
        ├── swagger-ui.js.map
        ├── swagger-ui-standalone-preset.js
        └── swagger-ui-standalone-preset.js.map

8 directories, 23 files

新建工程文件夾

  • 本次實(shí)戰(zhàn)與前面幾篇文章的代碼沒(méi)有關(guān)系,而是一個(gè)全新的工程,請(qǐng)?jiān)?lt;font color="blue">$GOPATH/src</font>下面新建名為<font color="red">swaggerdemo</font>的文件夾;

安裝必要的go包

  1. 安裝git,執(zhí)行命令<font color="blue">sudo yum install -y git unzip</font>
  2. 工程中會(huì)用到幾個(gè)包,接下來(lái)逐個(gè)安裝;
  3. <font color="blue">go-bindata</font>用來(lái)將swagger-ui的源碼轉(zhuǎn)為GO代碼:
go get -u github.com/jteeuwen/go-bindata/...
  1. <font color="blue">go-bindata-assetfs</font>在應(yīng)用啟動(dòng)后,對(duì)外提供文件服務(wù),這樣可以通過(guò)web訪問(wèn)swagger的json文件:
go get -u github.com/elazarl/go-bindata-assetfs/...
  1. <font color="blue">glog</font>是常用的日志工具:
go get -u github.com/golang/glog

編寫(xiě)proto文件

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

// 包名
package swaggerdemo;

import "google/api/annotations.proto";
import "protoc-gen-swagger/options/annotations.proto";

// 定義swagger內(nèi)容
option (grpc.gateway.protoc_gen_swagger.options.openapiv2_swagger) = {
  info: {
        title: "grpc gateway helloworld sample";
        version: "1.0"; 
  };
  schemes: HTTP;
};

// 定義的服務(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;
}
  • 文件swaggerdemo.proto和 《gRPC-Gateway實(shí)戰(zhàn)》一文中的proto文件大部分是一致的,不同之處在于增加了swagger的配置,這個(gè)配置的作用是讓swagger把遠(yuǎn)程調(diào)用配置成http,如果沒(méi)有這些配置,swagger默認(rèn)的遠(yuǎn)程調(diào)用就是https的,本文的gRPC-Gateway提供的是http服務(wù),所以要加上這些配置,在上述<font color="blue">swaggerdemo.proto</font>的內(nèi)容中,具體的配置有以下兩處:
  1. 用<font color="blue">import</font>關(guān)鍵詞導(dǎo)入<font color="red">protoc-gen-swagger/options/annotations.proto</font>
  2. 下面這段就是swagger的配置了,重點(diǎn)是<font color="blue">schemes</font>,里面只有<font color="red">HTTP</font>:
option (grpc.gateway.protoc_gen_swagger.options.openapiv2_swagger) = {
  info: {
        title: "grpc gateway helloworld sample";
        version: "1.0"; 
  };
  schemes: HTTP;
};
  • 還要把<font color="blue">swaggerdemo.proto</font>中提到的<font color="red">protoc-gen-swagger/options/annotations.proto</font>文件放在合適的地方,以便使用swaggerdemo.proto的時(shí)候能找到此annotations.proto文件,執(zhí)行以下命令:
cd $GOPATH/src
cp -r ./github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger ./
  • 上述命令中的<font color="blue">protoc-gen-swagger</font>文件夾,是在前文的操作中下載好的;

生成gRPC、gRPC-Gateway所需的go源碼

  • 生成gRPC、gRPC-Gateway所需的go源碼,這樣的操作在前面已經(jīng)做過(guò),這里用<font color="blue">swaggerdemo.proto</font>再做一次,先進(jìn)入目錄<font color="blue">$GOPATH/src/swaggerdemo</font>
  • 執(zhí)行以下命令,生成gRPC所需源碼:
protoc -I. \
-I$GOPATH/src \
-I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
--go_out=plugins=grpc:. \
swaggerdemo.proto
  • 執(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:. \
swaggerdemo.proto

生成swagger所需的json文件

  • 還是在目錄<font color="blue">$GOPATH/src/swaggerdemo</font>,執(zhí)行以下命令,生成swagger所需json:
protoc -I. \
-I$GOPATH/src \
-I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
--swagger_out=logtostderr=true:. \
swaggerdemo.proto
  • 此時(shí)的<font color="blue">$GOPATH/src/swaggerdemo</font>目錄下新增以下三個(gè)文件:
  1. swaggerdemo.pb.go:gRPC所需的go文件
  2. swaggerdemo.pb.gw.go:gRPC-Gateway所需的go文件
  3. swaggerdemo.swagger.json:swagger-ui要用的json文件,依據(jù)此文件,swagger展現(xiàn)的頁(yè)面中會(huì)有g(shù)RPC-Gateway暴露的服務(wù)和參數(shù)定義,可以在頁(yè)面上發(fā)起請(qǐng)求

生成swagger-ui的go文件

  • 要想在服務(wù)中提供swagger的web頁(yè)面,需要將swagger-ui的源碼轉(zhuǎn)為go文件,步驟如下:
  1. 接下來(lái)的命令會(huì)從Github下載swagger-ui的源碼,這個(gè)文件本該從swagger官方下載,但是我這里嘗試多次后發(fā)現(xiàn),下載得到的zip包很容器出現(xiàn)文件損壞而無(wú)法解壓縮的情況,于是我將此文件放在了自己的Github上,下面的操作也是從我自己的Github下載的,但實(shí)際上此文件和swagger官方的并無(wú)區(qū)別;
  2. 進(jìn)入目錄<font color="blue">$GOPATH/src/swaggerdemo</font>,執(zhí)行以下命令下載swagger-ui源碼,并放入指定位置:
wget https://raw.githubusercontent.com/zq2599/blog_download_files/master/files/swagger-ui.zip -O swagger-ui.zip \
&& unzip swagger-ui.zip \
&& mkdir -p $GOPATH/src/swaggerdemo/third_party/ \
&& mv ./swagger-ui-3.38.0/dist $GOPATH/src/swaggerdemo/third_party/ \
&& mv $GOPATH/src/swaggerdemo/third_party/dist $GOPATH/src/swaggerdemo/third_party/swagger-ui \
&& rm -f ./swagger-ui.zip \
&& rm -rf ./swagger-ui-3.38.0
  1. 執(zhí)行以下命令新建文件夾,該文件夾用來(lái)存放稍后生成的swagger-ui的go源碼:
mkdir -p $GOPATH/src/swaggerdemo/pkg/ui/data/swagger
  1. 執(zhí)行以下命令,將swagger-ui源碼轉(zhuǎn)為datafile.go文件:
cd $GOPATH/src/swaggerdemo/
go-bindata --nocompress -pkg swagger -o pkg/ui/data/swagger/datafile.go third_party/swagger-ui/...
  1. 這時(shí)候在<font color="blue">$GOPATH/src/swaggerdemo/pkg/ui/data/swagger</font>目錄下生成了文件<font color="red">datafile.go</font>
  • 所有文件和材料已經(jīng)準(zhǔn)備完成,開(kāi)始編碼;

編寫(xiě)gRPC的服務(wù)端代碼

  • 按照<font color="blue">swaggerdemo.proto</font>的配置新建一個(gè)gRPC服務(wù),步驟如下:
  1. 新建文件夾<font color="blue">$GOPATH/src/swaggerdemo/server</font>;
  2. 在新建的server文件夾下新增文件server.go,內(nèi)容如下,只是個(gè)普通的gRPC服務(wù)而已:
package main

import (
    "context"
    "log"
    "net"

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

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)
    }
}
  • 以上就是gRPC服務(wù)的代碼,與前幾篇文章中的差不多,就不贅述了;

編寫(xiě)gRPC-Gateway服務(wù)端的代碼

  • 開(kāi)始編寫(xiě)gRPC-Gateway服務(wù)端代碼,這是本文的重點(diǎn)所在,除了提供與前文一樣的gRPC-Gateway服務(wù),還提供了swagger的json文件服務(wù),以及swagger的ui服務(wù);
  • 新建文件夾<font color="blue">$GOPATH/src/swaggerdemo/gateway</font>;
  • 在新建的gateway文件夾下新增文件<font color="blue">gateway.go</font>,內(nèi)容如下,有幾處要注意的地方稍后會(huì)說(shuō)明:
package main

import (
    "github.com/elazarl/go-bindata-assetfs"
    "log"
    "net/http"
    "path"
    "strings"

    "github.com/golang/glog"
    "github.com/grpc-ecosystem/grpc-gateway/runtime"
    "golang.org/x/net/context"
    "google.golang.org/grpc"
    swagger "swaggerdemo/pkg/ui/data/swagger"
    gw "swaggerdemo"
)

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

    gwmux, err := newGateway(ctx)
    if err != nil {
        panic(err)
    }

    mux := http.NewServeMux()
    mux.Handle("/", gwmux)
    mux.HandleFunc("/swagger/", serveSwaggerFile)
    serveSwaggerUI(mux)

    log.Println("grpc-gateway listen on localhost:9090")
    return http.ListenAndServe(":9090", mux)
}

func newGateway(ctx context.Context) (http.Handler, error) {
    opts := []grpc.DialOption{grpc.WithInsecure()}

    gwmux := runtime.NewServeMux()
    if err := gw.RegisterGreeterHandlerFromEndpoint(ctx, gwmux, ":50051", opts); err != nil {
        return nil, err
    }

    return gwmux, nil
}

func serveSwaggerFile(w http.ResponseWriter, r *http.Request) {
    log.Println("start serveSwaggerFile")       


    if !strings.HasSuffix(r.URL.Path, "swagger.json") {
        log.Printf("Not Found: %s", r.URL.Path)
        http.NotFound(w, r)
        return
    }

    p := strings.TrimPrefix(r.URL.Path, "/swagger/")
    p = path.Join("../", p)

    log.Printf("Serving swagger-file: %s", p)

    http.ServeFile(w, r, p)
}

func serveSwaggerUI(mux *http.ServeMux) {
    fileServer := http.FileServer(&assetfs.AssetFS{
        Asset:    swagger.Asset,
        AssetDir: swagger.AssetDir,
        Prefix:   "third_party/swagger-ui",
    })
    prefix := "/swagger-ui/"
    mux.Handle(prefix, http.StripPrefix(prefix, fileServer))
}

func main() {
    defer glog.Flush()

    if err := run(); err != nil {
        glog.Fatal(err)
    }
}
  • 對(duì)于這個(gè)<font color="blue">gateway.go</font>文件,有以下幾處需要重點(diǎn)注意:
  1. 外部的RESTful請(qǐng)求轉(zhuǎn)發(fā)到server.go的功能,被封裝到<font color="blue">newGateway</font>方法中;
  2. 請(qǐng)求URL中如果含有<font color="blue">/swagger</font>,就交給<font color="blue">serveSwaggerFile</font>方法處理,這里面的邏輯是將文件<font color="blue">$GOPATH/src/swaggerdemo/swaggerdemo.swagger.json</font>返回給請(qǐng)求方;
  3. 重點(diǎn)關(guān)注<font color="blue">serveSwaggerUI</font>方法,經(jīng)過(guò)該方法的處理后,如果請(qǐng)求URL中含有<font color="blue">/swagger-ui</font>,就會(huì)交給前面生成的<font color="red">datafile.go</font>處理,也就是打開(kāi)了swagger-ui的頁(yè)面;
  • 至此,開(kāi)發(fā)工作已經(jīng)完成,可以開(kāi)始驗(yàn)證了;

驗(yàn)證

  1. 進(jìn)入目錄<font color="blue">$GOPATH/src/swaggerdemo/server</font>,執(zhí)行<font color="red">go run server.go</font>啟動(dòng)gRPC服務(wù);
  2. 進(jìn)入目錄<font color="blue">$GOPATH/src/swaggerdemo/gateway</font>,執(zhí)行<font color="red">go run gateway.go</font>啟動(dòng)gRPC-Gateway服務(wù);
  3. 確保服務(wù)所在機(jī)器的防火墻已經(jīng)關(guān)閉;
  4. 我這邊服務(wù)器IP地址是<font color="blue">http://192.168.133.204/</font>,因此瀏覽器訪問(wèn):http://192.168.133.204:9090/swagger/swaggerdemo.swagger.json ,即可看到swagger.json的內(nèi)容,如下圖:
在這里插入圖片描述
  1. 訪問(wèn)swagger-ui頁(yè)面,地址是:http://192.168.133.204:9090/swagger-ui/ ,如下圖,可見(jiàn)swagger-ui功能正常:
在這里插入圖片描述
  1. 此時(shí)的swagger-ui頁(yè)面并未展示gRPC-Gateway的接口內(nèi)容,需要將<font color="blue">http://192.168.133.204:9090/swagger/swaggerdemo.swagger.json</font>填入下圖紅框1中,再點(diǎn)擊紅框2的按鈕,即可正常展示,紅框3就是gRPC-Gateway對(duì)外暴露的服務(wù):
在這里插入圖片描述
  1. 點(diǎn)擊下圖紅框中的<font color="blue">Try it out</font>按鈕,即可在頁(yè)面上向后臺(tái)發(fā)起請(qǐng)求:
在這里插入圖片描述
  1. 如下圖,修改紅框1中的請(qǐng)求參數(shù),再點(diǎn)擊紅框2中的按鈕,即可發(fā)起請(qǐng)求:
在這里插入圖片描述
  1. 如下圖,紅框1中是請(qǐng)求地址,可見(jiàn)是<font color="blue">http</font>請(qǐng)求,證明咱們之前在proto文件中的設(shè)置已經(jīng)生效,紅框2中是收到的返回內(nèi)容,很明顯這個(gè)內(nèi)容來(lái)自server.go:
在這里插入圖片描述
  • 至此,gRPC-Gateway集成swagger的操作就完成了,可見(jiàn)這是一系列繁瑣的操作,希望本文能給您提供一些參考,助您順利集成swagger;

你不孤單,欣宸原創(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)容