gRPC学习之六:gRPC

Wesley13
• 阅读 1258

欢迎访问我的GitHub

这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos

gRPC学习系列文章链接

  1. 在CentOS7部署和设置GO
  2. GO的gRPC开发环境准备
  3. 初试GO版gRPC开发
  4. 实战四类服务方法
  5. gRPC-Gateway实战
  6. gRPC-Gateway集成swagger

本篇概览

  • 本文《gRPC学习》系列的第六篇,前文咱们实战了gRPC-Gateway,将gRPC服务以RESTful形式对外暴露,当时由于篇幅所限没有完成swagger集成,本篇来完成这个工作:开发gRPC服务,为其提供gRPC-Gateway,并提供在线swagger服务;
  • 本文由以下章节构成,这也是gRPC-Gateway集成swagger的常规流程:
  1. 提前预览关键知识点;
  2. 新建工程文件夹;
  3. 安装必要的go包;
  4. 编写proto文件,使swagger支持http(默认是https);
  5. 生成gRPC、gRPC-Gateway所需的go源码;
  6. 生成swagger所需的json文件;
  7. 下载swagger-ui的源码,以此生成go源码;
  8. 编写gRPC的服务端代码;
  9. 编写gRPC-Gateway服务端的代码;
  10. 验证;
  • 注意,本文的所有操作都没有用到root账号,而是前文创建的golang账号;

源码下载

名称

链接

备注

项目主页

https://github.com/zq2599/blog\_demos

该项目在GitHub上的主页

git仓库地址(https)

https://github.com/zq2599/blog\_demos.git

该项目源码的仓库地址,https协议

git仓库地址(ssh)

git@github.com:zq2599/blog_demos.git

该项目源码的仓库地址,ssh协议

  • 这个git项目中有多个文件夹,本章的应用在go-source文件夹下,如下图红框所示:
    gRPC学习之六:gRPC

  • go-source里面有多个子文件夹,本篇的源码在swaggerdemo中,如下图红框:
    gRPC学习之六:gRPC

提前预览关键知识点

在gRPC-Gateway集成swagger服务的过程并不简单,咱们将其中的重点提前看一下,做到心里有数:

  1. 为了简化实战过程,gRPC-Gateway暴露的服务并未使用https,而是http,但是swagger-ui提供的调用服务却是https的,因此要在proto文件中指定swagger以http调用服务,指定的时候会用到文件protoc-gen-swagger/options/annotations.proto,因此需要找到这个文件对应的包,放在合适的位置;
  2. swaggerdemo.swagger.json:这是swagger-ui要用的json文件,依据此文件,swagger才能正确的展现出gRPC-Gateway暴露的服务和参数定义,可以在页面上发起请求,此文件由插件protoc-gen-swagger生成,该插件是上一篇《gRPC-Gateway实战》中安装好的;
  3. 在gRPC-Gateway的代码中集成swagger-ui的代码:swagger-ui的代码由多个png、html、js文件组成,需要用工具go-bindata转换成go源码并放入合适的位置,流程如下图:
    gRPC学习之六:gRPC
  4. 要将swaggerdemo.swagger.json文件通过web暴露出来,需要工具go-bindata-assetfs;
  5. 使用swagger的方式:打开swagger-ui页面后,将swaggerdemo.swagger.json输入给swagger-ui页面,令其解析后,生成对应的在线接口服务;

前提条件

提前展示文件结构

  • 本次实战涉及到多个文件,在此先将最终的文件内容全部展示出来,以便您在开发过程中作为参考,所有内容都在$GOPATH/src/swaggerdemo目录下:

    [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

新建工程文件夹

  • 本次实战与前面几篇文章的代码没有关系,而是一个全新的工程,请在$GOPATH/src下面新建名为swaggerdemo的文件夹;

安装必要的go包

  1. 安装git,执行命令sudo yum install -y git unzip

  2. 工程中会用到几个包,接下来逐个安装;

  3. go-bindata用来将swagger-ui的源码转为GO代码:

    go get -u github.com/jteeuwen/go-bindata/...

  4. go-bindata-assetfs在应用启动后,对外提供文件服务,这样可以通过web访问swagger的json文件:

    go get -u github.com/elazarl/go-bindata-assetfs/...

  5. glog是常用的日志工具:

    go get -u github.com/golang/glog

编写proto文件

  • 进入目录$GOPATH/src/swaggerdemo,新建swaggerdemo.proto,内容如下,有几处要注意的地方稍后会说明:

    // 协议类型 syntax = "proto3";

    // 包名 package swaggerdemo;

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

    // 定义swagger内容 option (grpc.gateway.protoc_gen_swagger.options.openapiv2_swagger) = {

    info: {

        title: "grpc gateway helloworld sample";
        version: "1.0";    
    

    }; schemes: HTTP; };

    // 定义的服务名 service Greeter {

    // 具体的远程服务方法 rpc SayHello (HelloRequest) returns (HelloReply) {

    option (google.api.http) = {
    
    
    
      post: "/helloworld"
      body: "*"
    };
    

    } }

    // SayHello方法的入参,只有一个字符串字段 message HelloRequest {

    string name = 1; }

    // SayHello方法的返回值,只有一个字符串字段 message HelloReply {

    string message = 1; }

  • 文件swaggerdemo.proto和 《gRPC-Gateway实战》一文中的proto文件大部分是一致的,不同之处在于增加了swagger的配置,这个配置的作用是让swagger把远程调用配置成http,如果没有这些配置,swagger默认的远程调用就是https的,本文的gRPC-Gateway提供的是http服务,所以要加上这些配置,在上述swaggerdemo.proto的内容中,具体的配置有以下两处:

  1. 用import关键词导入protoc-gen-swagger/options/annotations.proto

  2. 下面这段就是swagger的配置了,重点是schemes,里面只有HTTP:

    option (grpc.gateway.protoc_gen_swagger.options.openapiv2_swagger) = {

    info: {

        title: "grpc gateway helloworld sample";
        version: "1.0";    
    

    }; schemes: HTTP; };

  • 还要把swaggerdemo.proto中提到的protoc-gen-swagger/options/annotations.proto文件放在合适的地方,以便使用swaggerdemo.proto的时候能找到此annotations.proto文件,执行以下命令:

    cd $GOPATH/src cp -r ./github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger ./

  • 上述命令中的protoc-gen-swagger文件夹,是在前文的操作中下载好的;

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

  • 生成gRPC、gRPC-Gateway所需的go源码,这样的操作在前面已经做过,这里用swaggerdemo.proto再做一次,先进入目录$GOPATH/src/swaggerdemo

  • 执行以下命令,生成gRPC所需源码:

    protoc -I.
    -I$GOPATH/src
    -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis
    --go_out=plugins=grpc:.
    swaggerdemo.proto

  • 执行以下命令,生成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文件

  • 还是在目录$GOPATH/src/swaggerdemo,执行以下命令,生成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

  • 此时的$GOPATH/src/swaggerdemo目录下新增以下三个文件:

  1. swaggerdemo.pb.go:gRPC所需的go文件
  2. swaggerdemo.pb.gw.go:gRPC-Gateway所需的go文件
  3. swaggerdemo.swagger.json:swagger-ui要用的json文件,依据此文件,swagger展现的页面中会有gRPC-Gateway暴露的服务和参数定义,可以在页面上发起请求

生成swagger-ui的go文件

  • 要想在服务中提供swagger的web页面,需要将swagger-ui的源码转为go文件,步骤如下:
  1. 接下来的命令会从Github下载swagger-ui的源码,这个文件本该从swagger官方下载,但是我这里尝试多次后发现,下载得到的zip包很容器出现文件损坏而无法解压缩的情况,于是我将此文件放在了自己的Github上,下面的操作也是从我自己的Github下载的,但实际上此文件和swagger官方的并无区别;

  2. 进入目录$GOPATH/src/swaggerdemo,执行以下命令下载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

  3. 执行以下命令新建文件夹,该文件夹用来存放稍后生成的swagger-ui的go源码:

    mkdir -p $GOPATH/src/swaggerdemo/pkg/ui/data/swagger

  4. 执行以下命令,将swagger-ui源码转为datafile.go文件:

    cd $GOPATH/src/swaggerdemo/ go-bindata --nocompress -pkg swagger -o pkg/ui/data/swagger/datafile.go third_party/swagger-ui/...

  5. 这时候在$GOPATH/src/swaggerdemo/pkg/ui/data/swagger目录下生成了文件datafile.go

  • 所有文件和材料已经准备完成,开始编码;

编写gRPC的服务端代码

  • 按照swaggerdemo.proto的配置新建一个gRPC服务,步骤如下:
  1. 新建文件夹$GOPATH/src/swaggerdemo/server;

  2. 在新建的server文件夹下新增文件server.go,内容如下,只是个普通的gRPC服务而已:

    package main

    import ( "context" "log" "net" "google.golang.org/grpc" pb "swaggerdemo" )

    const ( port = ":50051" )

    // 定义结构体,在调用注册api的时候作为入参, // 该结构体会带上SayHello方法,里面是业务代码 // 这样远程调用时就执行了业务代码了 type server struct {

    // pb.go中自动生成的,是个空结构体
    pb.UnimplementedGreeterServer
    

    }

    // 业务代码在此写,客户端远程调用SayHello时, // 会执行这里的代码 func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {

    // 打印请求参数
    log.Printf("Received: %v", in.GetName())
    // 实例化结构体HelloReply,作为返回值
    return &pb.HelloReply{
    

    Message: "Hello " + in.GetName()}, nil }

    func main() {

    // 要监听的协议和端口
    lis, err := net.Listen("tcp", port)
    if err != nil {
    
    
    
        log.Fatalf("failed to listen: %v", err)
    }
    
    // 实例化gRPC server结构体
    s := grpc.NewServer()
    
    // 服务注册
    pb.RegisterGreeterServer(s, &server{
    

    })

    log.Println("开始监听,等待远程调用...")
    
    if err := s.Serve(lis); err != nil {
    
    
    
        log.Fatalf("failed to serve: %v", err)
    }
    

    }

  • 以上就是gRPC服务的代码,与前几篇文章中的差不多,就不赘述了;

编写gRPC-Gateway服务端的代码

  • 开始编写gRPC-Gateway服务端代码,这是本文的重点所在,除了提供与前文一样的gRPC-Gateway服务,还提供了swagger的json文件服务,以及swagger的ui服务;

  • 新建文件夹$GOPATH/src/swaggerdemo/gateway;

  • 在新建的gateway文件夹下新增文件gateway.go,内容如下,有几处要注意的地方稍后会说明:

    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)
    }
    

    }

  • 对于这个gateway.go文件,有以下几处需要重点注意:

  1. 外部的RESTful请求转发到server.go的功能,被封装到newGateway方法中;
  2. 请求URL中如果含有/swagger,就交给serveSwaggerFile方法处理,这里面的逻辑是将文件$GOPATH/src/swaggerdemo/swaggerdemo.swagger.json返回给请求方;
  3. 重点关注serveSwaggerUI方法,经过该方法的处理后,如果请求URL中含有/swagger-ui,就会交给前面生成的datafile.go处理,也就是打开了swagger-ui的页面;
  • 至此,开发工作已经完成,可以开始验证了;

验证

  1. 进入目录$GOPATH/src/swaggerdemo/server,执行go run server.go启动gRPC服务;

  2. 进入目录$GOPATH/src/swaggerdemo/gateway,执行go run gateway.go启动gRPC-Gateway服务;

  3. 确保服务所在机器的防火墙已经关闭;

  4. 我这边服务器IP地址是http://192.168.133.204/,因此浏览器访问:http://192.168.133.204:9090/swagger/swaggerdemo.swagger.json ,即可看到swagger.json的内容,如下图:
    gRPC学习之六:gRPC

  5. 访问swagger-ui页面,地址是:http://192.168.133.204:9090/swagger-ui/ ,如下图,可见swagger-ui功能正常:
    gRPC学习之六:gRPC

  6. 此时的swagger-ui页面并未展示gRPC-Gateway的接口内容,需要将http://192.168.133.204:9090/swagger/swaggerdemo.swagger.json填入下图红框1中,再点击红框2的按钮,即可正常展示,红框3就是gRPC-Gateway对外暴露的服务:
    gRPC学习之六:gRPC

  7. 点击下图红框中的Try it out按钮,即可在页面上向后台发起请求:
    gRPC学习之六:gRPC

  8. 如下图,修改红框1中的请求参数,再点击红框2中的按钮,即可发起请求:
    gRPC学习之六:gRPC

  9. 如下图,红框1中是请求地址,可见是http请求,证明咱们之前在proto文件中的设置已经生效,红框2中是收到的返回内容,很明显这个内容来自server.go:
    gRPC学习之六:gRPC

  • 至此,gRPC-Gateway集成swagger的操作就完成了,可见这是一系列繁琐的操作,希望本文能给您提供一些参考,助您顺利集成swagger;

你不孤单,欣宸原创一路相伴

  1. Java系列
  2. Spring系列
  3. Docker系列
  4. kubernetes系列
  5. 数据库+中间件系列
  6. DevOps系列

欢迎关注公众号:程序员欣宸

微信搜索「程序员欣宸」,我是欣宸,期待与您一同畅游Java世界…

本文同步分享在 博客“程序员欣宸”(CSDN)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Wesley13 Wesley13
3年前
jackson学习之九:springboot整合(配置文件)
欢迎访问我的GitHub这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog\_demos(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fgithub.com%2Fzq2599%2Fblog_demos)
Wesley13 Wesley13
3年前
gRPC学习之三:初试GO版gRPC开发
欢迎访问我的GitHub这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog\_demos(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fgithub.com%2Fzq2599%2Fblog_demos)
Stella981 Stella981
3年前
Kurento实战之一:KMS部署和体验
欢迎访问我的GitHub这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog\_demos(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fgithub.com%2Fzq2599%2Fblog_demos)
Stella981 Stella981
3年前
Kurento实战之二:快速部署和体验
欢迎访问我的GitHub这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog\_demos(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fgithub.com%2Fzq2599%2Fblog_demos)
Stella981 Stella981
3年前
OpenFaaS实战之四:模板操作(template)
欢迎访问我的GitHub这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog\_demos(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fgithub.com%2Fzq2599%2Fblog_demos)
Wesley13 Wesley13
3年前
gRPC学习之二:GO的gRPC开发环境准备
欢迎访问我的GitHub这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog\_demos(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fgithub.com%2Fzq2599%2Fblog_demos)
Stella981 Stella981
3年前
JUnit5学习之四:按条件执行
欢迎访问我的GitHub这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog\_demos(https://www.oschina.net/action/GoToLink?urlhttps%3A%2F%2Fgithub.com%2Fzq2599%2Fblog_demos)
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
10个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这