Grpc+Grpc Gateway Practice 3 Swagger

  Api documentation, golang, grpc, swagger

Swagger, get to know

InPrevious section, we completed a server support at the same timeRpcAndRESTful ApiAfter that, you thought you’d done it, only to find out suddenly that you wanted to write it.ApiDocking documents with front-end colleagues = =. . .

Do you think there are any components that can be automatically generatedApiDocuments to solve this problem, and that’s when you found outSwaggerLet’s get to know each other.

Introduction

Swagger

SwaggerIs the largest in the worldOpenAPIThe specification (OAS)API development tool framework supports the development of the entire API life cycle from design and documentation to testing and deployment.

SwaggerIs currently the most popularRESTful ApiOne of the document generation tools, the main reason is as follows

At the same timegrpc-gatewayAlso supportSwagger

grpc-gateway features

OpenAPISpecification

OpenAPIThe norm isLinuxA project of the Foundation attempts to standardize by defining a language used to describe API formats or API definitions.RESTfulService development process.OpenAPIThe 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:OpenAPIThe introduction of the specification is quoted fromOriginal text

Use

generateSwaggerThe documentation for

First, we need to check whether $GOBIN containsprotoc-gen-swaggerexecutable 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/binFind the executable file under and move it to$GOBINJust go down

SecondBack to$GOPATH/src/grpc-hello-world/protoNext, 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 successlsYou can seehello.swagger.jsonFile

downloadSwagger UIFile

SwaggerProviding visualizationAPIThe management platform isSwagger UI

We download its source code and download itdistAll files in the directory are copied to the$GOPATH/src/grpc-hello-world/third_party/swagger-uiGo

willSwagger UIConvert toGoSource Code

The conversion tools we use here arego-bindata

It supports converting any file to manageableGoSource code. Used to embed binary data intoGoIn 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/bininferiorgo-bindataMove to$GOBINunder

Conversion

New under Projectpkg/ui/data/swaggerDirectory, back to$GOPATH/src/grpc-hello-world/third_party/swagger-uiNext, 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/swaggerDirectory, check if it existsdatafile.goFile

Swagger UIFile Server (External Service)

In this step, we need to use the matchinggo-bindata-assetfs

It can be usedgo-bindataGeneratedSwagger UITheGoCode, combinationnet/httpExternal service provision

Installation

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

write

Through analysis, we know that the generated file provides aassetFSFunction that returns a that encapsulates the embedded filehttp.FilesystemWhich can be used to provide aHTTPService

Then let’s write itSwagger UIThe code is mainly composed of two parts, one isswagger.jsonThe other isswagger-uiResponse of

serveSwaggerFile

Reference packagestringspath

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.PathJudging the path suffix

The main thing is to do the right thing.swagger.jsonFile access support forhttps://127.0.0.1:50052/swagger/hello.swagger.jsonVisit to)

serveSwaggerUI

Reference packagegithub.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))
}

In the function, we usedgo-bindata-assetfsTo schedule previously generateddatafile.go, combined withnet/httpTo provide to the outside worldswagger-uiServices 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.goAdd package global variablesSwaggerDir, modifycmd/server.goDocuments:

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.jsonThe file path of the can modify it according to the external situation

Finalserver.goFile 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.jsonTo see if the output ishello.swagger.jsonFor example:
hello.swagger.json

Access pathhttps://127.0.0.1:50052/swagger-ui/, view the content
图片描述

Summary

This concludes our chapter.SwaggerAnd 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 ApiDocument, complete docking with interface

References

Sample code