在上一節,咱們完成了一個服務端同時支持Rpc
和RESTful Api
後,你覺得本身大功告成了,結果忽然發現要寫Api
文檔和前端同事對接= = 。。。html
你尋思有沒有什麼組件可以自動化生成Api
文檔來解決這個問題,就在這時你發現了Swagger
,一塊兒瞭解一下吧!前端
原文地址:Swagger瞭解一下git
Swagger
是全球最大的OpenAPI
規範(OAS)API開發工具框架,支持從設計和文檔到測試和部署的整個API生命週期的開發github
Swagger
是目前最受歡迎的RESTful Api
文檔生成工具之一,主要的緣由以下golang
同時grpc-gateway
也支持Swagger
json
OpenAPI
規範OpenAPI
規範是Linux
基金會的一個項目,試圖經過定義一種用來描述API格式或API定義的語言,來規範RESTful
服務開發過程。OpenAPI
規範幫助咱們描述一個API的基本信息,好比:segmentfault
目前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 UI
的Go
代碼,結合net/http
對外提供服務
go get github.com/elazarl/go-bindata-assetfs/...
經過分析,咱們得知生成的文件提供了一個assetFS
函數,該函數返回一個封裝了嵌入文件的http.Filesystem
,能夠用其來提供一個HTTP
服務
那麼咱們來編寫Swagger UI
的代碼吧,主要是兩個部分,一個是swagger.json
,另一個是swagger-ui
的響應
引用包strings
、path
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
的訪問)
引用包github.com/elazarl/go-bindata-assetfs
、grpc-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
的內容,例如:
訪問路徑https://127.0.0.1:50052/swagger-ui/
,查看內容
至此咱們這一章節就完畢了,Swagger
和其生態圈十分的豐富,有興趣研究的小夥伴能夠到其官網認真研究
而目前完成的程度也知足了平常工做的需求了,可較自動化的生成RESTful Api
文檔,完成與接口對接