Restful
REST即表述性状态传递(英文:Representational State Transfer,简称REST)是Roy Fielding博士在2000年他的博士论文中提出来的一种网络应用程序的设计风格和开发方式。
用一句人话来说:
URI定位资源,用HTTP动词(GET, POST, PUT, DELETE)来描述操作
Level 0 - 面向前台
刚刚我去咖啡厅点了一杯拿铁,这个过程我们可以用文字描述告诉前台。
{
"addOrder" :{
"orderName" : "生椰拿铁"
}
}
前台看到这段问题,就知道我们想要点一杯生椰拿铁了,然后给我们返回这么一串回复
{
"orderId" : "001"
}
这就是订单编号,等下咖啡师制作完成后,就会显示让订单号 001 的客户去取餐了。
再假如,在下完单付款的时候,我想到我还有张会员卡,我想查查这个卡还有多少余额,那我们再向前台发起一个查询请求
{
"queryBalance" : "AA0001"
}
查询一下卡号AA0001的余额。
{
"balacne": 0.00
}
好的,一分钱都没有,那我不要了,那前台就要取消刚刚那笔订单
{
"deleteOrder": {
"orderId" : "001"
}
}
Level 1 - 面向资源
这家咖啡厅越做越大,人越来越多了,那么单单靠前台的显然是不行的,那么就要对这些资源进行分工,每个资源都有专人负责,我们直接面向资源进行操作就好。
好的,我们再来到这个店,我们还是点一杯生椰拿铁
/order
{
"addOrder" :{
"orderName" : "生椰拿铁"
}
}
/order 表示了一种资源,这个请求我们找专门负责订单的人点了一杯生椰拿铁。他会帮我们处理订单的所有操作,包括创建订单,修改订单,删除订单等。
然后他还是会返回我们一个订单编号
{
"orderId" : "001"
}
下面我们我们就要付钱了,还是先查下会员卡有没有钱
/card
{
"queryBalance" : "AA0001"
}
那我们会找负责会员卡资源的人帮我们查询啦
{
"balacne": 100.00
}
这次我们有钱了,但是我们不想和生椰拿铁了,我想喝经典拿铁,那我找负责订单的人帮我们修改订单
/order
{
"updateOrder" : {
"orderId" : "001",
"orderName" : "经典拿铁"
}
}
算了,还是不喝了
/order
{
"deleteOrder": {
"orderId" : "001"
}
}
Level 2 - 打上标签
接下来,店主还想继续优化他的咖啡厅的服务流程,他发现负责处理订单的员工,每次都要去订单内容里面看是新增订单还是删除订单,还是其他的什么操作,十分不方便,于是规定,所有新增资源的请求,都在请求上面写上大大的‘POST’,表示这是一笔新增资源的请求”
“其他种类的请求,比如查询类的,用‘GET’表示,删除类的,用‘DELETE’表示”
“还有修改类的,修改分为两种,第一种,如果这个修改,无论发送多少次,最后一次修改后的资源,总是和第一次修改后的一样,比如将拿铁改为猫屎,那么用‘PUT’表示;第二种,如果这个修改,每次修改都会让这个资源和前一次的不一样,比如是加一杯咖啡,那么这种请求用‘PATCH’或者‘POST’表示”。
我们继续上面的流程。
POST /order
{
"orderName" : "生椰拿铁"
}
请求内容就很简洁, 负责订单的员工一看就知道是新增
返回内容还是一样
{
"orderId": "001"
}
然后还是查询会员卡
GET /card
{
"cardId": "AA0001"
}
这一步还可以继续优化
GET /card/AA0001
然后修改订单
PUT /order
{
"orderId" : "001",
"orderName" : "经典拿铁"
}
再取消订单
DELETE /order
{
"orderId":"001"
}
DELETE /order/001
Level 3 - 完美服务
忽然有一天,有个顾客抱怨说,他买了咖啡后,不知道要怎么取消订单,咖啡厅一个店员回了一句,你不会看我们的宣传单吗,上面不写着
DELETE /order/${orderId}
顾客反问道,谁会去看那个啊,店员不服,又说到,你瞎了啊你…据说后面两人吵着吵着还打了起来…”
“有了这次教训,店长决定,顾客下了单之后,不仅给他们返回订单的编号,还给顾客返回所有可以对这个订单做的操作,比如告诉用户如何删除订单。现在,我们还是发出请求,请求内容和上一次一样”
POST /orders
{
"orderName": "生椰拿铁"
}
但是我们这次返回多一些信息
{
"orderId": "001",
"link": {
"rel": "cancel",
"url": "/order/001"
}
}
这次返回时多了一项link信息,里面包含了一个rel属性和url属性,rel是relationship的意思,这里的关系是cancel,url则告诉你如何执行这个cancel操作,接着你就可以这样子来取消订单啦”
DELETE /order/001
Level0和Level1最大的区别,就是Level1拥有了Restful的第一个特征——面向资源,这对构建可伸缩、分布式的架构是至关重要的。同时,如果把Level0的数据格式换成Xml,那么其实就是SOAP,SOAP的特点是关注行为和处理,和面向资源的RESTful有很大的不同。
Level0和Level1,其实都很挫,他们都只是把HTTP当做一个传输的通道,没有把HTTP当做一种传输协议。
Level2,真正将HTTP作为了一种传输协议,最直观的一点就是Level2使用了HTTP动词,GET/PUT/POST/DELETE/PATCH…,这些都是HTTP的规范,规范的作用自然是重大的,用户看到一个POST请求,就知道它不是幂等的,使用时要小心,看到PUT,就知道他是幂等的,调用多几次都不会造成问题,当然,这些的前提都是API的设计者和开发者也遵循这一套规范,确保自己提供的PUT接口是幂等的。
evel3,关于这一层,有一个古怪的名词,叫HATEOAS,中文翻译为“将超媒体格式作为应用状态的引擎”,核心思想就是每个资源都有它的状态,不同状态下,可对它进行的操作不一样。理解了这一层,再来看看REST的全称,Representational State Transfer,中文翻译为“表述性状态转移”,是不是好理解多了?
Level3的Restful API,给使用者带来了很大的便利,使用者只需要知道如何获取资源的入口,之后的每个URI都可以通过请求获得,无法获得就说明无法执行那个请求。
现在绝大多数的RESTful接口都做到了Level2的层次,做到Level3的比较少。当然,这个模型并不是一种规范,只是用来理解Restful的工具。所以,做到了Level2,也就是面向资源和使用Http动词,就已经很Restful了。Restful本身也不是一种规范,我比较倾向于用“风格"来形容它。
RESTful6大原则
REST之父Roy Fielding在论文中阐述REST架构的6大原则
1. C-S架构
数据的存储在Server端,Client端只需使用就行。两端彻底分离的好处使client端代码的可移植性变强,Server端的拓展性变强。两端单独开发,互不干扰.
2. 无状态
http请求本身就是无状态的,基于C-S架构,客户端的每一次请求带有充分的信息能够让服务端识别。请求所需的一些信息都包含在URL的查询参数、header、body,服务端能够根据请求的各种参数,无需保存客户端的状态,将响应正确返回给客户端。无状态的特征大大提高的服务端的健壮性和可拓展性。
当然这总无状态性的约束也是有缺点的,客户端的每一次请求都必须带上相同重复的信息确定自己的身份和状态(这也是必须的),造成传输数据的冗余性,但这种确定对于性能和使用来说,几乎是忽略不计的。
3.统一的接口
这个才是REST架构的核心,统一的接口对于RESTful服务非常重要。客户端只需要关注实现接口就可以,接口的可读性加强,使用人员方便调用。
4. 一致的数据格式
服务端返回的数据格式要么是XML,要么是Json(获取数据),或者直接返回状态码,有兴趣的可以看看博客园的开放平台的操作数据的api,post、put、patch都是返回的一个状态码 。
自我描述的信息,每项数据应该是可以自我描述的,方便代码去处理和解析其中的内容。比如通过HTTP返回的数据里面有 [MIME type ]信息,我们从MIME type里面可以知道数据的具体格式,是图片,视频还是JSON,客户端通过body内容、查询串参数、请求头和URI(资源名称)来传送状态。服务端通过body内容,响应码和响应头传送状态给客户端。这项技术被称为超媒体(或超文本链接)。
除了上述内容外,HATEOS也意味着,必要的时候链接也可被包含在返回的body(或头部)中,以提供URI来检索对象本身或关联对象。下文将对此进行更详细的阐述。
如请求一条微博信息,服务端响应信息应该包含这条微博相关的其他URL,客户端可以进一步利用这些URL发起请求获取感兴趣的信息,再如分页可以从第一页的返回数据中获取下一页的URT也是基于这个原理。
5. 可缓存
在万维网上,客户端可以缓存页面的响应内容。因此响应都应隐式或显式的定义为可缓存的,若不可缓存则要避免客户端在多次请求后用旧数据或脏数据来响应。管理得当的缓存会部分地或完全地除去客户端和服务端之间的交互,进一步改善性能和延展性。
6.按需编码、可定制代码
服务端可选择临时给客户端下发一些功能代码让客户端来执行,从而定制和扩展客户端的某些功能。比如服务端可以返回一些 Javascript 代码让客户端执行,去实现某些特定的功能。 提示:REST架构中的设计准则中,只有按需编码为可选项。如果某个服务违反了其他任意一项准则,严格意思上不能称之为RESTful风格。
实践
1.版本
如github开放平台
https://developer.github.com/v3/
就是将版本放在url,简洁明了,这个只有用了才知道,一般的项目加版本v1,v2,v3
有的会将版本号放在header里面,但是不如url直接了当。
2.参数命名规范
query parameter可以采用驼峰命名法,也可以采用下划线命名的方式,推荐采用下划线命名的方式,据说后者比前者的识别度要高,可能是用的人多了吧,因人而异,因团队规范而异吧。
https://example.com/api/users/today_login 获取今天登陆的用户
3.url命名规范
API 命名应该采用约定俗成的方式,保持简洁明了。
在RESTful架构中,每个url代表一种资源所以url中不能有动词,只能有名词,并且名词中也应该使用复数。
实现者应使用相应的Http动词GET、POST、PUT、PATCH、DELETE、HEAD来操作这些资源即可
不规范的的url,冗余没有意义,形式不固定,不同的开发者还需要了解文档才能调用。
https://example.com/api/getallUsers GET 获取所有用户
https://example.com/api/getuser/1 GET 获取标识为1用户信息
https://example.com/api/user/delete/1 GET/POST 删除标识为1用户信息
https://example.com/api/updateUser/1 POST 更新标识为1用户信息
https://example.com/api/User/add POST 添加新的用户
https://example.com/api/users GET 获取所有用户信息
https://example.com/api/users/1 GET 获取标识为1用户信息
https://example.com/api/users/1 DELETE 删除标识为1用户信息
https://example.com/api/users/1 Patch 更新标识为1用户部分信息,包含在body中
https://example.com/api/users POST 添加新的用户
4. 统一返回数据格式
于合法的请求应该统一返回数据格式,这里演示的是json
- code——包含一个整数类型的HTTP响应状态码。
- status——包含文本:”success”,”fail”或”error”。HTTP状态响应码在500-599之间为”fail”,在400-499之间为”error”,其它均为”success”(例如:响应状态码为1XX、2XX和3XX)。这个根据实际情况其实是可要可不要的。
- message——当状态值为”fail”和”error”时有效,用于显示错误信息。参照国际化(il8n)标准,它可以包含信息号或者编码,可以只包含其中一个,或者同时包含并用分隔符隔开。
- data——包含响应的body。当状态值为”fail”或”error”时,data仅包含错误原因或异常名称、或者null也是可以的
{
"code": 200,
"message": "success",
"data": {
"orderId": "001",
"orderName": "生椰拿铁"
}
}
{
"code": 401,
"message": "error message",
"data": null
}
5. http状态码
HTTP状态码本身就有足够的含义,根据http status code就可以知道删除、添加、修改等是否成功。
服务端向用户返回这些状态码并不是一个强制性的约束。简单点说你可以指定这些状态,但是不是强制的。常用HTTP状态码对照表 HTTP状态码也是有规律的
- 1**请求未成功
- 2**请求成功、表示成功处理了请求的状态代码。
- 3**请求被重定向、表示要完成请求,需要进一步操作。 通常,这些状态代码用来重定向。
- 4** 请求错误这些状态代码表示请求可能出错,妨碍了服务器的处理。
- 5**(服务器错误)这些状态代码表示服务器在尝试处理请求时发生内部错误。 这些错误可能是服务器本身的错误,而不是请求出错。
6. 合理使用query parameter
在请求数据时,客户端经常会对数据进行过滤和分页等要求,而这些参数推荐采用HTTP Query Parameter的方式实现
搜索用户,并按照注册时间升序、活跃度降序
https://example.com/api/users?q=key&sort=create_title_asc,liveness_desc