歡迎訪問(wèn)我的GitHub
https://github.com/zq2599/blog_demos
內(nèi)容:所有原創(chuàng)文章分類(lèi)匯總及配套源碼,涉及Java、Docker、Kubernetes、DevOPS等;
gRPC學(xué)習(xí)系列文章鏈接
- 在CentOS7部署和設(shè)置GO
- GO的gRPC開(kāi)發(fā)環(huán)境準(zhǔn)備
- 初試GO版gRPC開(kāi)發(fā)
- 實(shí)戰(zhàn)四類(lèi)服務(wù)方法
- gRPC-Gateway實(shí)戰(zhàn)
- 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ī)流程:
- 提前預(yù)覽關(guān)鍵知識(shí)點(diǎn);
- 新建工程文件夾;
- 安裝必要的go包;
- 編寫(xiě)proto文件,使swagger支持http(默認(rèn)是https);
- 生成gRPC、gRPC-Gateway所需的go源碼;
- 生成swagger所需的json文件;
- 下載swagger-ui的源碼,以此生成go源碼;
- 編寫(xiě)gRPC的服務(wù)端代碼;
- 編寫(xiě)gRPC-Gateway服務(wù)端的代碼;
- 驗(yàn)證;
- 注意,本文的所有操作都沒(méi)有用到<font color="blue">root</font>賬號(hào),而是前文創(chuàng)建的<font color="red">golang</font>賬號(hào);
源碼下載
- 本篇實(shí)戰(zhàn)中的源碼可在GitHub下載到,地址和鏈接信息如下表所示(https://github.com/zq2599/blog_demos):
| 名稱(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ù):
- 為了簡(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)的包,放在合適的位置;
- 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)》中安裝好的;
- 在gRPC-Gateway的代碼中集成swagger-ui的代碼:swagger-ui的代碼由多個(gè)png、html、js文件組成,需要用工具<font color="blue">go-bindata</font>轉(zhuǎn)換成go源碼并放入合適的位置,流程如下圖:

在這里插入圖片描述
- 要將swaggerdemo.swagger.json文件通過(guò)web暴露出來(lái),需要工具go-bindata-assetfs;
- 使用swagger的方式:打開(kāi)swagger-ui頁(yè)面后,將swaggerdemo.swagger.json輸入給swagger-ui頁(yè)面,令其解析后,生成對(duì)應(yīng)的在線接口服務(wù);
前提條件
- 本文是 《gRPC-Gateway實(shí)戰(zhàn)》的續(xù)篇,請(qǐng)您參考前文將gRPC-Gateway環(huán)境準(zhǔn)備好;
提前展示文件結(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包
- 安裝git,執(zhí)行命令<font color="blue">sudo yum install -y git unzip</font>
- 工程中會(huì)用到幾個(gè)包,接下來(lái)逐個(gè)安裝;
- <font color="blue">go-bindata</font>用來(lái)將swagger-ui的源碼轉(zhuǎn)為GO代碼:
go get -u github.com/jteeuwen/go-bindata/...
- <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/...
- <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)容中,具體的配置有以下兩處:
- 用<font color="blue">import</font>關(guān)鍵詞導(dǎo)入<font color="red">protoc-gen-swagger/options/annotations.proto</font>
- 下面這段就是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è)文件:
- swaggerdemo.pb.go:gRPC所需的go文件
- swaggerdemo.pb.gw.go:gRPC-Gateway所需的go文件
- 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文件,步驟如下:
- 接下來(lái)的命令會(huì)從Github下載swagger-ui的源碼,這個(gè)文件本該從swagger官方下載,但是我這里嘗試多次后發(fā)現(xiàn),下載得到的zip包很容器出現(xiàn)文件損壞而無(wú)法解壓縮的情況,于是我將此文件放在了自己的Github上,下面的操作也是從我自己的Github下載的,但實(shí)際上此文件和swagger官方的并無(wú)區(qū)別;
- 進(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
- 執(zhí)行以下命令新建文件夾,該文件夾用來(lái)存放稍后生成的swagger-ui的go源碼:
mkdir -p $GOPATH/src/swaggerdemo/pkg/ui/data/swagger
- 執(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/...
- 這時(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ù),步驟如下:
- 新建文件夾<font color="blue">$GOPATH/src/swaggerdemo/server</font>;
- 在新建的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)注意:
- 外部的RESTful請(qǐng)求轉(zhuǎn)發(fā)到server.go的功能,被封裝到<font color="blue">newGateway</font>方法中;
- 請(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)求方;
- 重點(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)證
- 進(jìn)入目錄<font color="blue">$GOPATH/src/swaggerdemo/server</font>,執(zhí)行<font color="red">go run server.go</font>啟動(dòng)gRPC服務(wù);
- 進(jìn)入目錄<font color="blue">$GOPATH/src/swaggerdemo/gateway</font>,執(zhí)行<font color="red">go run gateway.go</font>啟動(dòng)gRPC-Gateway服務(wù);
- 確保服務(wù)所在機(jī)器的防火墻已經(jīng)關(guān)閉;
- 我這邊服務(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)容,如下圖:

在這里插入圖片描述
- 訪問(wèn)swagger-ui頁(yè)面,地址是:http://192.168.133.204:9090/swagger-ui/ ,如下圖,可見(jiàn)swagger-ui功能正常:

在這里插入圖片描述
- 此時(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ù):

在這里插入圖片描述
- 點(diǎn)擊下圖紅框中的<font color="blue">Try it out</font>按鈕,即可在頁(yè)面上向后臺(tái)發(fā)起請(qǐng)求:

在這里插入圖片描述
- 如下圖,修改紅框1中的請(qǐng)求參數(shù),再點(diǎn)擊紅框2中的按鈕,即可發(fā)起請(qǐng)求:

在這里插入圖片描述
- 如下圖,紅框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)一路相伴
歡迎關(guān)注公眾號(hào):程序員欣宸
微信搜索「程序員欣宸」,我是欣宸,期待與您一同暢游Java世界...
https://github.com/zq2599/blog_demos