1、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
生成。
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
$ 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代码:
$ go install github.com/jteeuwen/go-bindata/...
3、go-bindata-assetfs
在应用启动后,对外提供文件服务,这样可以通过web
访问swagger
的json
文件:
$ go install github.com/elazarl/go-bindata-assetfs/...
4、glog
是常用的日志工具:
$ go get -u github.com/golang/glog
1.2 编写proto文件
新建swaggerdemo.proto
:
// 协议类型
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文件:
$ protoc --go_out=plugins=grpc:. protoc/swaggerdemo.proto
生成swaggerdemo.pb.gw.go
,gRPC-Gateway所需的go文件:
$ protoc --grpc-gateway_out=logtostderr=true:. protoc/swaggerdemo.proto
生成swaggerdemo.swagger.json
,swagger-ui
要用的json
文件,依据此文件,swagger
展现的页面中会有
gRPC-Gateway
暴露的服务和参数定义,可以在页面上发起请求:
$ 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
代码:
$ go-bindata --nocompress -pkg swagger -o swagger/datafile.go swagger/swagger-ui/...
1.4 编写gRPC的服务端代码
新建文件server.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
,内容如下:
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 测试
[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完成。
# 项目结构
$ 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