• Swagger了解一下
    • 介绍
      • Swagger
      • OpenAPI规范
    • 使用
      • 生成Swagger的说明文件
      • 下载Swagger UI文件
      • Swagger UI转换为Go源代码
        • 安装
        • 转换
        • 检查
      • Swagger UI文件服务器(对外提供服务)
        • 安装
        • 编写
          • serveSwaggerFile
          • serveSwaggerUI
        • 结合
    • 测试
    • 小结
    • 参考
      • 示例代码

    Swagger了解一下

    在上一节,我们完成了一个服务端同时支持RpcRESTful Api后,你以为自己大功告成了,结果突然发现要写Api文档和前端同事对接= = 。。。

    你寻思有没有什么组件能够自动化生成Api文档来解决这个问题,就在这时你发现了Swagger,一起了解一下吧!

    介绍

    Swagger

    Swagger是全球最大的OpenAPI规范(OAS)API开发工具框架,支持从设计和文档到测试和部署的整个API生命周期的开发

    Swagger是目前最受欢迎的RESTful Api文档生成工具之一,主要的原因如下

    • 跨平台、跨语言的支持
    • 强大的社区
    • 生态圈 Swagger Tools(Swagger Editor、Swagger Codegen、Swagger UI …)
    • 强大的控制台

    同时grpc-gateway也支持Swagger

    [image]

    OpenAPI规范

    OpenAPI规范是Linux基金会的一个项目,试图通过定义一种用来描述API格式或API定义的语言,来规范RESTful服务开发过程。OpenAPI规范帮助我们描述一个API的基本信息,比如:

    • 有关该API的一般性描述
    • 可用路径(/资源)
    • 在每个路径上的可用操作(获取/提交…)
    • 每个操作的输入/输出格式

    目前V2.0版本的OpenAPI规范(也就是SwaggerV2.0规范)已经发布并开源在github上。该文档写的非常好,结构清晰,方便随时查阅。

    注:OpenAPI规范的介绍引用自原文

    使用

    生成Swagger的说明文件

    第一,我们需要检查$GOBIN下是否包含protoc-gen-swagger可执行文件

    若不存在则需要执行:

    1. go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger

    等待执行完毕后,可在$GOPATH/bin下发现该执行文件,将其移动到$GOBIN下即可

    第二,回到$GOPATH/src/grpc-hello-world/proto下,执行命令

    1. protoc -I/usr/local/include -I. -I$GOPATH/src/grpc-hello-world/proto/google/api --swagger_out=logtostderr=true:. ./hello.proto

    成功后执行ls即可看到hello.swagger.json文件

    下载Swagger UI文件

    Swagger提供可视化的API管理平台,就是Swagger UI

    我们将其源码下载下来,并将其dist目录下的所有文件拷贝到我们项目中的$GOPATH/src/grpc-hello-world/third_party/swagger-ui

    Swagger UI转换为Go源代码

    在这里我们使用的转换工具是go-bindata

    它支持将任何文件转换为可管理的Go源代码。用于将二进制数据嵌入到Go程序中。并且在将文件数据转换为原始字节片之前,可以选择压缩文件数据

    安装

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

    完成后,将$GOPATH/bin下的go-bindata移动到$GOBIN

    转换

    在项目下新建pkg/ui/data/swagger目录,回到$GOPATH/src/grpc-hello-world/third_party/swagger-ui下,执行命令

    1. go-bindata --nocompress -pkg swagger -o pkg/ui/data/swagger/datafile.go third_party/swagger-ui/...

    检查

    回到pkg/ui/data/swagger目录,检查是否存在datafile.go文件

    Swagger UI文件服务器(对外提供服务)

    在这一步,我们需要使用与其配套的go-bindata-assetfs

    它能够使用go-bindata所生成Swagger UIGo代码,结合net/http对外提供服务

    安装

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

    编写

    通过分析,我们得知生成的文件提供了一个assetFS函数,该函数返回一个封装了嵌入文件的http.Filesystem,可以用其来提供一个HTTP服务

    那么我们来编写Swagger UI的代码吧,主要是两个部分,一个是swagger.json,另外一个是swagger-ui的响应

    serveSwaggerFile

    引用包stringspath

    1. func serveSwaggerFile(w http.ResponseWriter, r *http.Request) {
    2. if ! strings.HasSuffix(r.URL.Path, "swagger.json") {
    3. log.Printf("Not Found: %s", r.URL.Path)
    4. http.NotFound(w, r)
    5. return
    6. }
    7. p := strings.TrimPrefix(r.URL.Path, "/swagger/")
    8. p = path.Join("proto", p)
    9. log.Printf("Serving swagger-file: %s", p)
    10. http.ServeFile(w, r, p)
    11. }

    在函数中,我们利用r.URL.Path进行路径后缀判断

    主要做了对swagger.json的文件访问支持(提供https://127.0.0.1:50052/swagger/hello.swagger.json的访问)

    serveSwaggerUI

    引用包github.com/elazarl/go-bindata-assetfsgrpc-hello-world/pkg/ui/data/swagger

    1. func serveSwaggerUI(mux *http.ServeMux) {
    2. fileServer := http.FileServer(&assetfs.AssetFS{
    3. Asset: swagger.Asset,
    4. AssetDir: swagger.AssetDir,
    5. Prefix: "third_party/swagger-ui",
    6. })
    7. prefix := "/swagger-ui/"
    8. mux.Handle(prefix, http.StripPrefix(prefix, fileServer))
    9. }

    在函数中,我们使用了go-bindata-assetfs来调度先前生成的datafile.go,结合net/http来对外提供swagger-ui的服务

    结合

    在完成功能后,我们发现path.Join("proto", p)是写死参数的,这样显然不对,我们应该将其导出成外部参数,那么我们来最终改造一番

    首先我们在server.go新增包全局变量SwaggerDir,修改cmd/server.go文件:

    1. package cmd
    2. import (
    3. "log"
    4. "github.com/spf13/cobra"
    5. "grpc-hello-world/server"
    6. )
    7. var serverCmd = &cobra.Command{
    8. Use: "server",
    9. Short: "Run the gRPC hello-world server",
    10. Run: func(cmd *cobra.Command, args []string) {
    11. defer func() {
    12. if err := recover(); err != nil {
    13. log.Println("Recover error : %v", err)
    14. }
    15. }()
    16. server.Run()
    17. },
    18. }
    19. func init() {
    20. serverCmd.Flags().StringVarP(&server.ServerPort, "port", "p", "50052", "server port")
    21. serverCmd.Flags().StringVarP(&server.CertPemPath, "cert-pem", "", "./conf/certs/server.pem", "cert-pem path")
    22. serverCmd.Flags().StringVarP(&server.CertKeyPath, "cert-key", "", "./conf/certs/server.key", "cert-key path")
    23. serverCmd.Flags().StringVarP(&server.CertServerName, "cert-server-name", "", "grpc server name", "server's hostname")
    24. serverCmd.Flags().StringVarP(&server.SwaggerDir, "swagger-dir", "", "proto", "path to the directory which contains swagger definitions")
    25. rootCmd.AddCommand(serverCmd)
    26. }

    修改path.Join("proto", p)path.Join(SwaggerDir, p),这样的话我们swagger.json的文件路径就可以根据外部情况去修改它

    最终server.go文件内容:

    1. package server
    2. import (
    3. "crypto/tls"
    4. "net"
    5. "net/http"
    6. "log"
    7. "strings"
    8. "path"
    9. "golang.org/x/net/context"
    10. "google.golang.org/grpc"
    11. "google.golang.org/grpc/credentials"
    12. "github.com/grpc-ecosystem/grpc-gateway/runtime"
    13. "github.com/elazarl/go-bindata-assetfs"
    14. pb "grpc-hello-world/proto"
    15. "grpc-hello-world/pkg/util"
    16. "grpc-hello-world/pkg/ui/data/swagger"
    17. )
    18. var (
    19. ServerPort string
    20. CertServerName string
    21. CertPemPath string
    22. CertKeyPath string
    23. SwaggerDir string
    24. EndPoint string
    25. tlsConfig *tls.Config
    26. )
    27. func Run() (err error) {
    28. EndPoint = ":" + ServerPort
    29. tlsConfig = util.GetTLSConfig(CertPemPath, CertKeyPath)
    30. conn, err := net.Listen("tcp", EndPoint)
    31. if err != nil {
    32. log.Printf("TCP Listen err:%v\n", err)
    33. }
    34. srv := newServer(conn)
    35. log.Printf("gRPC and https listen on: %s\n", ServerPort)
    36. if err = srv.Serve(util.NewTLSListener(conn, tlsConfig)); err != nil {
    37. log.Printf("ListenAndServe: %v\n", err)
    38. }
    39. return err
    40. }
    41. func newServer(conn net.Listener) (*http.Server) {
    42. grpcServer := newGrpc()
    43. gwmux, err := newGateway()
    44. if err != nil {
    45. panic(err)
    46. }
    47. mux := http.NewServeMux()
    48. mux.Handle("/", gwmux)
    49. mux.HandleFunc("/swagger/", serveSwaggerFile)
    50. serveSwaggerUI(mux)
    51. return &http.Server{
    52. Addr: EndPoint,
    53. Handler: util.GrpcHandlerFunc(grpcServer, mux),
    54. TLSConfig: tlsConfig,
    55. }
    56. }
    57. func newGrpc() *grpc.Server {
    58. creds, err := credentials.NewServerTLSFromFile(CertPemPath, CertKeyPath)
    59. if err != nil {
    60. panic(err)
    61. }
    62. opts := []grpc.ServerOption{
    63. grpc.Creds(creds),
    64. }
    65. server := grpc.NewServer(opts...)
    66. pb.RegisterHelloWorldServer(server, NewHelloService())
    67. return server
    68. }
    69. func newGateway() (http.Handler, error) {
    70. ctx := context.Background()
    71. dcreds, err := credentials.NewClientTLSFromFile(CertPemPath, CertServerName)
    72. if err != nil {
    73. return nil, err
    74. }
    75. dopts := []grpc.DialOption{grpc.WithTransportCredentials(dcreds)}
    76. gwmux := runtime.NewServeMux()
    77. if err := pb.RegisterHelloWorldHandlerFromEndpoint(ctx, gwmux, EndPoint, dopts); err != nil {
    78. return nil, err
    79. }
    80. return gwmux, nil
    81. }
    82. func serveSwaggerFile(w http.ResponseWriter, r *http.Request) {
    83. if ! strings.HasSuffix(r.URL.Path, "swagger.json") {
    84. log.Printf("Not Found: %s", r.URL.Path)
    85. http.NotFound(w, r)
    86. return
    87. }
    88. p := strings.TrimPrefix(r.URL.Path, "/swagger/")
    89. p = path.Join(SwaggerDir, p)
    90. log.Printf("Serving swagger-file: %s", p)
    91. http.ServeFile(w, r, p)
    92. }
    93. func serveSwaggerUI(mux *http.ServeMux) {
    94. fileServer := http.FileServer(&assetfs.AssetFS{
    95. Asset: swagger.Asset,
    96. AssetDir: swagger.AssetDir,
    97. Prefix: "third_party/swagger-ui",
    98. })
    99. prefix := "/swagger-ui/"
    100. mux.Handle(prefix, http.StripPrefix(prefix, fileServer))
    101. }

    测试

    访问路径https://127.0.0.1:50052/swagger/hello.swagger.json,查看输出内容是否为hello.swagger.json的内容,例如:
    [image]

    访问路径https://127.0.0.1:50052/swagger-ui/,查看内容
    [image]

    小结

    至此我们这一章节就完毕了,Swagger和其生态圈十分的丰富,有兴趣研究的小伙伴可以到其官网认真研究

    而目前完成的程度也满足了日常工作的需求了,可较自动化的生成RESTful Api文档,完成与接口对接

    参考

    示例代码

    • grpc-hello-world