尝试过用 Wiki 和 Swagger 等工具写基于 HTTP 协议的 API 的 文档,虽然有提供 curl 示例,但接口调用者使用起来还是觉得不方便,毕竟不是所有人都习惯命令行.

直到了解到 Swagger, 简直发现了写 HTTP API 文档的神器啊. 现已捐赠给 Open API Initiative (OAI) , 和 OpenAPI 2.0 Specification 合并了.

Swagger 简介

详情介绍可以直接看官网.

按个人的理解. Swagger 提供一种简单的方式为 HTTP API 写文档,同时又方便 API 调用者测试.

Swagger 本身是由标准与工具组成的.

Swagger 标准

现已和 OpenAPI 2.0 Specification 是同一个标准了.

这个标准,有点像是 XML 和 XML Schema 的关系.

XML 是非结构化数据,根本就不清楚 A 接点下面包含的是 B 还是 C 接口. 而 XML Schema 就是用来告诉我们, A 接点下面包含什么接点,同时还支持数据验证等功能.

我们写出来的 HTTP API 接口,如果没有文档,调用者根本就知道要传什么参数,返回什么数据. 而 OpenAPI Specification 就是这样一种标准,告诉我们应该怎样描述我们的接口,描述接口要传什么参数,返回什么数据.

OpenAPI Specification 最终是 JSON 或 YAML 数据格式表示, Specification 本身是告诉我们应该生成怎样的 JSON 或 YAML 数据.

主要描述请求的主机是什么,路径是什么,请求是 GET 还是 POST 等; 传参是 QUERY STRING 还是 BODY 等, 需要传什么头,返回什么头. 返回的数据是什么格式.

Swagger 工具

  • Swagger UI: 把 Swagger 标准的 JSON 数据,显示成友好可操作的 HTML 文档,方便调用者查看与调试接口.
  • Swagger Editor: 一个在线 YAML 编辑器,方便编写 Swagger 标准的接口描述数据,并能生成JSON格式的数据,同时能生成本地客户端,方便文档分发.
  • Sdk Generators: 根据 Swagger 标准的数据生成接口代码.

在 Rails 里使用 Swagger

上面三个工具,只用到 Swagger UI, 用它把写的接口描述JSON数据显示成友好的 HTML 界面.

ruby 中,如果用 grape 写 HTTP API,那配合 grape-swagger ,可以同步生成好文档,非常方便.

但个人习惯 Rails 了,觉得用 grape 要自己管理数据迁移脚本之类的,太麻烦了.

Ruby 里面的 Swagger 库我选 Swagger::Blocks ,纯 Ruby 实现,代码只有一个文件,700多行,简单. 其本身只是一个生成 Swagger 标准的 JSON 数据的 DSL . 调用 Swagger::Blocks.build_root_json 方法,最终生成的只是 json 字符串而已.与 Web 框架无关,只要把此 json 数据做为 response 数据返回即可.

下面创建一个 Rails 项目,对 Swagger 主要点进行演示,算是个人踩过坑后的一点心得.

1. 创建 Rails 演示项目

参考 Getting Started with Rails 创建一个 Rails 项目,并带有简单的 CURD 接口.

gem install rails 
rails new -B swagger_demo
cd swagger_demo
vi Gemfile # 编辑 Gemfile 文件,把第一行的 https://rubygems.org 替换成 https://gems.ruby-china.org
bundle install
bundle exec rails g scaffold Article title:string text:text # 生成 Article 的 model 和 controller
bundle exec rake db:create db:migrate # 创建数据库,运行迁移

代码: https://github.com/mangege/swagger_demo/tree/step1

2. 建立 swagger 初始文件

vi Gemfile # 添加 gem 'swagger-blocks' 到最后一行
bundle install
bundle exec rails g controller Apidocs index
vi app/controllers/apidocs_controller.rb # 复制此段内容 https://github.com/fotinakis/swagger-blocks#docs-controller ,还需要再编辑此文件内容,最终请看仓库代码
vi config/routes.rb # 删除掉 get 'apidocs/index' ,添加 resources :apidocs, only: [:index]
cd /tmp; git clone https://github.com/swagger-api/swagger-ui.git
cp -R /tmp/swagger-ui/dist ~/workspace/swagger_demo/public/ # 复制 swagger 的静态文件到 rails 项目的 public 目录下.
cd ~/workspace/swagger_demo/public/; mv dist swagger-ui # 重命名 dist 文件夹为 swagger-ui
vi public/swagger-ui/index.html # 替换 http://petstore.swagger.io/v2/swagger.json 为 /apidocs.json

打开浏览器,访问 http://localhost:3000/swagger-ui/ 即可.

代码: https://github.com/mangege/swagger_demo/tree/step2

3. 为 Article 接口添加文档

按照 Swagger::Blocks 的示例,一般是在 model 或 controller 文件里写文档.但这样有可能导致 model 或 controller 文件行数过长.

通过分析源码了解,我们随便建一个类也可以.所以我们在 app 目录下建立专门的 swagger 目录.

mkdir app/swagger
vi config/application.rb # 添加 config.autoload_paths << Rails.root.join('app/swagger') ,改了此文件记得重启 rails server

vi app/swagger/app/swagger/article_swagger.rb

添加以下内容

class ArticleSwagger
  include Swagger::Blocks

  swagger_schema :Article do
    key :required, [:id]
    property :id do
      key :type, :integer
    end
    property :title do
      key :type, :string
      key :description, '标题'
    end
    property :text do
      key :type, :string
      key :description, '正文'
    end
  end
end

vi app/controllers/apidocs_controller.rb # 在 SWAGGERED_CLASSES 添加 ArticleSwagger

vi app/swagger/articles_controller_swagger.rb

添加以下内容

class ArticlesControllerSwagger
  include Swagger::Blocks

  swagger_path '/articles' do
	operation :get do
	  key :description, 'article list'
	  key :operationId, 'articleIndex'
	  key :tags, [
		'article'
	  ]
	  response 200 do
		key :description, 'article response'
		schema do
		  key :type, :array
		  items do
			key :'$ref', :Article
		  end
		end
	  end
	  response :default do
		key :description, 'unexpected error'
		schema do
		  key :'$ref', :ErrorModel
		end
	  end
	end
	operation :post do
	  key :description, 'create article'
	  key :operationId, 'articleCreate'
	  key :tags, [
		'article'
	  ]
	  parameter do
		key :name, :article
		key :in, :body
		key :required, true
		schema do
		  key :'$ref', :Article
		end
	  end
	  response 200 do
		key :description, 'article response'
		schema do
		  key :'$ref', :Article
		end
	  end
	  response :default do
		key :description, 'unexpected error'
		schema do
		  key :'$ref', :ErrorModel
		end
	  end
	end
  end

  swagger_path '/articles/{id}' do
	operation :get do
	  key :description, 'article show'
	  key :operationId, 'articleShow'
	  key :tags, [
		'article'
	  ]
      parameter do
        key :name, :id
        key :in, :path
        key :required, true
        key :type, :integer
      end
	  response 200 do
		key :description, 'article response'
		schema do
          key :'$ref', :Article
		end
	  end
	  response :default do
		key :description, 'unexpected error'
		schema do
		  key :'$ref', :ErrorModel
		end
	  end
	end
	operation :patch do
	  key :description, 'article update'
	  key :operationId, 'articleUpdate'
	  key :tags, [
		'article'
	  ]
      parameter do
        key :name, :id
        key :in, :path
        key :required, true
        key :type, :integer
      end
	  parameter do
		key :name, :article
		key :in, :body
		key :required, true
		schema do
		  key :'$ref', :Article
		end
	  end
	  response 200 do
		key :description, 'article response'
		schema do
          key :'$ref', :Article
		end
	  end
	  response :default do
		key :description, 'unexpected error'
		schema do
		  key :'$ref', :ErrorModel
		end
	  end
	end
	operation :delete do
	  key :description, 'article destroy'
	  key :operationId, 'articleDestroy'
	  key :tags, [
		'article'
	  ]
      parameter do
        key :name, :id
        key :in, :path
        key :required, true
        key :type, :integer
      end
	  response 204 do
		schema do
		  key :type, :string
		end
	  end
	  response :default do
		key :description, 'unexpected error'
		schema do
		  key :'$ref', :ErrorModel
		end
	  end
	end
  end
end

vi app/swagger/error_model_swagger.rb

添加以下内容

module ErrorModelSwagger
  include Swagger::Blocks

  swagger_schema :ErrorModel do
    key :description, '错误定义'
    key :required, [:code, :message]
    property :code do
      key :type, :integer
      key :description, '错误代码. 401 没有登录, 403 没有权限, 422 表单数据有误'
    end
    property :message do
      key :type, :string
      key :description, '错误消息'
    end
    property :errors do
      key :type, :object
      key :description, '错误详情. 键为出错的属性名.值为出错信息,值是字符串数组.'
    end
  end

end

vi app/controllers/apidocs_controller.rb # 在 SWAGGERED_CLASSES 添加 ErrorModelSwagger 和 ArticlesControllerSwagger

vi app/controllers/application_controller.rb # 替换 exception 为 null_session ,注意,如果项目还有普通的web页面,不要把此改成 null_session ,而是新建一个 api_controller.rb 文件,在新建的文件里设置为 null_session ,然后所有的 api controller 都继承与它.

好了, Article 的 CRUD 操作的接口文档都已经编写完成,现在我们打开浏览器,访问 http://localhost:3000/swagger-ui/ ,即可通过 swagger ui 来阅读文档,并测试接口了.

代码: https://github.com/mangege/swagger_demo/tree/step3