Grpc+Grpc Gateway實踐三 Swagger瞭解一下

上一節,咱們完成了一個服務端同時支持RpcRESTful Api後,你覺得本身大功告成了,結果忽然發現要寫Api文檔和前端同事對接= = 。。。html

你尋思有沒有什麼組件可以自動化生成Api文檔來解決這個問題,就在這時你發現了Swagger,一塊兒瞭解一下吧!前端

原文地址:Swagger瞭解一下git

介紹

Swagger

Swagger是全球最大的OpenAPI規範(OAS)API開發工具框架,支持從設計和文檔到測試和部署的整個API生命週期的開發github

Swagger是目前最受歡迎的RESTful Api文檔生成工具之一,主要的緣由以下golang

同時grpc-gateway也支持Swaggerjson

grpc-gateway features

OpenAPI規範

OpenAPI規範是Linux基金會的一個項目,試圖經過定義一種用來描述API格式或API定義的語言,來規範RESTful服務開發過程。OpenAPI規範幫助咱們描述一個API的基本信息,好比:segmentfault

  • 有關該API的通常性描述
  • 可用路徑(/資源)
  • 在每一個路徑上的可用操做(獲取/提交...)
  • 每一個操做的輸入/輸出格式

目前V2.0版本的OpenAPI規範(也就是SwaggerV2.0規範)已經發布並開源在github上。該文檔寫的很是好,結構清晰,方便隨時查閱。api

注:OpenAPI規範的介紹引用自原文服務器

使用

生成Swagger的說明文件

第一,咱們須要檢查$GOBIN下是否包含protoc-gen-swagger可執行文件框架

若不存在則須要執行:

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

等待執行完畢後,可在$GOPATH/bin下發現該執行文件,將其移動到$GOBIN下便可

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

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程序中。而且在將文件數據轉換爲原始字節片以前,能夠選擇壓縮文件數據

安裝

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下,執行命令

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對外提供服務

安裝

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

編寫

經過分析,咱們得知生成的文件提供了一個assetFS函數,該函數返回一個封裝了嵌入文件的http.Filesystem,能夠用其來提供一個HTTP服務

那麼咱們來編寫Swagger UI的代碼吧,主要是兩個部分,一個是swagger.json,另一個是swagger-ui的響應

serveSwaggerFile

引用包stringspath

func serveSwaggerFile(w http.ResponseWriter, r *http.Request) {
      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("proto", p)

    log.Printf("Serving swagger-file: %s", p)

    http.ServeFile(w, r, p)
}

在函數中,咱們利用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

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

在函數中,咱們使用了go-bindata-assetfs來調度先前生成的datafile.go,結合net/http來對外提供swagger-ui的服務

結合

在完成功能後,咱們發現path.Join("proto", p)是寫死參數的,這樣顯然不對,咱們應該將其導出成外部參數,那麼咱們來最終改造一番

首先咱們在server.go新增包全局變量SwaggerDir,修改cmd/server.go文件:

package cmd

import (
    "log"

    "github.com/spf13/cobra"
    
    "grpc-hello-world/server"
)

var serverCmd = &cobra.Command{
    Use:   "server",
    Short: "Run the gRPC hello-world server",
    Run: func(cmd *cobra.Command, args []string) {
        defer func() {
            if err := recover(); err != nil {
                log.Println("Recover error : %v", err)
            }
        }()
        
        server.Run()
    },
}

func init() {
    serverCmd.Flags().StringVarP(&server.ServerPort, "port", "p", "50052", "server port")
    serverCmd.Flags().StringVarP(&server.CertPemPath, "cert-pem", "", "./conf/certs/server.pem", "cert-pem path")
    serverCmd.Flags().StringVarP(&server.CertKeyPath, "cert-key", "", "./conf/certs/server.key", "cert-key path")
    serverCmd.Flags().StringVarP(&server.CertServerName, "cert-server-name", "", "grpc server name", "server's hostname")
    serverCmd.Flags().StringVarP(&server.SwaggerDir, "swagger-dir", "", "proto", "path to the directory which contains swagger definitions")
    
    rootCmd.AddCommand(serverCmd)
}

修改path.Join("proto", p)path.Join(SwaggerDir, p),這樣的話咱們swagger.json的文件路徑就能夠根據外部狀況去修改它

最終server.go文件內容:

package server

import (
    "crypto/tls"
    "net"
    "net/http"
    "log"
    "strings"
    "path"

    "golang.org/x/net/context"
    "google.golang.org/grpc"
    "google.golang.org/grpc/credentials"
    "github.com/grpc-ecosystem/grpc-gateway/runtime"
    "github.com/elazarl/go-bindata-assetfs"
    
    pb "grpc-hello-world/proto"
    "grpc-hello-world/pkg/util"
    "grpc-hello-world/pkg/ui/data/swagger"
)

var (
    ServerPort string
    CertServerName string
    CertPemPath string
    CertKeyPath string
    SwaggerDir string
    EndPoint string

    tlsConfig *tls.Config
)

func Run() (err error) {
    EndPoint = ":" + ServerPort
    tlsConfig = util.GetTLSConfig(CertPemPath, CertKeyPath)

    conn, err := net.Listen("tcp", EndPoint)
    if err != nil {
        log.Printf("TCP Listen err:%v\n", err)
    }

    srv := newServer(conn)

    log.Printf("gRPC and https listen on: %s\n", ServerPort)

    if err = srv.Serve(util.NewTLSListener(conn, tlsConfig)); err != nil {
        log.Printf("ListenAndServe: %v\n", err)
    }

    return err
}
 
func newServer(conn net.Listener) (*http.Server) {
    grpcServer := newGrpc()
    gwmux, err := newGateway()
    if err != nil {
        panic(err)
    }

    mux := http.NewServeMux()
    mux.Handle("/", gwmux)
    mux.HandleFunc("/swagger/", serveSwaggerFile)
    serveSwaggerUI(mux)

    return &http.Server{
        Addr:      EndPoint,
        Handler:   util.GrpcHandlerFunc(grpcServer, mux),
        TLSConfig: tlsConfig,
    }
}

func newGrpc() *grpc.Server {
    creds, err := credentials.NewServerTLSFromFile(CertPemPath, CertKeyPath)
    if err != nil {
        panic(err)
    }

    opts := []grpc.ServerOption{
        grpc.Creds(creds),
    }
    server := grpc.NewServer(opts...)

    pb.RegisterHelloWorldServer(server, NewHelloService())

    return server
}

func newGateway() (http.Handler, error) {
    ctx := context.Background()
    dcreds, err := credentials.NewClientTLSFromFile(CertPemPath, CertServerName)
    if err != nil {
        return nil, err
    }
    dopts := []grpc.DialOption{grpc.WithTransportCredentials(dcreds)}
    
    gwmux := runtime.NewServeMux()
    if err := pb.RegisterHelloWorldHandlerFromEndpoint(ctx, gwmux, EndPoint, dopts); err != nil {
        return nil, err
    }

    return gwmux, nil
}

func serveSwaggerFile(w http.ResponseWriter, r *http.Request) {
      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(SwaggerDir, 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))
}

測試

訪問路徑https://127.0.0.1:50052/swagger/hello.swagger.json,查看輸出內容是否爲hello.swagger.json的內容,例如:
hello.swagger.json

訪問路徑https://127.0.0.1:50052/swagger-ui/,查看內容
圖片描述

小結

至此咱們這一章節就完畢了,Swagger和其生態圈十分的豐富,有興趣研究的小夥伴能夠到其官網認真研究

而目前完成的程度也知足了平常工做的需求了,可較自動化的生成RESTful Api文檔,完成與接口對接

參考

示例代碼

相關文章
相關標籤/搜索