go restful 生成 swagger 2.0 文档

什么是swagger

Swagger 允许您描述 API 的结构,以便机器能够读取它们。Swagger 所有能力中最卓越的是 api 描述自身结构的能力。为什么他这么棒?通过阅读API的结构,我们可以自动构建漂亮的交互式 API 文档。我们还可以用多种语言为您的 API 自动生成服务端库,并探索其他可能性,比如自动化测试。Swagger 通过请求 API 返回包含整个 API 详细描述的 YAML 或 JSON 来实现这一点。这个文件本质上是您的API的资源列表,它遵循OpenAPI规范。该规范要求您包括以下信息:

  • 您的API支持哪些操作?
  • 您的API的参数是什么?它返回什么?
  • 您的API需要一些授权吗?
  • 甚至还有一些有趣的东西,比如术语、联系信息和使用API的许可。

您可以手动为您的 API 编写一个 Swagger 规范,或者让它从源代码中的注释自动生成。

一些与swagger 相关的工具:

  • swagger ui 生成交互式API文档,让用户直接在浏览器中尝试API调用。
  • swagger CodeGen 用API文档生成代码
  • swagger edit 集合了 swagger ui 和 swagger CodeGen 的部分功能

搭建swagger相关的工具

这里只需要搭建 swagger ui 和 swagger edit 就可以了,搭建swagger edit 是为了从网页端生成代码。 搭建swagger ui 是为了可以读取API文档,生成交互式API。
以下是使用docker搭建的方式:

搭建 swagger edit

docker run -d -p 8081:8080 swaggerapi/swagger-editor

访问 即可看到 swagger editor页面了。swagger editor 既可以编写 swagger 2.0 也可编写 swagger 3.0 。 但是,swagger 3.0 可生成代码的种类明显少于2.0,这也是我为什么用2.0 的原因。

搭建 swagger ui

docker run -d -p 8082:8080 swaggerapi/swagger-ui

swagger 2.0 API 文档的结构

swagger 2.0的文档结构可以参考,如果你不愿意读英文的,我简单翻译了一下下
下面这个 API 文档是由之后的 go-restful 生成的。

swagger: '2.0'
info:
description: Resource for managing Users
title: UserService
contact:
name: john
url: 'http://johndoe.org'
email: john@doe.rp
license:
name: MIT
url: 'http://mit.org'
version: 1.0.0
paths:
/users:
get:
consumes:
- application/xml
- application/json
produces:
- application/json
- application/xml
tags:
- usersa
summary: get all users
operationId: findAllUsers
responses:
'200':
description: OK
schema:
type: array
items:
$ref: '#/definitions/main.User'
put:
consumes:
- application/xml
- application/json
produces:
- application/json
- application/xml
tags:
- usersa
summary: create a user
operationId: createUser
parameters:
- name: body
in: body
required: true
schema:
$ref: '#/definitions/main.User'
responses:
'200':
description: OK
'/users/{user-id}':
get:
consumes:
- application/xml
- application/json
produces:
- application/json
- application/xml
tags:
- usersa
summary: get a user
operationId: findUser
parameters:
- type: integer
default: 1
description: identifier of the user
name: user-id
in: path
required: true
responses:
'200':
description: OK
schema:
$ref: '#/definitions/main.User'
'404':
description: Not Found
put:
consumes:
- application/xml
- application/json
produces:
- application/json
- application/xml
tags:
- usersa
summary: update a user
operationId: updateUser
parameters:
- type: string
description: identifier of the user
name: user-id
in: path
required: true
- name: body
in: body
required: true
schema:
$ref: '#/definitions/main.User'
responses:
'200':
description: OK
delete:
consumes:
- application/xml
- application/json
produces:
- application/json
- application/xml
tags:
- usersa
summary: delete a user
operationId: removeUser
parameters:
- type: string
description: identifier of the user
name: user-id
in: path
required: true
responses:
'200':
description: OK
definitions:
main.User:
required:
- id
- name
- age
properties:
age:
description: age of the user
type: integer
format: int32
default: 21
id:
description: identifier of the user
type: string
name:
description: name of the user
type: string
default: john
tags:
- description: Managing users
name: users

go restful 集成 swagger 2.0

获取 go restful 和 swagger 相关的包

go get github.com/emicklei/go-restful
go get github.com/emicklei/go-restful-openapi

代码先贴为敬(这个代码是go-restful-openapi中自带的例子):

package main

import (
"log"
"net/http"

"github.com/emicklei/go-restful"
restfulspec "github.com/emicklei/go-restful-openapi"
"github.com/go-openapi/spec"
)

type UserResource struct {
// normally one would use DAO (data access object)
users map[string]User
}

func (u UserResource) WebService() *restful.WebService {
ws := new(restful.WebService)
ws.
Path("/users").
Consumes(restful.MIME_XML, restful.MIME_JSON).
Produces(restful.MIME_JSON, restful.MIME_XML) // you can specify this per route as well

tags := []string{"users"}

ws.Route(ws.GET("/").To(u.findAllUsers).
// docs
Doc("get all users").
Metadata(restfulspec.KeyOpenAPITags, tags).
Writes([]User{}).
Returns(200, "OK", []User{}))

ws.Route(ws.GET("/{user-id}").To(u.findUser).
// docs
Doc("get a user").
Param(ws.PathParameter("user-id", "identifier of the user").DataType("integer").DefaultValue("1")).
Metadata(restfulspec.KeyOpenAPITags, tags).
Writes(User{}). // on the response
Returns(200, "OK", User{}).
Returns(404, "Not Found", nil))

ws.Route(ws.PUT("/{user-id}").To(u.updateUser).
// docs
Doc("update a user").
Param(ws.PathParameter("user-id", "identifier of the user").DataType("string")).
Metadata(restfulspec.KeyOpenAPITags, tags).
Reads(User{})) // from the request

ws.Route(ws.PUT("").To(u.createUser).
// docs
Doc("create a user").
Metadata(restfulspec.KeyOpenAPITags, tags).
Reads(User{})) // from the request

ws.Route(ws.DELETE("/{user-id}").To(u.removeUser).
// docs
Doc("delete a user").
Metadata(restfulspec.KeyOpenAPITags, tags).
Param(ws.PathParameter("user-id", "identifier of the user").DataType("string")))

return ws
}

// GET http://localhost:8080/users
//
func (u UserResource) findAllUsers(request *restful.Request, response *restful.Response) {
list := []User{}
for _, each := range u.users {
list = append(list, each)
}
response.WriteEntity(list)
}

// GET http://localhost:8080/users/1
//
func (u UserResource) findUser(request *restful.Request, response *restful.Response) {
id := request.PathParameter("user-id")
usr := u.users[id]
if len(usr.ID) == 0 {
response.WriteErrorString(http.StatusNotFound, "User could not be found.")
} else {
response.WriteEntity(usr)
}
}

// PUT http://localhost:8080/users/1
// <user><id>1</id><name>Melissa Raspberry</name></user>
//
func (u *UserResource) updateUser(request *restful.Request, response *restful.Response) {
usr := new(User)
err := request.ReadEntity(&amp;usr)
if err == nil {
u.users[usr.ID] = *usr
response.WriteEntity(usr)
} else {
response.WriteError(http.StatusInternalServerError, err)
}
}

// PUT http://localhost:8080/users/1
// <user><id>1</id><name>Melissa</name></user>
//
func (u *UserResource) createUser(request *restful.Request, response *restful.Response) {
usr := User{ID: request.PathParameter("user-id")}
err := request.ReadEntity(&amp;usr)
if err == nil {
u.users[usr.ID] = usr
response.WriteHeaderAndEntity(http.StatusCreated, usr)
} else {
response.WriteError(http.StatusInternalServerError, err)
}
}

// DELETE http://localhost:8080/users/1
//
func (u *UserResource) removeUser(request *restful.Request, response *restful.Response) {
id := request.PathParameter("user-id")
delete(u.users, id)
}

func main() {
u := UserResource{map[string]User{}}
restful.DefaultContainer.Add(u.WebService())

config := restfulspec.Config{
WebServices: restful.RegisteredWebServices(), // you control what services are visible
APIPath:     "/apidocs.json",
PostBuildSwaggerObjectHandler: enrichSwaggerObject}
restful.DefaultContainer.Add(restfulspec.NewOpenAPIService(config))

// Optionally, you can install the Swagger Service which provides a nice Web UI on your REST API
// You need to download the Swagger HTML5 assets and change the FilePath location in the config below.
// Open http://localhost:8080/apidocs/?url=http://localhost:8080/apidocs.json
//http.Handle("/apidocs/", http.StripPrefix("/apidocs/", http.FileServer(http.Dir("/Users/emicklei/Projects/swagger-ui/dist"))))

// Optionally, you may need to enable CORS for the UI to work.
cors := restful.CrossOriginResourceSharing{
AllowedHeaders: []string{"Content-Type", "Accept"},
AllowedMethods: []string{"GET", "POST", "PUT", "DELETE"},
CookiesAllowed: false,
Container:      restful.DefaultContainer}
restful.DefaultContainer.Filter(cors.Filter)

log.Printf("Get the API using http://localhost:8080/apidocs.json")
log.Printf("Open Swagger UI using http://10.110.27.102:8082/?url=http://localhost:8080/apidocs.json")
log.Fatal(http.ListenAndServe(":8080", nil))
}

func enrichSwaggerObject(swo *spec.Swagger) {
swo.Info = &amp;spec.Info{
InfoProps: spec.InfoProps{
Title:       "UserService",
Description: "Resource for managing Users",
Contact: &amp;spec.ContactInfo{
Name:  "john",
Email: "john@doe.rp",
URL:   "http://johndoe.org",
},
License: &amp;spec.License{
Name: "MIT",
URL:  "http://mit.org",
},
Version: "1.0.0",
},
}
swo.Tags = []spec.Tag{spec.Tag{TagProps: spec.TagProps{
Name:        "users",
Description: "Managing users"}}}
}

// User is just a sample type
type User struct {
ID   string `json:"id" description:"identifier of the user"`
Name string `json:"name" description:"name of the user" default:"john"`
Age  int    `json:"age" description:"age of the user" default:"21"`
}

api 文档生成规则

package mainimport部分就略过了。
UserResource这个类是go-restful的基本结构 关于go-restful框架的这里就不多说了。

enrichSwaggerObject这个函数,是对应生成了swagger 2.0 API 文档(下称API文档)的info和 tags部分。

User 这个结构,即被go-restful使用,作为操作的对象,也是对应了API文档的definitions部分。在反单引号(`)之间的内容规定了这个结构体与definitions的对应关系。

WebService函数中:
ws.path().Consumes().Produces()对应swagger 2.0 中的basePathConsumes、Produces,只不过,在生成API过程中把他们分解到了具体的每一个path
tags就是个字符串数组,在ws.Route.Metadata中用到,规定了这个path的标签,如果enrichSwaggerObject中定义的tags定义了这个tag,将从里面取对应的Description信息,如果没有定义,就当作一个全新的tag
ws.Route生成了API文档的path
ws.Route(ws.GET)对应了一个path的一个operation
ws.Route.To这里是go-restful框架将请求路由到相应的函数(用词可能不准确,大概就是这么个意思,这函数处理这次请求),同时,在生成API文档的时候,函数的名称会做为path.operation.operationId
ws.Route.Doc对应生成了API文档的 path.operation.summary
ws.Route.Writes生成的path.operation.responses.default(这里有个很奇怪的现象。我用 chrome 和 firefox 看到的生成的API是不一样的,chrome 不会显示这个path.operation.responses.default字段,在swagger ui中可以看到这个字段)
ws.Route.Return这里生成path.operation.responses对应的状态码的返回。第一个参数,对应状态码,第二个参数对应description,第三个参数对应了schema
ws.Route.Param生成API文档的parameter
ws.Route.Reads也是生成API文档的parameter,区别是它使用了schema,引用了definitions中的对象

main函数中:
config设置了生成API文档的一些配置:

  • WebServices,为哪个WebServices 生成 API文档
  • APIPath,访问API文档的路径。
  • PostBuildSwaggerObjectHandler,使用的基本信息(infotags

http.Handle是这个程序和swagger ui在同一个服务器上,启动这个程序的时候同时启动swagger ui用的,这里用了外部的,就把它注释掉了,不用它了。
cors 这个过滤器是为了让swagger ui能够访问到这个 API文档,没有的话,用swagger ui访问会报错。

swagger UI 访问API文档。

访问就可以看到 swagger ui 解析API 文档后的交互式API文档了。

Author: jxin

发表评论

电子邮件地址不会被公开。 必填项已用*标注