gRPC之gateway集成swagger

1、gateway集成swagger

1、为了简化实战过程,gRPC-Gateway暴露的服务并未使用https,而是http,但是swagger-ui提供的调用服

务却是https的,因此要在proto文件中指定swaggerhttp调用服务,指定的时候会用到文件

protoc-gen-swagger/options/annotations.proto,因此需要找到这个文件对应的包,放在合适的位置。

2、swaggerdemo.swagger.json:这是swagger-ui要用的json文件,依据此文件,swagger才能正确的展现

gRPC-Gateway暴露的服务和参数定义,可以在页面上发起请求,此文件由插件protoc-gen-swagger生成。

3、在gRPC-Gateway的代码中集成swagger-ui的代码:swagger-ui的代码由多个png、html、js文件组成,

需要用工具go-bindata转换成go源码并放入合适的位置,流程如下图:

4、要将swaggerdemo.swagger.json文件通过web暴露出来,需要工具go-bindata-assetfs

5、使用swagger的方式:打开swagger-ui页面后,将swaggerdemo.swagger.json输入给swagger-ui页面,

令其解析后,生成对应的在线接口服务。

1.1 安装必要的go包

1、安装protoc-gen-swagger

shell 复制代码
$ go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger
$ go install github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger

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

shell 复制代码
$ go install github.com/jteeuwen/go-bindata/...

3、go-bindata-assetfs在应用启动后,对外提供文件服务,这样可以通过web访问swaggerjson文件:

shell 复制代码
$ go install github.com/elazarl/go-bindata-assetfs/...

4、glog是常用的日志工具:

shell 复制代码
$ go get -u github.com/golang/glog

1.2 编写proto文件

新建swaggerdemo.proto

protobuf 复制代码
// 协议类型
syntax = "proto3";

// 包名
package swaggerdemo;

option go_package="./protoc;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;
}

protoc-gen-swagger/options/annotations.proto来自于:

github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger

生成swaggerdemo.pb.go,gRPC所需的go文件:

shell 复制代码
$ protoc --go_out=plugins=grpc:. protoc/swaggerdemo.proto

生成swaggerdemo.pb.gw.go,gRPC-Gateway所需的go文件:

shell 复制代码
$ protoc --grpc-gateway_out=logtostderr=true:. protoc/swaggerdemo.proto

生成swaggerdemo.swagger.jsonswagger-ui要用的json文件,依据此文件,swagger展现的页面中会有

gRPC-Gateway暴露的服务和参数定义,可以在页面上发起请求:

shell 复制代码
$ protoc --swagger_out=logtostderr=true:. protoc/swaggerdemo.proto

1.3 生成swagger-ui的go文件

https://github.com/swagger-api/swagger-ui下载包,解压把dist目录下的所有文件拷贝我们项目的

/swagger/swagger-ui/目录下。

运行指令把Swagger UI转成datafile.go代码:

shell 复制代码
$ go-bindata --nocompress -pkg swagger -o swagger/datafile.go swagger/swagger-ui/...

1.4 编写gRPC的服务端代码

新建文件server.go,内容如下:

go 复制代码
package main

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

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

1.5 编写gRPC-Gateway服务端的代码

新建文件gateway.go,内容如下:

go 复制代码
package main

import (
	"github.com/golang/glog"
	"github.com/grpc-ecosystem/grpc-gateway/runtime"
	"golang.org/x/net/context"
	"google.golang.org/grpc"
	"log"
	"net/http"
	"path"
	"strings"
	gw "swaggerproject/protoc"
	"swaggerproject/swagger"
	assetfs "github.com/elazarl/go-bindata-assetfs"
)

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("./protoc/", 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:   "swagger/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方法处理,这里面的逻辑是将文件

swaggerdemo.swagger.json返回给请求方;

3、重点关注serveSwaggerUI方法,经过该方法的处理后,如果请求URL中含有/swagger-ui,就会交给前面

生成的datafile.go处理,也就是打开了swagger-ui的页面;

至此,开发工作已经完成,可以开始验证了。

1.6 测试

elixir 复制代码
[root@zsx swagger_demo]# go run server.go
2023/02/12 19:00:16 开始监听,等待远程调用...

[root@zsx swagger_demo]# go run gateway.go
2023/02/12 19:00:28 grpc-gateway listen on localhost:9090

访问 http://127.0.0.1:9090/swagger-ui/

输入 http://127.0.0.1:9090/swagger/swaggerdemo.swagger.json

输入请求数据:

发送请求:

至此,gateway集成swagger完成。

shell 复制代码
# 项目结构
$ tree swagger_demo/
swagger_demo/
├── gateway.go
├── go.mod
├── google
│   └── api
│       ├── annotations.proto
│       └── http.proto
├── go.sum
├── protoc
│   ├── swaggerdemo.pb.go
│   ├── swaggerdemo.pb.gw.go
│   ├── swaggerdemo.proto
│   └── swaggerdemo.swagger.json
├── protoc-gen-swagger
│   └── options
│       ├── annotations.proto
│       └── openapiv2.proto
├── server.go
└── swagger
    ├── datafile.go
    └── swagger-ui
        ├── favicon-16x16.png
        ├── favicon-32x32.png
        ├── index.css
        ├── index.html
        ├── oauth2-redirect.html
        ├── swagger-initializer.js
        ├── 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

7 directories, 31 files
相关推荐
hlsd#1 小时前
go 集成go-redis 缓存操作
redis·缓存·golang
qq_1728055915 小时前
GIN 反向代理功能
后端·golang·go
__AtYou__15 小时前
Golang | Leetcode Golang题解之第535题TinyURL的加密与解密
leetcode·golang·题解
kevin_tech20 小时前
Go API 多种响应的规范化处理和简化策略
开发语言·后端·golang·状态模式
幺零九零零1 天前
【Golang】sql.Null* 类型使用(处理空值和零值)
数据库·sql·golang
cookies_s_s1 天前
Golang--DOS命令、变量、基本数据类型、标识符
golang
__AtYou__1 天前
Golang | Leetcode Golang题解之第541题反转字符串II
leetcode·golang·题解
flying robot1 天前
Go的JSON转化
golang
幺零九零零1 天前
【Golang】validator库的使用
开发语言·后端·golang
海绵宝宝de派小星1 天前
Go:接口和反射
开发语言·后端·golang