Swagger, get to know
InPrevious section, we completed a server support at the same timeRpc
AndRESTful Api
After that, you thought you’d done it, only to find out suddenly that you wanted to write it.Api
Docking documents with front-end colleagues = =. . .
Do you think there are any components that can be automatically generatedApi
Documents to solve this problem, and that’s when you found outSwagger
Let’s get to know each other.
Introduction
Swagger
Swagger
Is the largest in the worldOpenAPI
The specification (OAS)API development tool framework supports the development of the entire API life cycle from design and documentation to testing and deployment.
Swagger
Is currently the most popularRESTful Api
One of the document generation tools, the main reason is as follows
- Cross-platform, cross-language support
- A strong community
- Ecosystem Swagger Tools (Swagger Editor、Swagger Codegen、Swagger UI…)
- Powerful console
At the same timegrpc-gateway
Also supportSwagger
OpenAPI
Specification
OpenAPI
The norm isLinux
A project of the Foundation attempts to standardize by defining a language used to describe API formats or API definitions.RESTful
Service development process.OpenAPI
The specification helps us to describe the basic information of an API, such as:
- A general description of the API
- Available Paths (/Resources)
- Available actions on each path (get/commit …)
- Input/output format for each operation
The current V2.0 versionOpenAPI specification(i.e. SwaggerV2.0 specification) has been released and open source on github. This document is very well written and has a clear structure, which is convenient to consult at any time.
Note:OpenAPI
The introduction of the specification is quoted fromOriginal text
Use
generateSwagger
The documentation for
First, we need to check whether $GOBIN containsprotoc-gen-swagger
executable file
If it does not exist, it needs to be executed:
go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger
After waiting for the completion of the execution, can be in$GOPATH/bin
Find the executable file under and move it to$GOBIN
Just go down
SecondBack to$GOPATH/src/grpc-hello-world/proto
Next, execute the command
protoc -I/usr/local/include -I. -I$GOPATH/src/grpc-hello-world/proto/google/api --swagger_out=logtostderr=true:. ./hello.proto
Execute after successls
You can seehello.swagger.json
File
downloadSwagger UI
File
Swagger
Providing visualizationAPI
The management platform isSwagger UI
We download its source code and download itdist
All files in the directory are copied to the$GOPATH/src/grpc-hello-world/third_party/swagger-ui
Go
willSwagger UI
Convert toGo
Source Code
The conversion tools we use here arego-bindata
It supports converting any file to manageableGo
Source code. Used to embed binary data intoGo
In the program. And can choose to compress the file data before converting the file data into the original byte slice
Installation
go get -u github.com/jteeuwen/go-bindata/...
When completed, will$GOPATH/bin
inferiorgo-bindata
Move to$GOBIN
under
Conversion
New under Projectpkg/ui/data/swagger
Directory, back to$GOPATH/src/grpc-hello-world/third_party/swagger-ui
Next, execute the command
go-bindata --nocompress -pkg swagger -o pkg/ui/data/swagger/datafile.go third_party/swagger-ui/...
Check
Go back topkg/ui/data/swagger
Directory, check if it existsdatafile.go
File
Swagger UI
File Server (External Service)
In this step, we need to use the matchinggo-bindata-assetfs
It can be usedgo-bindata
GeneratedSwagger UI
TheGo
Code, combinationnet/http
External service provision
Installation
go get github.com/elazarl/go-bindata-assetfs/...
write
Through analysis, we know that the generated file provides aassetFS
Function that returns a that encapsulates the embedded filehttp.Filesystem
Which can be used to provide aHTTP
Service
Then let’s write itSwagger UI
The code is mainly composed of two parts, one isswagger.json
The other isswagger-ui
Response of
serveSwaggerFile
Reference packagestrings
、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)
}
In the function, we user.URL.Path
Judging the path suffix
The main thing is to do the right thing.swagger.json
File access support forhttps://127.0.0.1:50052/swagger/hello.swagger.json
Visit to)
serveSwaggerUI
Reference packagegithub.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))
}
In the function, we usedgo-bindata-assetfsTo schedule previously generateddatafile.go
, combined withnet/http
To provide to the outside worldswagger-ui
Services of
combine
After completing the function, we found thatpath.Join("proto", p)
It is written as a dead parameter, which is obviously wrong. We should export it as an external parameter, and then we will finally transform it.
First of all, we areserver.go
Add package global variablesSwaggerDir
, modifycmd/server.go
Documents:
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)
}
Modifypath.Join("proto", p)
Forpath.Join(SwaggerDir, p)
, so weswagger.json
The file path of the can modify it according to the external situation
Finalserver.go
File content:
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))
}
test
Access pathhttps://127.0.0.1:50052/swagger/hello.swagger.json
To see if the output ishello.swagger.json
For example:
Access pathhttps://127.0.0.1:50052/swagger-ui/
, view the content
Summary
This concludes our chapter.Swagger
And its ecological circle is very rich. interested partners can visit it.The official websiteSeriously study
However, the current level of completion also meets the needs of daily work and can be generated more automatically.RESTful Api
Document, complete docking with interface