Restfull风格
文章目录
什么是REST?
REST全称Representational State Transfer,翻译成中文即是:表述性状态转移。我在第一次接触该词时,也是万千疑惑,这到底是谁翻译的鬼东西?其实,REST是Roy Fielding博士于2000年在他的博士论文里提出的一种web架构风格。
简单来说,REST是定义了一组架构约束原则,并能应用于分布式web应用的一种架构风格。
REST的六个架构约束
-
客户端-服务器
-
无状态
-
缓存
-
统一接口
-
分层系统
-
按需代码
1、客户端-服务器
REST引入客户端-服务器这一约束的目的是为了更好的分离用户界面与数据存储这两个关注点,此约束的优点是:改善了用户界面跨平台的可能性,即提高了客户端程序的可移植性。同时简化服务器组件,改善了系统的可伸缩性。最重要的一点,这种分离使得组件可独立进化,从而支持多个组织领域的互联网规则的需求。
2、无状态
无状态这一约束,即通信过程本质上应该是无状态的。即客户端向服务端发起的每个请求都必须包含足够信息来使客户端准确地表达请求含义,同时客户端不能利用储存在服务器端的任何上下文,会话状态必须全部保存在客户端。举个简单的例子,你正在浏览此文,服务端并不关心你是谁,只关心请求是否能有权限查看此文而已。你有权限,我则给你展示,你没有,则不展示。至于你有没有登录,服务端针对此次的请求是不关心。
3、缓存
引入缓存这一约束,是为了解决网络天然的传输效率这一症结,当有大量并发网络请求,缓存是解决服务端压力的一大利器。
4、统一接口
统一接口约束是REST最核心,也是区别于其它架构风格的一个最重要特征。它强调组件与组件之间,必须有统一的接口。同时REST提出了四个接口约束条件:资源的识别(identification of resources)、通过表述来操作资源(manipulation of resources through representations)、自描述的消息(self-deive messages)、以及作为应用状态引擎的超媒体(hypermedia as the engine of application state,即HATEOAS)。这些约束条件,将在后文所说的RESTful API中详细提到。
5、分层系统
引入分层系统这一约束,是为了解决分布式网络应用中量的问题,一般而言,单体软件是非常庞大且效率低下,为此,很多软件先驱总结了一系列方法,这其中就包括分层的思想,通过垂直或者水平的划分应用,将网络流量交给分布式网络中不同的,但功能类似或者相同的组件去处理。这就让系统在业务上很好的解耦,有利于实践中的软件维护。
6、按需代码
按需代码的意思是,REST允许客户端通过下载并执行一些来自于服务端的脚本程序,来对客户端功能进行扩展。这样可以简化客户端功能的开发,比如常见的移动端webview,web小游戏等。所以按需代码改善了客户端及系统的可扩展性。同时也降低了可见性,因此它只是REST架构中的一个可选约束。
什么是RESTful API?
REST一般应用于HTTP协议,实际上,HTTP/1.1就是基于REST架构的,REST的高度抽象其实是一个有限自动机。显然,超媒体是REST的驱动引擎,这一点正是HTTP的特性。所以HTTP甚至整个web,都可以说是一个有限自动机。
一般,我们认为只要符合REST风格的API,都是RESTful API。同时,RESTful API就是最大限度的利用了http能力的API。
RESTful API最佳实践:
1、使用名词而非动词
例如我们在获取一个用户资源时,尽量语义化,最好使用形如:GET /user/{id} HTTP/1.1而不是:GET /getuser?id=xxx HTTP/1.1
因为在HTTP协议中,已经有method(GET,POST,PUT,DELETE)来指明动作,而URL只是资源的唯一标识而已。
由于大部分国人英文水平的问题,不建议在资源名中使用复数
2、使用HTTP Method代替资源操作
示例:
操作 正确示例 错误示例
获取用户 GET /user/{id} GET /getuser/{id}
新建用户 POST /user GET /createuser?queryparams
修改用户 PUT /user/{id} GET /modifyuser?id=xxx&otherparams
删除用户 DELETE /user/{id} GET /deleteuser?id=xxx&otherparams
3、使用HTTP STATUS CODE + 自定义错误码
HTTP状态码是HTTP规范预定义的一组HTTP响应状态标识。从1xx - 5xx已经明确返回值。很多情况下,一些程序员在开发过程中,不管是正确还是正确情况下,都返回200的状态码,然后用自定义的错误码来标识错误信息。这虽然对开发没有问题,但已经弱化了HTTP本身的能力,以及一些开发框架及代理服务器的能力。例如很多框架本身就封装了对HTTP状态码异常的处理流程。
常见的HTTP状态有如下:
状态码 定义
200 GET请求成功
201 POST,PUT请求成功
204 DELETE请求成功
400 客户端请求错误
401 客户端请求未认证
403 客户端请求无权限
404 资源未找到
500 服务器内部错误
4、使用API版本
目前前后端分离的情况下,客户端更新出去后,用户实际上是很难与企业产品最新版本保持一致,所以服务端也需要支持不同版本的API,这样就算用户在使用老版本的客户端,也能正常使用产品业务。
示例:GET /v1.0/user/{id} HTTP/1.1
5、使用通用的查询参数名
客户端在与服务端对接时,经常会有一些列表查询接口,使用分页,条件过滤等。对于这些参数,双方尽量约定通用的参数名。以便双方开发通用的接口。
6、返回通用的结构体
服务端应保持API的返回结构体一致,这样客户端无须针对每个接口做特殊处理,有利于双方开发效率提高。
示例:
{
"data":{
"example":{
"_id":"5a5470f0fa424534e0002546",
"name":"resource 4",
"count":40,
"updated_at":"2018-01-09 15:46:59",
"created_at":"2018-01-09 15:36:16"
}
},
"msg":"",
"url":"",
"code":0
}
约定默认返回JSON格式,并且只含有data,msg,url,code四个属性:
字段名 定义
data 即客户端有效的payload,可以是对象,可以是值等
msg 响应的具体描述
url POST,PUT成功时返回的资源url,不做特殊要求
code 自定义错误码,成功时默认为0
总结
本文所述的RESTful API,仅为Richardson成熟度模型中第二级API,真正的RESTful API应该是HATEOAS的,即以超媒体作为应用状态的引擎(Hypermedia as the Engine of Application State),这种API目前在实践中应用的比较少,也没有比较成熟的方案。所以暂不介绍
Richardson成熟度模型:
API等级 说明
第0级 没有明确的资源概念,只有一个URL,只使用单个HTTP方法
第1级 有明确的资源概念,存在很多URL,只使用单个HTTP方法
第2级 有明确的资源概念,存在很多URL,使用HTTP作为操作资源的统一接口。通常将对于资源的CRUD式操作分别映射到4个HTTP方法
第3级 在满足第2级标准的基础上,使用超媒体作为应用状态的引擎(HATEOAS)
REST是一种看似简单,其实有很大学问的架构风格,RESTful API也是时下最流行的API风格。RESTful的设计只要符合REST约束,大部分情况下也是可行的。所以实践中只要有统一的规范风格更重要。同时,使用一种风格的API,有利于思考模式,工作模式的转变。这对想做架构师的程序员是非常有益的。例如使用json-RPC风格的API,那么思维必定走向RPC。RPC这种耦合性高,依赖于库的API,大家使用过程务必要非常小心。
案例
/** 风格一:查询:GET 2 * 按ID值获取订单信息
*
* @param id
* @return
*/
@ResponseBody //id为浏览器传过来的值,名称与之前定义的名称要一致
@RequestMapping(value = "/order/{id}", method = RequestMethod.GET)
public OrderEntity getOrder(@PathVariable int id) {
return orderManager.get(id);
}
在用Ajax进行信息交互时:
1.先获取到当前所需要的id,可通过var id = $("_id").val();获取到具体的值
2.再在url:"xxx/xxx/xxx/"+id;即可,记得后面那个斜杠/
3.ajax根据url路径发送请求过来后@RequestMapping自动接收id进行处理 //来自两个地方的id 他们的名称要相同
/**
* 按类型查询货物信息
*
* @param id
* @return
*/
@ResponseBody
@RequestMapping(value = "/product", method = RequestMethod.GET)
public List<ProductEntity> getProductByType(String type) {
return productManager.queryByType(type);
}
/** 风格二:增加:POST
* 创建订单信息
*
* @param id
* @return
*/
@RequestMapping(value = "/order", method = RequestMethod.POST)
public void createOrder(OrderEntity entity) {
orderManager.create(entity);
}
/** 风格三:修改:PUT
* 修改订单信息
*
* @param id
* @return
*/
@RequestMapping(value = "/order/{id}", method = RequestMethod.PUT)
public OrderEntity updateOrder(@PathVariable int id, OrderEntity entity) {
return orderManager.update(entity);
}
/** 风格四:删除:DELETE
* 删除指定ID值的订单信息
*
* @param id
* @return
*/
@RequestMapping(value = "/order/{id}", method = RequestMethod.DELETE)
public void deleteOrder(@PathVariable int id) {
orderManager.delete(id);
}