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做类比.它只是规范,他不涉及具体实现,也没有对错一说.
REST
是Representational 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
风格的接口服务.
其特点是:
- Web服务只是使用HTTP作为传输方式
- Web服务引入了资源的概念,使用路径(Endpoint)作为标识符指代资源集合下的特定资源
- Web服务可以根据需要使用多种表现层协议,当然最常用的还是JSON
- Web服务使用不同的HTTP方法来进行不同的操作,并且使用HTTP状态码来表示不同的结果.
- 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语境下,常见的版本控制方法有两种:
-
在请求和响应的HTTP头信息的Accept字段中带上版本信息.
这种方式的好处是颗粒度细,支持渐进式的服务更新,但缺点也很明显,调用方通常不关心版本,他们关心的是调用服务的稳定性和功能是否按预期实现.服务版本往往也是在接口变动的情况下才会有大版本的更新,因此这种方式我几乎从没见过.但在返回结果的头部加上版本信息也确实是一个比较好的实现,这样有利于收集bug.
Accept: vnd.example-com.foo+json; version=1.0
-
在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更小更有效率的表现层协议,性能优越而且不需要定义数据模式,在追求性能的场景完全可以替代jsonAccept: 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 }
观察这些结构可以发现它们都有一些共同的地方:
- 都利用了Http状态码
- 有些返回了业务错误码
- 都提供了给用户看的错误提示信息
- 有些提供了给开发者看的错误信息
那可以总结出RESTful风格接口对于错误的处理基本原则是复用http状态码和用户优先.而方便开发者的业务错误码其实并不是关键.
这里是全部的http状态码而这些错误类型中,我们最常用的是:
-
400 Bad Request
由于包含语法错误,当前请求无法被服务器理解.除非进行修改否则客户端不应该重复提交这个请求.通常在请求参数不合法或格式错误的时候可以返回这个状态码.
-
401 Unauthorized
当前请求需要用户验证.通常在没有登录的状态下访问一些受保护的API时会用到这个状态码. -
403 Forbidden
服务器已经理解请求,但是拒绝执行它.与401响应不同的是身份验证并不能提供任何帮助.通常在没有权限操作资源时(如修改/删除一个不属于该用户的资源时)会用到这个状态码.
-
404 Not Found
请求失败,请求所希望得到的资源未被在服务器上发现.通常在找不到资源时返回这个状态码.
尽管我们可以通过Http状态码来表示错误的类型,但在实际应用中如果仅仅使用Http状态码的话,我们会发现其颗粒度过大,这并不能很好的提示用户应该后续如何操作.我们就需要对错误进行细化,并告知应该如何排查和处理.这种时候我们就会知道如果错误是一种类型就好了,类型本身是信息,还可以已定义其message,甚至可以追踪其错误栈.
我觉得在实际项目中可以参考sanic的异常设计为每个异常定义一个类型.他们可以有继承关系,而一旦出现异常,则可以将异常类型的名字和异常信息都作为返回值传出去.这样可读性更好而且也便于修复问题.
使用超媒体(Hypermedia)描述资源间的关联
按照Richardson提出了REST成熟度模型,这是RESTful实现的最高等级,然而多数企业并没有按这个做,同时即便有做也并没有起到什么根本的变化,更不用说什么最佳实践了.
首先解释下什么是超媒体,所谓超媒体可以简单理解为超链接.比如我们的网页可以通过a
标签跳转到相关的页面.超媒体的好处是赋予了资源描述关系的能力.但具体是什么关系却又需要其他规范来支持.这确实可以带来一种编写客户端时的新思路–让客户端根据超媒体信息自己寻找需要的资源.但彻底的这么做要付出的代价也是巨大的
-
更多的代码量
毫无疑问这会加重客户端和服务端两头的工作量.在没有约定的情况下客户端恐怕连如何抽取需要的信息都成问题.而如果借助一些基于语义网技术的工具如JSON-LD,JSONAPI这样的技术,那接口数据将变得相当繁杂,这一样会增加代码量而且可读性会很差.
-
更多的数据交互
由于要让客户端自己寻找需要的数据,那也就意味着客户端无法一次找到想要的数据,而且查找的过程中每次的跳转都是一次数据通信.复杂的话甚至需要客户端在本地建立和同步一张资源间的关系图,通过先在本地遍历找到要用的资源,然后再请求的方式才能减少这种交互.
这就像工人只是要一把锤子,但供应商却认为应该把锤子的制作方法一起告诉工人一样.
但是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 ,apiKey 和oauth2 |
description |
string |
false | 描述文本 |
name |
string |
apiKey 方式需要 |
在header或者query parameter中使用的字段 |
in |
string |
apiKey 方式需要 |
apikey存放的位置,支持的只有query 和header |
flow |
string |
oauth2 方式需要 |
oauth2 验证流程,支持的只有implicit ,password ,application 和accessCode |
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中formData
body中且content-type为application/x-www-form-urlencoded
或multipart/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服务,只是它在请求被服务器接受后并不会返回完整响应也不会断开连接,而是持续推送消息.
sse
是HTML5
规范的一个组成部分,其定义的规范有:
- 规定服务器接收一个
GET
请求以建立连接. - 服务器端的响应的内容类型是
text/event-stream
-
响应内容为纯文本,每个事件数据和可选的事件id及事件类型3部分组成,每个部分使用一次换行符
\r\n
分隔event: myevent data: third event id: 101
-
事件间使用两个连续的换行符
\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>
.