RESTful

RESTFUL是一种网络应用程序的设计风格和开发方式,基于HTTP,可以使用XML格式定义或JSON格式定义。RESTFUL适用于移动互联网厂商作为业务接口的场景,实现第三方OTT调用移动网络资源的功能,动作类型为新增、变更、删除所调用资源。

[1]“RESTful_百度百科,” Baike, https://baike.baidu.com/item/RESTful/4406165(访问时间 Jan. 1, 1970).

RESTful 三要素

RESTFul主要有以下三个要点:

  • 基于HTTP协议URL对外暴露
  • 使用XML/JSON格式定义
  • 根据不同行为使用不同的请求方式

以下从这三个方面分别阐述。

基于HTTP协议URL对外暴露

在RESTful协议中,URL被视为资源,要求使用名词进行说明。

RESTful API一般标准如下:

http(s)://域名:端口[/版本]/资源1[/子资源2/.../子资源n][/路径变量]

举例:

GET http(s)://edu.lagou.com/v1.1/blog/article/10

若需要返回数据集合,最后一级资源应使用负数。例如:

GET http(s)://edu.lagou.com/v1.1/blog/articles?categoryId=10

表示查询分类号为10的文章数据,调用接口版本为v1.1。

反例:

GET http(s)://edu.lagou.com/v1.1/blog/article/select_by_id?id=10

在该反例中,描述的是一个动作,而非一个代表资源的名词。

使用XML/JSON格式定义

接口返回必须是JSON或XML数据,绝不能包含与具体展示相关的内容。反例如下:

GET http(s)://edu.lagou.com/v1.1/blog/article/10
{
	"code": "0",
	"message": "success",
	"data": "<h1>员工姓名:张三</h1>"
}

在这个反例中,结构返回数据包含<h1>标签,这个标签只能由浏览器解析展现,与具体展示相关。这样做,若调用该接口的是APP,则无法使用。应修改为如下返回方式:

GET http(s)://edu.lagou.com/v1.1/blog/article/10
{
	"code": "0",
	"message": "success",
	"data": {
		"name": "张三",
		"age": 30,
	}
}

根据不同行为使用不同的请求方式

在RESTful语义中,不同的HTTP动作对应着不同的行为。常用的如:

  • GET - SELECT: 从服务器拉取资源
  • DELETE - DELETE: 从服务器删除资源
  • POST - CREATE: 在服务器创建一个资源
  • PUT - UPDATE: 在服务器更新资源

示例,若请求如下:

GET http(s)://edu.lagou.com/v1.1/blog/article/10

则表示:向服务器查询编号为10的员工数据。若请求为:

POST http(s)://edu.lagou.com/v1.1/blog/article/10
{
	"id": 10,
	"name": "张三",
	"age": 25,
}

则表示,在服务器中新增一个编号为10的员工。类似的:PUT代表更新员工数据,DELETE代表删除员工数据。

RESTful 接口的设计规则和注意事项

接口要保证幂等性设计

幂等性:当多次重复请求时,接口应当能够保证产生于预期相符的结果。

若设计了一个为员工涨薪的接口:

PUT https://edu.lagou.com/employee/salary
{
	"id": 1,
	"incr_salary": 500,
}

后端伪代码可能会这样写:

// 查询员工数据
Employee employee = employeeService.selectById(1);
// 更新工资
employee.setSalary(employee.getSalary() + incrSalary);
// 执行更新语句
employeeService.update(employee);

但在分布式环境下,为了保证消息的高可靠性,客户端往往会采用重试或消息补偿的形式,重复发送同一个请求。此时,每当一次重复请求到达时,该员工的工资都会被加500,最终员工拿到的工资超过了员工的应得工资。此时,这个接口的设计就是不理想的。

为了解决这个问题,可以采用很多方法,这里浅谈一下。

  1. 乐观锁

在员工表上额外添加一个version字段,代表当前的数据版本。每次修改员工数据时,都要使该字段加一。同时,这个version应当被附加在请求中,如下所示:

PUT https://edu.lagou.com/employee/salary
{
	"id": 1,
	"incr_salary": 500,
	"version": 1,
}

服务器代码也应更新:

// 查询员工数据
Employee employee = employeeService.selectById(1);
// 更新工资
employee.setSalary(employee.getSalary() + incrSalary);
// 执行成功,version变更
employee.setVersion(employee.getVersion() + 1);
// 执行更新语句
employeeService.update(employee);

此时的更新SQL语句变为:

update employee set salary = 3500, version = 2 where id = 1 and version = 1

此时,即使客户端重复发送相同请求,由于version的存在,只会产生一个版本变更。

  1. 幂等表
  2. 分布式锁
  3. 状态机

标准化的响应结果集

RESTful接口的响应结果应当保持全局相同的语义和结构。以行业常见的数据格式为例:

{
	code: "0",
	message: "success",
	data: {
		empoyee: {
			name: "张三",
			salary: 3500,
			version: 2,
		}
	}
}
  • code: 服务器处理结果。

  • message: 返回的消息内容。

  • [data]: 响应返回的额外数据,如:查询结果、新增或者更新后的数据。

在语义层面也应保持一致。例如:服务器处理成功的code值为0,如遇到异常,所有开发人员也应遵守统一的code,如:

  • 1xx :参数异常
  • 2xx:数据库处理异常

等。

接口设计采用无状态方案

无状态方案:RESTful接口每一个请求进来,都应该能得到相同的预期。

若存在如下经典设计:通过Nginx前置做负载均衡,将用户请求打到某一个Tomcat节点上,用户数据会保存在Tomcat服务器的Session会话中。

MD_Picture_1_NginxLoadBalancing

若用户请求1打到Tomcat服务器1上,数据保存在Tomcat服务器1的Session中。请求2打到Tomcat服务器2上,则会缺少数据。此时则是有状态的。常用的去状态化方案一般有一下几种:

  1. 客户端存储数据

将用户数据保存在客户端的cookie或者其他介质上,这样用户数据可以被附加在请求体或请求头中,发送到服务器进行处理。

但很明显,用户数据存储被包含在网络传输过程中,若网络被抓包,用户数据可能泄露。所以必须在客户端进行加密存储,再在服务端进行解密。著名的JWT(Json Web Token),就是使用这种方案实现用户认证的去状态化。

  1. 后端统一存储状态数据

例如,可以在tomcat后端加入redis,进行统一存储。这样既可以保护用户敏感数据,也可以做到去状态化。但是,会导致架构复杂度增加,部署成本提高。

文章作者: Serendipity
本文链接:
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 闲人亭
杂货店 RESTful
喜欢就支持一下吧