什么是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
访问 http://IP:8081即可看到 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的文档结构可以参考https://swagger.io/docs/specification/2-0/basic-structure/,如果你不愿意读英文的,我简单翻译了一下下https://github.com/SongJXin/swagger-2.0-translate/blob/master/%E5%9F%BA%E6%9C%AC%E7%BB%93%E6%9E%84.md。
下面这个 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(&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(&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 = &spec.Info{
InfoProps: spec.InfoProps{
Title: "UserService",
Description: "Resource for managing Users",
Contact: &spec.ContactInfo{
Name: "john",
Email: "john@doe.rp",
URL: "http://johndoe.org",
},
License: &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 main
和 import
部分就略过了。
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 中的basePath
和Consumes、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
,使用的基本信息(info
和tags
)
http.Handle
是这个程序和swagger ui在同一个服务器上,启动这个程序的时候同时启动swagger ui用的,这里用了外部的,就把它注释掉了,不用它了。
cors
这个过滤器是为了让swagger ui能够访问到这个 API文档,没有的话,用swagger ui访问会报错。
swagger UI 访问API文档。
访问http://swaggerUI-ip:8082/?url=http://localhost:8080/apidocs.json就可以看到 swagger ui 解析API 文档后的交互式API文档了。