RESTful风格的接口设计

Posted by Hsz on March 14, 2019

RESTful风格的接口设计

RESTful架构是目前最流行的一种互联网软件架构.它结构清晰,语义化,易于理解,扩展性好,所以国外知名的网站都早已采用,比如:Github,Google,Facebook,Twitter等,国内也越来越多的公司在做着这方面的尝试,但可能是由于国内环境的问题,不少所谓的RESTful风格的接口其实都是不得精髓的劣化版实现.写这篇文章就是为了避免写的RESTful风格接口也闹这种李逵变李鬼的笑话.

丑话说前面

首先广义上的RESTful是一个并没有严格规范化的东西,它是一个松散的API设计风格协议.因此本文为个人理解,如果有不同意的也不一定会进行修改.

为什么会有RESTful风格

REST这个词是Roy Thomas Fielding在他2000年的博士论文’Architectural Styles and the Design of Network-based Software Architectures‘中提出的

其中有一段话解释了这篇文章的初衷

本文研究计算机科学两大前沿–软件和网络–的交叉点.长期以来,软件研究主要关注软件设计的分类,设计方法的演化,很少客观地评估不同的设计选择对系统行为的影响.而相反地网络研究主要关注系统之间通信行为的细节,如何改进特定通信机制的表现,常常忽视了一个事实那就是改变应用程序的互动风格比改变互动协议对整体表现有更大的影响.我这篇文章的写作目的就是想在符合架构原理的前提下理解和评估以网络为基础的应用软件的架构设计,得到一个功能强,性能好,适宜通信的架构.

This dissertation explores a junction on the frontiers of two research disciplines in computer science: software and networking. Software research has long been concerned with the categorization of software designs and the development of design methodologies, but has rarely been able to objectively evaluate the impact of various design choices on system behavior. Networking research, in contrast, is focused on the details of generic communication behavior between systems and improving the performance of particular communication techniques, often ignoring the fact that changing the interaction style of an application can have more impact on performance than the communication protocols used for that interaction. My work is motivated by the desire to understand and evaluate the architectural design of network-based application software through principled use of architectural constraints, thereby obtaining the functional, performance, and social properties desired of an architecture.

简而言之,他希望将网络服务软件化.让接口有一个规范性接口风格约束,从而让服务之间可以互动.

如何理解RESTful

首先RESTful只是一种接口风格规范,可以拿python下的pep8做类比.它只是规范,他不涉及具体实现,也没有对错一说.

RESTRepresentational State Transfer(表现层状态转化)的缩写.

要理解RESTful架构,最好的方法就是去理解Representational State Transfer这个词组到底是什么意思,它的每一个词代表了什么涵义.如果你把这个名称搞懂了,也就不难体会REST是一种什么样的设计.

下面将解释RESTful语境下的几个名词,以方便后续讨论.

  • 资源

REST的名称’表现层状态转化’中省略了主语.”表现层”其实指的是”资源”(Resources)的”表现层”. 所谓”资源”就是一个信息实体.它可以是一段文本,一张图片,一首歌曲,一种服务,总之就是一个具体的实在的东西.在RESTful架构下,所有的操作都是围绕资源来的.

类比来说资源有点像面向对象编程范式下的对象.

  • 表现层

“资源”是一种信息实体,它可以有多种外在表现形式(描述形式).我们把”资源”具体呈现出来的形式叫做它的”表现层”(Representation)

比如描述一个资源可以用XML格式,JSON格式,protobuff甚至可以直接采用二进制格式.类比来说同一内容的图片可以是png格式,可以是gif格式也可以是jpg格式.他们用什么格式表现都不影响其内容.

  • 状态转化

资源往往不是静止不动的,它是一个有内部状态的实体,如何改变其内部状态也是需要规范的内容.还是那面相对象编程做对比,如果实例没有方法且属性都是只读的话那就不存在状态转化了,但只要属性并非只读,其内部的状态就可以通过方法或者改变字段中的值来改变.

约束

REST是作为互联网自身架构的抽象而出现的,其关键在于所定义的架构上的各种约束.只有满足这些约束才能称之为符合REST架构风格.REST的约束包括:

  • 客户端-服务器结构

    通过一个统一的接口来分开客户端和服务器,使得两者可以独立开发和演化.客户端的实现可以简化,而服务器可以更容易的满足可伸缩性的要求.

  • 无状态

    在不同的客户端请求之间,服务器并不保存客户端相关的上下文状态信息.任何客户端发出的每个请求都包含了服务器处理该请求所需的全部信息.

  • 可缓存

    客户端可以缓存服务器返回的响应结果.服务器可以定义响应结果的缓存设置.

  • 分层的系统

    在分层的系统中,可能有中间服务器来处理安全策略和缓存等相关问题以提高系统的可伸缩性.客户端并不需要了解中间的这些层次的细节.

  • 按需代码(可选)

    服务器可以通过传输可执行代码的方式来扩展或自定义客户端的行为.这是一个可选的约束.

  • 统一接口

    该约束是REST服务的基础,是客户端和服务器之间的桥梁.该约束又包含下面4个子约束.

    • 资源标识符

      每个资源都有各自的标识符.客户端在请求时需要指定该标识符.

    • 通过资源的表达来操纵资源

      客户端根据所得到的资源的表达中包含的信息来了解如何操纵资源,比如对资源进行修改或删除.

    • 自描述的消息

      每条消息都包含足够的信息来描述如何处理该消息.

    • 超媒体作为应用状态的引擎(HATEOAS)

      客户端通过服务器提供的超媒体内容中动态提供的动作来进行状态转换.对于不使用HATEOAS的REST服务,客户端和服务器的实现之间是紧密耦合的.客户端需要根据服务器提供的相关文档来了解所暴露的资源和对应的操作.当服务器发生了变化时,如修改了资源的URI,客户端也需要进行相应的修改.而使HATEOAS的REST服务中,客户端可以通过服务器提供的资源的表达来智能地发现可以执行的操作.当服务器发生了变化时,客户端并不需要做出修改,因为资源的URI和其他信息都是动态发现的.

使用HTTP协议实现RESTful

狭义上讲的RESTful接口指的是使用HTTP/HTTPS(最好是使用HTTPS)来实现的RESTful风格的接口服务.

其特点是:

  1. Web服务只是使用HTTP作为传输方式
  2. Web服务引入了资源的概念,使用路径(Endpoint)作为标识符指代资源集合下的特定资源
  3. Web服务可以根据需要使用多种表现层协议,当然最常用的还是JSON
  4. Web服务使用不同的HTTP方法来进行不同的操作,并且使用HTTP状态码来表示不同的结果.
  5. Web服务使用HATEOAS.在资源的表达中包含了链接信息.客户端可以根据链接来发现可以执行的动作.

Richardson提出了REST成熟度模型,该模型把REST服务按照成熟度划分成4个层次:

  • Level 0 – 满足上面第一点,本质上只是一种RPC(远程方法调用)而已,XML-RPC,SOAP,JSON-RPC,包括Grpc都属于此类.
  • Level 1 – 进一步满足上面的2,3两点.
  • Level 2 – 进一步满足上面的第4点.这也是多数号称RESTful的接口停留的层次.
  • Level 3 – 进一步满足第5点.

使用Endpoint来定位资源

路径描述资源实体:

https://api.example.com/animal/1

通常路径的的描述顺序代表一个包含关系,如上例中资源animal表示一个动物资源的整体资源,animal/1则表示动物资源下index为1的动物实例资源.

个人对资源的分类

可以看出资源和资源也是不一样的,用户总体可以是一个资源,单一一个用户也是一个资源,他们之间是从属关系.我个人将这种表示总体的资源称为容器资源.

描述动词

RESTful是操作资源的协议,在其语境下用户只能对资源做增删改查,因此是名词性的,很多时候我们可以当它是数据库的一层抽象.因此我们的URI中只会有名词不会有动词.那如果遇到动词怎么办呢?

例: 我们希望有一个RESTful的接口可以为我们实现计算加法的功能

一般来说这种工作更适合rpc做,但RESTful也不是做不了,只是路劲中不能有动词,那我们完全可以将动词转换为名词,参考python中的operator模块我们定义一个资源operator就是了.

GET https://api.example.com/operator/add?params=1,2

这个例子中我们将做加法这个动词转换成了加法这一个名词,而将它挂在了父资源operator下.

面相特定资源的描述动词

例: 我们希望用户资源下可以有follow操作,它可以关注感兴趣的其他用户.

这个例子就很业务,一个社交网站总会有这样的需求,比如github.那该如何实现呢?

我们将这个follow操作转换为两个用户下的子资源

  • follower描述这个用户被其他哪些用户关注

      Get https://api.example.com/user/1/follower
      Put https://api.example.com/user/1/follower
      Del https://api.example.com/user/1/follower
    
  • followed描述这个用户关注了哪些别的用户

      Get https://api.example.com/user/1/followed
      Put https://api.example.com/user/1/followed
      Del https://api.example.com/user/1/followed
    

之后业务上要实现follow这个操作只要修改这两个资源的状态即可.

那一般点击关注是一个原子操作,A关注了B,那A的关注列表里就应该多出B,B的被关注列表中也该多出A,另一种解决方式是和上面的加法一样将这种动作作为服务资源.

GET https://api.example.com/user/operator/follow?follower=A&followed=B

API的版本管理

版本控制本来就是一件糟心的事儿,在RESTful语境下,常见的版本控制方法有两种:

  1. 在请求和响应的HTTP头信息的Accept字段中带上版本信息.

    这种方式的好处是颗粒度细,支持渐进式的服务更新,但缺点也很明显,调用方通常不关心版本,他们关心的是调用服务的稳定性和功能是否按预期实现.服务版本往往也是在接口变动的情况下才会有大版本的更新,因此这种方式我几乎从没见过.但在返回结果的头部加上版本信息也确实是一个比较好的实现,这样有利于收集bug.

     Accept: vnd.example-com.foo+json; version=1.0
    
  2. 在uri的第一段使用版本号.

    这种方式相当于将不同版本的服务也作为一种资源来看待.我看到多数都是这样实现的,比如github.这种方式的好处就是不同的接口可以严格区分,而且维护相对更加简单

     https://api.example.com/v1/user
    

使用HTTP动词定义状态转化

RESTful使用http的默认方法定义状态转化的方法,具体来讲

http方法 说明
GET 从服务器取出资源(一项或多项)
POST 在服务器新建一个资源
PUT 在服务器更新资源(客户端提供改变后的完整资源)
PATCH 在服务器更新资源(客户端提供改变的属性和属性对应的值)
DELETE 从服务器删除资源
HEAD 获取资源的元数据。
OPTIONS 获取信息,关于资源的哪些属性是客户端可以改变的。

在http中实现表现层

表现层本身只是一个协议,服务端和客户端相当于做了一个编码解码的工作,以或许需要的信息.用http实现表现层,我们可以用的东西有:

  • http头信息
  • http的body信息
  • http状态码
  • url参数

使用json作为表现层协议

RESTful发展至今,将json放在http请求的body中作为表现层协议已经是一个默认选项,但我们还是要来在http头部将其申明出来,毕竟默认不代表严格限制.

Accept: application/json
Content-Type: application/json

在一些特殊场景下可能下面的表现层协议更符合要求:

  • xml,不得不说xml确实表现力更强,历史更悠久,但也更大,解析更慢.在一些比较古老的系统可能还是使用xml,在一些需要更强表现力不太在意响应速度和传输

      Accept: application/xml
      Content-Type: application/xml
    
  • protobuf,目前流行的表现层协议中既小解析又块的一种,不过必须事先定义数据模式(schema),在追求性能的场景下使用,内网环境下较为常用

      Accept: application/protobuf
      Content-Type: application/protobuf
    
  • msgpack,比json更小更有效率的表现层协议,性能优越而且不需要定义数据模式,在追求性能的场景完全可以替代json

      Accept: application/msgpack
      Content-Type: application/msgpack
    

从容器资源中搜索

通常我们要求容器资源的Get方法有搜索能力,比如https://api.example.com/v1/user要有可以通过url参数搜索出一个或多个https://api.example.com/v1/user/:id的能力,下面是一些常见的参数.

参数 说明
?limit=10 指定返回记录的数量
?offset=10 指定返回记录的开始位置。
?page=2&per_page=100 指定第几页,以及每页的记录数。
?sortby=name&order=asc 指定返回结果按照哪个属性排序,以及排序顺序。
?animal_type_id=1 指定筛选条件

使用HTTP状态码定义异常

接口的异常在表现层中往往没有明确的规范,下面我们来看看几种常见的:

  • Github (use http status)

      {
      "message": "Validation Failed",
      "errors": [
          {
          "resource": "Issue",
          "field": "title",
          "code": "missing_field"
          }
      ]
      }
    
  • Google (use http status)

      {
          "error": {
              "errors": [
              {
                  "domain": "global",
                  "reason": "insufficientFilePermissions",
                  "message": "The user does not have sufficient permissions for file {fileId}."
              }
              ],
              "code": 403,
              "message": "The user does not have sufficient permissions for file {fileId}."
          }
      }
    
  • Facebook (use http status)

      {
          "error": {
              "message": "Message describing the error", 
              "type": "OAuthException",
              "code": 190,
              "error_subcode": 460,
              "error_user_title": "A title",
              "error_user_msg": "A message",
              "fbtrace_id": "EJplcsCHuLu"
          }
      }
    
  • Twitter (use http status)

      {
          "errors": [
              {
              "message": "Sorry, that page does not exist",
              "code": 34
              }
          ]
      }
    
  • Twilio (use http status)

      {
          "code": 21211,
          "message": "The 'To' number 5551234567 is not a valid phone number.",
          "more_info": "https://www.twilio.com/docs/errors/21211",
          "status": 400
      }
    

观察这些结构可以发现它们都有一些共同的地方:

  1. 都利用了Http状态码
  2. 有些返回了业务错误码
  3. 都提供了给用户看的错误提示信息
  4. 有些提供了给开发者看的错误信息

那可以总结出RESTful风格接口对于错误的处理基本原则是复用http状态码和用户优先.而方便开发者的业务错误码其实并不是关键.

这里是全部的http状态码而这些错误类型中,我们最常用的是:

  • 400 Bad Request

    由于包含语法错误,当前请求无法被服务器理解.除非进行修改否则客户端不应该重复提交这个请求.通常在请求参数不合法或格式错误的时候可以返回这个状态码.

  • 401 Unauthorized 当前请求需要用户验证.通常在没有登录的状态下访问一些受保护的API时会用到这个状态码.

  • 403 Forbidden

    服务器已经理解请求,但是拒绝执行它.与401响应不同的是身份验证并不能提供任何帮助.通常在没有权限操作资源时(如修改/删除一个不属于该用户的资源时)会用到这个状态码.

  • 404 Not Found

    请求失败,请求所希望得到的资源未被在服务器上发现.通常在找不到资源时返回这个状态码.

尽管我们可以通过Http状态码来表示错误的类型,但在实际应用中如果仅仅使用Http状态码的话,我们会发现其颗粒度过大,这并不能很好的提示用户应该后续如何操作.我们就需要对错误进行细化,并告知应该如何排查和处理.这种时候我们就会知道如果错误是一种类型就好了,类型本身是信息,还可以已定义其message,甚至可以追踪其错误栈.

我觉得在实际项目中可以参考sanic的异常设计为每个异常定义一个类型.他们可以有继承关系,而一旦出现异常,则可以将异常类型的名字和异常信息都作为返回值传出去.这样可读性更好而且也便于修复问题.

使用超媒体(Hypermedia)描述资源间的关联

按照Richardson提出了REST成熟度模型,这是RESTful实现的最高等级,然而多数企业并没有按这个做,同时即便有做也并没有起到什么根本的变化,更不用说什么最佳实践了.

首先解释下什么是超媒体,所谓超媒体可以简单理解为超链接.比如我们的网页可以通过a标签跳转到相关的页面.超媒体的好处是赋予了资源描述关系的能力.但具体是什么关系却又需要其他规范来支持.这确实可以带来一种编写客户端时的新思路–让客户端根据超媒体信息自己寻找需要的资源.但彻底的这么做要付出的代价也是巨大的

  1. 更多的代码量

    毫无疑问这会加重客户端和服务端两头的工作量.在没有约定的情况下客户端恐怕连如何抽取需要的信息都成问题.而如果借助一些基于语义网技术的工具如JSON-LD,JSONAPI这样的技术,那接口数据将变得相当繁杂,这一样会增加代码量而且可读性会很差.

  2. 更多的数据交互

    由于要让客户端自己寻找需要的数据,那也就意味着客户端无法一次找到想要的数据,而且查找的过程中每次的跳转都是一次数据通信.复杂的话甚至需要客户端在本地建立和同步一张资源间的关系图,通过先在本地遍历找到要用的资源,然后再请求的方式才能减少这种交互.

这就像工人只是要一把锤子,但供应商却认为应该把锤子的制作方法一起告诉工人一样.

但是HATEOAS是不是就完全不可取呢?也不是,毕竟它的表现力更好,描述资源可以多一层关系的维度.就像关系数据库中的外键一样,是很好的补充.

例: 重新构建用户关注清单

上面的例子中我们已经构建过一回用户关注关系,它是从动作这个角度,这次我们使用Hypermedia重新设计这个实现

我们可以定义两个资源:

  • 用户
https://api.example.com/v1/user/1

response:

{
    ...,
    "follower":{
        "source": "https://api.example.com/v1/user-relation/follow",
        "args":{
            "followed-id": 1
        }
    },
    "followed":{
        "source": "https://api.example.com/v1/user-relation/follow",
        "args":{
            "follower-id": 1
        }
    }
}
  • 用户关系

在用户关系资源容器一层提供一个搜索的方法:

Get https://api.example.com/v1/user-relation/follow?followed-id=xx&follower-id=yy

然后每个follow关系像下面这样表示

https://api.example.com/v1/user-relation/follow/:id

response:

{
    ...
    "source": "https://api.example.com/v1/user",
    "follower-id": 1,
    "followed-id": 2,
}

这样我们的接口中利用关系就实现了关注清单,如果一个用户关注了另一个用户,那只要在用户关系资源中加一条就好,用户间的关系就和用户本身解耦了.而像关注列表这种信息也会延后获取.这相当于给出了搜索方法而非搜索结果.

使用OpenAPI描述你的接口

即便RESTful接口已经很容易理解,但实现细节往往依然需要有文档.目前最流行的用于描述http服务接口的协议是OpenAPI,目前已经到了v3.1版本(满足这个jsonschema),但实际上应用最广的还是v2版本(满足这个jsonschema)本文也将使用v2版本来介绍 .OpenAPI使用json或者yaml格式描述,可以完整的描述接口的各种信息.其形式类似:

basePath: /
definitions:
  user.User:
    properties:
      ID:
        type: integer
      Name:
        type: string
    type: object
  usernamespace.UserCreateQuery:
    properties:
      Name:
        type: string
    type: object
host: localhost
info:
  contact:
    email: hsz1273327@gmail.com
    name: hsz
  description: 测试
  license:
    name: Apache 2.0
    url: http://www.apache.org/licenses/LICENSE-2.0.html
  title: tp_go_gin_complex
  version: "1.0"
paths:
  /v1/api/user:
    get:
      produces:
      - application/json
      responses:
        "200":
          description: OK
          schema:
            type: string
      summary: 获取用户列表信息
      tags:
      - user
    post:
      consumes:
      - application/json
      parameters:
      - description: 用户名
        in: body
        name: name
        required: true
        schema:
          $ref: '#/definitions/usernamespace.UserCreateQuery'
      produces:
      - application/json
      responses:
        "200":
          description: '{"Name":"1234","ID":1}'
          schema:
            $ref: '#/definitions/user.User'
      summary: 创建新用户
      tags:
      - user
swagger: "2.0"

但是依然对一般人而言可读性不好.而Swagger则是一套专门用于将OPENAPI协议转化为文档服务,且提供测试执行功能的工具集.多数编程语言多数http开发框架都会要么官方提供要么第三方提供swagger插件,同时提供生成openapi描述的文件.在我们提供了RESTful接口之外,我们也应该提供Swagger页面让对接方可以更加简单的调试和对接.

OpenAPI支持的数据类型

OpenAPI使用type字段申明字段类型,同时提供一个可选的format字段用于补充其形式,OpenAPI的类型系统是在JSON-Schema类型系统的基础上扩展得来的,扩展的部分就是添加了类型file用于描述文件类型.

format字段是没有限制的string类型,标准中给出了如下默认类型:

通用名称 数据类型 数据格式 描述
integer integer int32 signed 32 bits,32位有符号数
long integer int64 signed 64 bits,64位有符号数
float number float
double number double
string string
byte string byte base64 encoded characters,base64编码字符
binary string binary any sequence of octets,8进制字符串
boolean boolean
date string date 参考 RFC3339 - full-date
dateTime string date-time 参考 RFC3339 - date-time
password string password UI提示隐藏输入

OpenAPI根对象

OpenAPI给定的根对象中比较重要的有如下几个:

字段名 类型 是否必须 描述
swagger string true 申明使用的openapi版本
host string false api所在的主机,可以是host或者ip+端口
basePath string false api接口的根路径,比如/比如/api/
schemes array<string> false 使用的协议,只能从http,https, ws, wss. 四个枚举中选择,但可以是多选
tags array<object> false api的标签列表,具体如何展现取决于工具
consumes array<string> false 声明api全局可以消费的MIME type类型(也就是上面说的表现层协议)
security array<object> false 声明api全局的安全设置,这个后面讲
produces array<string> false 声明api全局可以生产(返回的应答)的MIME type类型(也就是上面说的表现层协议)
definitions object false 预定义的数据模式,可以在各处使用$ref引用,和jsonschema中差不多
parameters object false 声明预定义的参数设置项
securityDefinitions object false 声明预定义的安全设置项
responses object false 声明预定义的响应设置项
info object true api接口的元信息,这个后面细讲
paths object true 描述接口信息的主体,这个后面讲

info对象

info对象用于描述api的元信息,也就是作者,标题,开源协议什么的

字段名 类型 是否必须 描述
title string true 应用名
version string true api版本
description string false api服务描述
termsOfService string false api服务条款
contact object false 联系信息,只有3个字段name–作者;url–联系人网址;email–联系人邮箱
license object false 协议信息,只有2个字段name–协议名,必须;url–协议网址

paths对象

paths对象用于描述接口信息,它是一个key为/<path>,value为object形式的对象,每个对象又以http方法为key,一个object为value描述这个路径下使用这个http方法后的预期行为和接口描述,我们姑且将之称作操作对象.

操作对象大致分为如下几个部分

元信息字段,用于描述和归并操作

字段名 类型 是否必须 描述
tags array<string> false 接口归类标签
summary string false 接口简介
description string false 接口详细介绍
operationId string false 接口唯一标识
schemes array<string> false 使用的协议,只能从http,https, ws, wss. 四个枚举中选择,但可以是多选
deprecated boolean false 接口是否已经过时废弃

认证信息描述字段

字段名 类型 是否必须 描述
security array<object> false 声明api的验证信息,通常先在根信息securityDefinitions中定义结构,然后再引用

security对象用于描述验证信息

字段名 类型 是否必须 描述
type string true 验证类型,支持的只有basic,apiKeyoauth2
description string false 描述文本
name string apiKey方式需要 在header或者query parameter中使用的字段
in string apiKey方式需要 apikey存放的位置,支持的只有queryheader
flow string oauth2方式需要 oauth2验证流程,支持的只有implicit,password,applicationaccessCode
authorizationUrl string oauth2方式的implicit,accessCode流程需要 验证服务器url
tokenUrl string oauth2方式的password,application,accessCode流程需要 token服务器url
scopes object oauth2方式需要 声明允许的范围

我们比较常用jwt术语apikey,像下面这样定义和使用:

securityDefinitions:
  Bearer:
    type: apiKey
    name: Authorization
    in: header

paths:
  /:
    get:
      security:
        - Bearer: []

请求描述字段

字段名 类型 是否必须 描述
consumes array<string> false 声明api全局可以消费的MIME type类型(也就是上面说的表现层协议)
parameters array<object> false 声明参数形式

consumes前面已经介绍过,主要还是parameters.parameters管的是请求参数,每个parameter相当于描述一个字段,下面是常用的描述字段:

字段名 类型 是否必须 描述
name string true 参数名
in string true 参数位置
description string false 介绍描述
required boolean true 是否必须

其中in字段可选的有

  • query–url的?后面
  • header–http头里
  • path–动态url中
  • formDatabody中且content-type为application/x-www-form-urlencodedmultipart/form-data
  • body–httpbody里

如果in字段为body,则还需要有字段schema用于描述body中的数据模式,这个schema就是一个满足jsonschema规范的模式.

如果in字段为其他,则还可以使用jsonschema中规定的字段直接定义这个参数的模式,比如type,enum等.

响应描述字段

字段名 类型 是否必须 描述
produces array<string> false 声明api全局可以生产(返回的应答)的MIME type类型(也就是上面说的表现层协议)
responses object false 声明返回值的形式

produces前面也已经介绍过,主要还是定义responses,responses是一个key为default或者http状态码字符串,value为描述对象的对象,而每个描述对象可以有如下字段

字段名 类型 是否必须 描述
description string true 描述文本
schema object false 描述返回的schema,符合jsonschema标准
headers object false 描述http头字段和值的对象
examples object false 返回样例

使用SSE扩展RESTful接口

通常来说RESTful接口都是请求响应模式,也不会用长连接,但在不少场景下订阅推送是刚需(比如聊天室应用等),如果纯使用RESTful接口就必须使用轮询,而轮询我们都知道性能差资源占用高.当然了我们也可以使用websocket,但通常来说websocket技术难以语义化.一个折衷的方式就是使用SSE(Server-sent Events)技术.sse本质上还是http服务,只是它在请求被服务器接受后并不会返回完整响应也不会断开连接,而是持续推送消息.

sseHTML5规范的一个组成部分,其定义的规范有:

  1. 规定服务器接收一个GET请求以建立连接.
  2. 服务器端的响应的内容类型是text/event-stream
  3. 响应内容为纯文本,每个事件数据和可选的事件id及事件类型3部分组成,每个部分使用一次换行符\r\n分隔

     event: myevent
     data: third event
     id: 101
    
  4. 事件间使用两个连续的换行符\r\n分隔

     data: first event
     
     data: second event
     id: 100
    

同过SSE我们可以很容易的扩展我们的RESTful接口,同时在语义上也没有什么歧义.我们可以定义资源组event全部使用sse实现,其中每项为一个资源容器,用于订阅其中的全部符合筛选条件的资源提供的事件,而每个资源容器下的单个资源也可以单独订阅监听.而创建资源也可以通过访问资源容器的POST方法.打个比方:

我们有个资源容器是定时器timer,它可以每隔1s返回计时器还剩下的时间.我们可以通过POST方法访问/event/timer创建一个定时器,它会返回一个id,然后我们可以get方法监听/event/timer/<id>.