golang:echoでSwagger UIを使ってみる

echoで同じポートでSwagger UIを開けるようにするのに少し詰まった為メモ
sampleはgithub参照

今回使ったもの

ディレクトリ構成

echo-swagger/
 ├ main.go
 ├ hello/
 │ └ controller/
 │   └ hello_controller.go
 ├ swagger-ui/
 │ ├ swagger-ui.go
 │ ├ docs.go
 │ ├ index.html
 │ ├ o2c.html
 │ ├ swagger-ui.js
 │ ├ swagger-ui.min.js
 │ ├ css/
 │ ├ lib/
 │ └ images/
 └ vendor/

実際に書いたコード

main.go

// @APIVersion 1.0.0
// @APITitle echo-swagger
// @APIDescription echo-swagger sample api
// @BasePath http://localhost:8080/swagger-ui
// @SubApi hello [/hello]

package main

import (
    helloC "github.com/go-examples/echo-swagger/hello/controller"
    swaggerui "github.com/go-examples/echo-swagger/swagger-ui"
    "github.com/labstack/echo"
)

func main() {
    e := echo.New()
    helloC.NewHelloController(e)

    swaggerui.NewSwaggerController(e)

    e.Logger.Fatal(e.Start(":8080"))
}

まずはmain.go ポイントはBasePathを{host}:{port}/swagger-uiにしたところ ここを{host}:{port}にするとswagger for Goで生成するファイルのAPI定義を読み込んでくれなくなる

hello_contoller.go

package controller

import (
    "net/http"

    "github.com/labstack/echo"
)

// HelloController hello controller
type HelloController struct{}

// NewHelloController mount hello controller
func NewHelloController(e *echo.Echo) {
    handler := &HelloController{}

    e.GET("/hello", handler.Hello)
}

// Hello hello world
// @Title Hello
// @Description Hello 'your name'
// @Assept json
// @Param name query string true "名前"
// @Success 200 {object} string true "Hello 'your name'"
// @Resource /hello
// @Router /hello [get]
func (c *HelloController) Hello(ctx echo.Context) error {
    name := ctx.QueryParam("name")
    return ctx.String(http.StatusOK, "Hello, "+name)
}

controllerに関してはドキュメントどおりに作成

swagger-ui.go

package swaggerui

import (
    "flag"
    "html/template"
    "net/http"
    "strings"

    "github.com/labstack/echo"
)

var apiurl = flag.String("api", "http://localhost", "The base path URI of the API service")

// SwaggerController swagger controller
type SwaggerController struct{}

// NewSwaggerController mount swagger controller
func NewSwaggerController(e *echo.Echo) {
    handler := &SwaggerController{}
    flag.Parse()

    e.GET("/", echo.WrapHandler(http.HandlerFunc(handler.IndexHandler)))
    e.Static("/swagger-ui", "./swagger-ui/")

    for apiKey := range apiDescriptionsJson {
        e.GET("/swagger-ui/"+apiKey, echo.WrapHandler(http.HandlerFunc(handler.APIDescriptionHandler)))
    }
}

// IndexHandler index handler
func (c *SwaggerController) IndexHandler(w http.ResponseWriter, r *http.Request) {
    isJSONRequest := false

    if acceptHeaders, ok := r.Header["Accept"]; ok {
        for _, acceptHeader := range acceptHeaders {
            if strings.Contains(acceptHeader, "json") {
                isJSONRequest = true
                break
            }
        }
    }

    if isJSONRequest {
        w.Write([]byte(resourceListingJson))
    } else {
        http.Redirect(w, r, "/swagger-ui/", http.StatusFound)
    }
}

// APIDescriptionHandler api description handler
func (c *SwaggerController) APIDescriptionHandler(w http.ResponseWriter, r *http.Request) {
    apiKey := strings.Split(strings.Trim(r.RequestURI, "/"), "/")

    if json, ok := apiDescriptionsJson[apiKey[1]]; ok {
        t, e := template.New("desc").Parse(json)
        if e != nil {
            w.WriteHeader(http.StatusInternalServerError)
            return
        }
        t.Execute(w, *apiurl)
    } else {
        w.WriteHeader(http.StatusNotFound)
    }
}

swagger-uiのcontrollerはswagger for goのweb.go-exampleの中身を書き換えたものを作成 IndexHandlerとAPIDescriptionHandlerはそのまま使用し、
Routingの際にecho.WrapHandlerでラップする

e.Static("/swagger-ui", "./swagger-ui/")

ここの部分はweb.go-exampleでは下記のようになっているがecho.staticで/swaggerで静的ファイルを読み込むように変更

http.Handle("/swagger-ui/", http.StripPrefix("/swagger-ui/", http.FileServer(http.Dir(*staticContent))))

Makefile

REPO=github.com/go-examples/echo-swagger

## generate swagger
gen-swagger:
    # swaggerファイル生成
    swagger -apiPackage="$(REPO)" -mainApiFile="$(REPO)/main.go" -output=./swagger-ui
    sed -i -e "s/package main/package swaggerui/g" ./swagger-ui/docs.go
    sed -i -e "s/\"basePath\": \"http:\/\/localhost:8080\/swagger-ui\"/\"basePath\": \"http:\/\/localhost:8080\"/g" swagger-ui/docs.go
    sed -i -e "1,/\"basePath\": \"http:\/\/localhost:8080\"/s/\"basePath\": \"http:\/\/localhost:8080\"/\"basePath\": \"http:\/\/localhost:8080\/swagger-ui\"/" swagger-ui/docs.go

ポイントはAPP定義のbasePathは{host}/swaggerに設定
APIのbasePathは{host}/に設定すること
こうすることでSwagger UIから実際にAPIを叩くことができるようになる
更に、package名をswaggeruiにすることで変数をswagger-ui.goで使用できるようにする

起動してみる

$ make gen-swagger
$ go build
$ ./echo-swagger

http://localhost:8080にアクセスして確認

f:id:j211025ogu:20180308183949g:plain

無事Swagger UIを表示させることが出来た