Swagger使用初探

简介

Swagger是什么?在解答这个问题之前,我们先来看看开发Restful API过程中会遇到什么问题。我们如何去设计和定义API接口?如何写接口文档?是否可以自动生成接口文档,或者设计好API之后自动生成接口代码?如果有那么一个工具可以帮助我们实现上述功能,是否感觉很棒。是的,Swagger就是干这个的,而且通过Swagger生成的接口文档不仅非常漂亮,还能实现交互测试的功能。
Swagger实际上是通过一系列工具来实现这些功能的。其主要工具如下:

  • Swagger-editor : 一个用于编辑API文档的工具
  • Swagger-ui: 用于解析editor生成的json文件,并生成对应的API文档的页面,并可以展示页面的web应用
  • Swagger-core: Swagger的Java/Scala实现,并已集成 JAX-RS (Jersey, Resteasy, CXF...), Servlets与Play Framework (其实不懂,有明白的网友欢迎留言)
  • Swagger-codegen: 用于自动生成客户端和服务器端的代码

我目前使用的主要是swagger-editor和swagger-ui,用于生成API文档。下面先介绍这两个工具。

Swagger-Editor

swagger-editor主要用于在设计和定义restful api的时候使用。我们通过编辑YAML配置文件,从而可以自动显示出API文档,从而允许我们先将接口定义清楚。

Swagger-Editor安装

  1. 下载Swagger-Editor。直接下载zip文件地址并解压
  2. 下载并且安装node.js
  3. npm install -g http-server
  4. 进入swagger-editor文件夹。运行http-server命令。
  5. 在浏览器中输入http://127.0.0.1:8080即可看到Swagger-Editor的界面

YAML语言

关于YAML语言可以参考阮一峰老师的YAML 语言教程。简单来说,我们可以通过YAML来实现某种类似于json的数据表达和描述的方式。但是它跟json相比较而言,可以有更丰富的信息表达,比如可以描述数据的类型,可以添加更多的文档相关的信息(这一点类似于xml,但是yaml要比xml简洁的多,当然代价是不能像xml那样可以通过dtd文件来验证其正确性)。另外,大部分语言都有YAML语言的相关实现,比如在Python中我们通过安装pyyaml或者ruamel.yaml(支持yaml1.2)来实现对YAML文档的读写。

OpenAPI Specification

YAML只是一种数据表示,我们还必须通过一定的规范才能描述API接口文档(比如定义path用来描述路径),而这个规范就是OpenAPI Specification。目前最新的版本是3.x版本(文档开头会是openapi: 3.0.0),而在Swagger的很多例子当中我们看到的是2.0的版本(也就是通常在文件开头的swagger: 2.0)。这一点是需要注意的,因为不同的版本的spec,其语法并不相同,如果搞混就会报语法错误。

OpenAPI Spec文档可以参考其github地址。我们以3.0版本为例,先上一个例子:

# 文本开头必须先定义openapi版本,必填项
openapi: 3.0.0

# 文档的meta data,显示文档的一些基本信息,必填项
info:
  title: Sample API
  description: Optional multiline or single-line description in [CommonMark](http://commonmark.org/help/) or HTML.
  version: 0.1.9

# 定义server相关信息,非必填
servers:
  - url: http://api.example.com/v1
    description: Optional server description, e.g. Main (production) server
  - url: http://staging-api.example.com
    description: Optional server description, e.g. Internal staging server for testing

# API的具体描述,必填项
paths:
  /users:  # api path
    get:  # api method
      summary: Returns a list of users.
      description: Optional extended description in CommonMark or HTML.
      responses:  # define response
        '200':    # status code
          description: A JSON array of user names
          content:
            application/json:
              schema: 
                type: array
                items: 
                  type: string
  /user/{userId}:
    get:
      summary: Returns a user by ID.
      parameters:  # 定义参数
        - name: userId
          in: path
          required: true
          description: Parameter description in CommonMark or HTML.
          schema:
            type : integer
            format: int64
            minimum: 1
      responses: 
        '200':
          description: OK

对照上边的例子和下表,我们可以看到OpenAPI spec是如何定义一个接口的。此种定义方式非常细致和繁琐,这主要是因为Swagger需要根据这些细致地描述来生成相对应的代码,所以就有必要将所有的细节都描述清楚。

Field Name Type Description
openapi string REQUIRED. This string MUST be the semantic version number of the OpenAPI Specification version that the OpenAPI document uses. The openapi field SHOULD be used by tooling specifications and clients to interpret the OpenAPI document. This is not related to the API info.version string.
info Info Object REQUIRED. Provides metadata about the API. The metadata MAY be used by tooling as required.
servers [Server Object] An array of Server Objects, which provide connectivity information to a target server. If the servers property is not provided, or is an empty array, the default value would be a Server Object with a url value of /.
paths Paths Object REQUIRED. The available paths and operations for the API.
components Components Object An element to hold various schemas for the specification.
security [Security Requirement Object] A declaration of which security mechanisms can be used across the API. The list of values includes alternative security requirement objects that can be used. Only one of the security requirement objects need to be satisfied to authorize a request. Individual operations can override this definition.
tags [Tag Object] A list of tags used by the specification with additional metadata. The order of the tags can be used to reflect on their order by the parsing tools. Not all tags that are used by the Operation Object must be declared. The tags that are not declared MAY be organized randomly or based on the tools' logic. Each tag name in the list MUST be unique.
externalDocs External Documentation Object Additional external documentation.

通过Swagger-Editor我们可以编辑YAML,同时直接在右端显示相应的API文档,如下图:


swagger-editor

通过[File -> Convert and save as JSON], 我们可以将该YAML转换成json文件,而该json文件可以通过Swagger-UI来解析并展示成API文档,同时,通过Swagger-UI我们还可以执行相应的http request测试。

Swagger-UI

Swagger-UI安装

  1. 下载Swagger-UI。直接下载zip文件地址并解压
  2. 下载并且安装node.js
  3. npm install -g http-server
  4. 进入swagger-editor文件夹。运行http-server -p 8081命令。
  5. 在浏览器中输入http://127.0.0.1:8081即可看到Swagger-UI展示的界面

配置

  1. 首先将Swagger-Editor生成的json文件拷贝到swagger-ui主目录下的dist文件夹
  2. 修改index.html中的url,假设你的json文件名为swagger.json,那么修改url如下:
 <script>
    window.onload = function() {
      // Begin Swagger UI call region
      const ui = SwaggerUIBundle({
        # 主要是修改这里的url,以对应你在dist目录下的json文件
        url: "http://127.0.0.1:8081/dist/swagger.json",
        dom_id: '#swagger-ui',
        deepLinking: true,
        presets: [
          SwaggerUIBundle.presets.apis,
          SwaggerUIStandalonePreset
        ],
        plugins: [
          SwaggerUIBundle.plugins.DownloadUrl
        ],
        layout: "StandaloneLayout"
      })
      // End Swagger UI call region

      window.ui = ui
    }
  </script>
  1. 重新启动Swagger-UI
# 注意端口必须要与url中设定的端口一致,否则会有跨域问题
http-server -p 8081

这时,就可以看到Swagger-UI解析生成的API文档了

通过Nginx展示静态页面

当我们需要对外部展示API文档的时候,我们可以通过redoc-cli来生成静态的html页面。步骤如下:

  1. npm install -g redoc-cli
  2. 有时redoc-cli无法直接使用, 需要安装npx.
    npm install -g npx
    注意: 有些系统(比如Ubuntu),其安装位置为/opt目录下,我们需要将其添加到path当中或者软链接到/usr/bin下
  3. 指定要生成API文档的swagger json文件进行转化,如下
    npx redoc-cli bundle -o index.html swagger.json
  4. 然后通过nginx建立一个server直接指向该index.html文档即可。配置示例:
server {
    listen 8081 default_server backlog=20480;
    listen [::]:8081 default_server;

    server_name localhost;

    access_log /home/ubuntu/logs/nginx/swagger-ui/swagger-ui.access.log main;
    error_log /home/ubuntu/logs/nginx/swagger-ui/swagger-ui.error.log;

    # 此处是存放index.html的目录,作为根目录
    set $doc_root_dir "/home/ubuntu/swagger/swagger-ui-master/dist";

    location / {
        root $doc_root_dir;
        index index.php index.html index.htm;
     }
    # 此处用于缓存相关的文件
    location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
        expires 15d;
    }
    # 用于Amazon配置的健康检查
    location /health_check {
        return 200;
    }
}

Swagger与Django

我们可以在Django中直接生成API文档,但是需要两个库的支持,一个是DRF(Django Rest Framework),另一个是drf_yasg

  1. 安装库
pip install djangorestframework
pip install drf-yasg
  1. 在项目url文件中(根url文件)添加如下代码
from django.conf.urls import include, url
from django.contrib import admin
from django.shortcuts import redirect
from rest_framework import permissions
from rest_framework.decorators import api_view

from drf_yasg import openapi
from drf_yasg.views import get_schema_view

swagger_info = openapi.Info(
    title="Snippets API",
    default_version='v1',
    description="""This is a demo project for the [drf-yasg](https://github.com/axnsan12/drf-yasg) Django Rest Framework library.

The `swagger-ui` view can be found [here](/cached/swagger).
The `ReDoc` view can be found [here](/cached/redoc).
The swagger YAML document can be found [here](/cached/swagger.yaml).

You can log in using the pre-existing `admin` user with password `passwordadmin`.""",  # noqa
    terms_of_service="https://www.google.com/policies/terms/",
    contact=openapi.Contact(email="contact@snippets.local"),
    license=openapi.License(name="BSD License"),
)

SchemaView = get_schema_view(
    validators=['ssv', 'flex'],
    public=True,
    permission_classes=(permissions.AllowAny,),
)

urlpatterns = [
    url(r'^swagger(?P<format>.json|.yaml)$', SchemaView.without_ui(cache_timeout=0), name='schema-json'),
    url(r'^swagger/$', SchemaView.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
    url(r'^redoc/$', SchemaView.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
    url(r'^redoc-old/$', SchemaView.with_ui('redoc-old', cache_timeout=0), name='schema-redoc-old'),

    url(r'^cached/swagger(?P<format>.json|.yaml)$', SchemaView.without_ui(cache_timeout=None), name='cschema-json'),
    url(r'^cached/swagger/$', SchemaView.with_ui('swagger', cache_timeout=None), name='cschema-swagger-ui'),
    url(r'^cached/redoc/$', SchemaView.with_ui('redoc', cache_timeout=None), name='cschema-redoc'),
    ...
    
]
  1. Settings文件的设置
    install apps需要有如下app
INSTALLED_APPS = [
    ...
    'django.contrib.staticfiles',
    'rest_framework',
    'drf_yasg'
]

middleware中需要添加如下中间件

MIDDLEWARE = [
    ...
    'drf_yasg.middleware.SwaggerExceptionMiddleware',
]

其它相关设置

SWAGGER_SETTINGS = {
    'DEFAULT_INFO': 'testproj.urls.swagger_info',
}
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles')
STATICFILES_DIRS = [
    os.path.join(BASE_DIR, 'testproj', 'static'),
]

  1. 在Serializer中定义相关fields时加上help_text就可以了
class AssetSerializer(serializers.Serializer):
    supportWithdraw = BooleanField(
        read_only=True, help_text='if support withdraw')
    supportDeposit = BooleanField(
        read_only=True, help_text='if support deposit')
    fee = FloatField(read_only=True, help_text='withdraw fee rate')
    currency = CharField(max_length=10, read_only=True, help_text='asset name')
    info = AssetInfoSerializer()

    def create(self, validated_data):
        pass

    def update(self, instance, validated_data):
        pass
  1. 启动server后,可以通过访问/swagger/ 或者/redoc/等路径就可以看到对应的文档

Swagger与Flask

生成Flask代码

我们可以通过swagger-py-codegen来生成对应Flask的API代码脚手架,过程如下:

  1. 安装swagger-py-codegen
pip install swagger-py-codegen
  1. .yml文件拷贝到你的目录中,这里使用的是api.yml

  2. 进入该目录,执行如下命令

swagger_py_codegen --swagger-doc api.yml example-app

这时就可以看到目录中生成的Flask代码,目录结构如下:

example-app
├── example_app
│   ├── __init__.py
│   └── v1
│       ├── __init__.py
│       ├── api
│       │   ├── __init__.py
│       │   ├── pets.py
│       │   └── pets_petId.py
│       ├── routes.py
│       ├── schemas.py
│       └── validators.py
└── requirements.txt

swagger_py_codegen有如下选项:

Options:
  -s, --swagger-doc TEXT   Swagger doc file.  [required]
  -f, --force              Force overwrite.
  -p, --package TEXT       Package name / application name.
  -t, --template-dir TEXT  Path of your custom templates directory.
  --spec, --specification  Generate online specification json response.
  --ui                     Generate swagger ui.
  --validate               Validate swagger file.
  -tlp, --templates TEXT   gen flask/tornado/falcon/sanic templates, default
                           flask.
  --version                Show current version.
  --help                   Show this message and exit.

需要注意的是选项--spec--ui可以帮助我们生成在线的API文档,使用示例:

swagger_py_codegen --swagger-doc api.yml example-app --ui --spec

这时,执行example_app/example_app下的init.py可以运行起Flask server,在浏览器中输入URL"http://0.0.0.0:5000/static/swagger-ui/index.html#/default/get_users_uid"就可以看到生成的API文档。

References

推荐阅读更多精彩内容