为什么学习RESTful?设计方法和规范
什么是 RESTful ?
REST 全称是 Representational State Transfer,中文意思是表述性状态转移(注:通常译为表征性状态转移)。它首次出现在 2000 年 Roy Fielding 的博士论文中,Roy Fielding 是 HTTP 规范的主要编写者之一。
Roy Fielding 在论文中提到:“我这篇文章的写作目的,就是想在符合架构原理的前提下,理解和评估以网络为基础的应用软件的架构设计,得到一个功能强、性能好、适宜通信的架构。REST 指的是一组架构约束条件和原则。” 如果一个架构符合 REST 的约束条件和原则,我们就可以称之为 RESTful 架构。
通俗地讲:RESTful 就是客户端与服务器进行数据交互的一种规范,而且是当今绝大多数开发者都在遵循的规范。
应用 RESTful 架构,可以想像成读者去图书馆找书,读者相当于客户端,图书馆相当于服务器。不同种类的书籍,对应不同分类,且有固定的分类缩写。如编号以 T 开头的图书,表示工业技术类图书,编号以 J 开头的图书,表示艺术类图书。不管去哪一个图书馆,这些分类缩写都是相同的,任何一位读者只要知道图书种类,就可在标有相应分类缩写的书架区域找到相应书籍。RESTful 就是 Web 开发行业的规范,符合这种规范,就是一套 RESTful 架构。
为什么学习RESTful?
近年来,随着前后端分离技术的普遍应用,API 接口技术已经成为前后端开发人的必修课之一。在业内,不论使用什么编程语言开发 API,都需要遵守 RESTful 规范。因此,不论你是使用 API 的前端开发人员,还是直接开发 API 接口的后端开发人员,都必须熟悉 RESTful Web 规范,否则将很难同其他人配合。
如何学习RESTful ?
我们通过理论介绍加动手实践的方式完成 RESTful Web 的学习。实践环节,我们选用 Django Rest framework 框架带领读者亲自搭建一套 RESTful 架构的 API。Django Rest framework 是基于 Django 框架开发的用来帮助开发者快速构建 RESTful Web API 的强大而又灵活的工具。在实现 API 的过程中,Django Rest framework 为我们实现了大量的操作,使用该框架仅需书写少量代码,就可实现 API 的构建,大大减少了工作量,可使开发者将更多精力集中在 API 的设计,而非 API 的实现工程。
RESTful设计方法和规范
在初步了解了 RESTful 之后,我们接到一项任务,需要为一所学校开发一套师生管理系统,客户要求所开发的系统能在 PC 桌面通过浏览器使用,而且日后还想开发 IOS 和 Android 应用。了解需求之后,我们毫不犹豫选择了前后端分离的开发模式,并且决定遵从时下最为流行的 RESTful 规范。接下来,我们就以后端开发人员的角色,一起来了解整个开发过程。
1. 域名(Domain)
根据 RESTful 规范,应该尽量使用专用的域名用于部署 API,于是我们和校方沟通,使用下方域名作为 API 访问地址:
https://api.demo.com
但是经过沟通,发现上述域名已被占用,校方否决了我们的提议,考虑到 API 相对简单,于是我们使用下面地址部署 API:
https://www.demo.com/api
上述地址中,https代表协议名称,常见的还有http,二者区别在于前者在传输过程中是将信息加密后传输的,而后者是明文传输;**www.demo.com为域名,可以理解成某个机房里一台电脑的地址,通过这个地址,就能访问这台电脑提供的资源;api**代表一个资源路径,可以想象成这台电脑中一个文件夹的路径。
2. 版本(Versioning)
师生管理系统不是一成不变的,日后还要更新维护。为了区分不同版本,API 的 URL 中应当包含 API 版本信息:
http://www.demo.com/api/1.0/foo
http://www.demo.com/api/1.1/foo
http://www.demo.com/api/2.0/foo
除了上述方法外,API 版本信息还可放在 HTTP 请求头中。Github采用的就是这种做法。
因为不同的版本,可以理解成同一种资源的不同表现形式,所以应该采用同一个 URL。版本号可以在 HTTP 请求头信息的 Accept 字段中进行区分(参见Versioning REST Services):
Accept: vnd.example-com.foo+json; version=1.0
Accept: vnd.example-com.foo+json; version=1.1
Accept: vnd.example-com.foo+json; version=2.0
实际工作中,通常采用第一种方法,因为这样的方式更加直观,方便使用。
3. 路径(Endpoint)
路径即"终点"(endpoint),是访问 API 的具体网址,通过访问每个网址,可以获取到相应的资源(resource)。在师生管理系统中,所谓资源,就是我们想获取的信息,比如获取 3 年 2 班所有学生姓名,获取小明的年龄、成绩等。
路径须满足以下规范:
1. 资源路径中应当使用名词,杜绝动词。资源路径中的名词,应当与数据库的表名相对应。
以下路径中包含动词,是不符合规范的例子,在实际工作中,应当避免。
/getStudents :获取学生信息
/listTeachers :获取老师信息
/retreiveStudentByID?Id=2020 :获取ID为2020的学生信息
对于资源的操作,应该通过 HTTP 中的不同方法来区分处理资源的动作,资源路径中应当只包含名词。
GET /students :将返回所有学生信息
POST /students :将新增的学生信息存入数据库
GET /students/4 :获取编号为4号的学生信息
PATCH(或)PUT /students/4 :更新编号为4的学生信息
2. API 中的名词应该使用复数。无论是子资源或者是所有资源。
例如:
获取单个学生信息:http://www.demo.com/students/1 :获取编号为1的学生信息
获取所有学生信息: http://www.demo.com/students :获取所有学生信息
4. HTTP动词
对于资源的具体操作类型,由 HTTP 动词表示。
常用的 HTTP 动词有下面 4 个(括号里是对应的 SQL 命令)。
-
GET(SELECT):从服务器取出资源(一项或多项) -
POST(CREATE):在服务器新建一个资源 -
PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源) -
DELETE(DELETE):从服务器删除资源
还有 3 个不常用的 HTTP 动词。
-
PATCH(UPDATE):在服务器更新(更新)资源(客户端提供改变的属性) -
HEAD:获取资源的元数 -
OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的
下面是一些例子。
GET /classes:列出所有班级
POST /classes:新建一个班级(上传文件)
GET /classes/ID:获取某个指定班级的信息
PUT /classes/ID:更新某个指定班级的信息(提供该班级的全部信息)
PATCH /classes/ID:更新某个指定班级的信息(提供该班级的部分信息)
DELETE /classes/ID:删除某个班级
GET /classes/ID/students:列出某个指定班级的所有学生
DELETE /classes/ID/students/ID:删除某个指定班级的指定学生
5. 过滤信息(Filtering)
如果记录数量很多,服务器不可能都将它们返回给用户。API 应该提供参数,过滤返回结果。比如,我们想获取全校师生的个人信息,如果将这些信息一股脑地全部展示在网页上,是不明智也是不现实的。如果数据量太大,在实际开发中我们会采用分页展示的形式。另外,如果想在一次考试后,按照成绩高低展示学生信息,那么可以通过过滤信息来实现。
所谓过滤,就是在 URL 中添加一下限制参数。下面是一些常见的参数。
?limit=10:指定返回记录的数量
?offset=10:指定返回记录的开始位置。
?page=2&per_page=100:指定第几页,以及每页的记录数。
?sortby=score&order=asc:指定返回结果按照学生的成绩(score)正序(asc)排列顺序。
参数的设计允许存在冗余,即允许 API 路径和 URL 参数允许有重复。比如,想要查询某个班级所有学生信息,我们可以设计GET /classes/ID/students
与GET /students?class_id=ID
两种地址,任何一种都可得到相同的结果。
6. 状态码(Status Codes)
服务器向用户返回的状态码和提示信息,常见的有以下一些(方括号中是该状态码对应的 HTTP 动词)。不同的状态码代表着不同的含义,比如以 2 开头的状态码通常代表服务器成功响应,3 开头的状态码代表发生了重定性(即跳转到了别的链接),4 开头的状态码通常表示客户端这边提供的信息有误,而 5 开头的状态码则表示服务器内部出现的错误。通过返回的状态码,用户即可判断请求成功与否,不成功问题在何处。
一些常用的状态码列举如下:
-
200 OK GET:服务器成功返回用户请求的数据 -
201 CREATED POST/PUT/PATCH:用户新建或修改数据成功。 -
202 Accepted:表示一个请求已经进入后台排队(异步任务) -
204 NO CONTENT DELETE:用户删除数据成功 -
400 INVALID REQUEST POST/PUT/PATCH:用户发出的请求有错误,服务器没有进行新建或修改数据的操作 -
401 Unauthorized:表示用户没有权限(令牌、用户名、密码错误) -
403 Forbidden:表示用户得到授权(与401错误相对),但是访问是被禁止的 -
404 NOT FOUND:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的 -
406 Not Acceptable GET:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式) -
410 Gone GET:用户请求的资源被永久删除,且不会再得到的 -
422 Unprocesable entity POST/PUT/PATCH:当创建一个对象时,发生一个验证错误 -
500 INTERNAL SERVER ERROR:服务器发生错误,用户将无法判断发出的请求是否成功
状态码的完全列表参见这里或这里。
7. 错误信息
如果状态码是 4xx,服务器就应该向用户返回出错信息。一般来说,返回的信息是键值对形式的数据,将error
作为键名,出错信息作为键值即可。比如,在一个提供查询学生信息的 API 中,要求客户端提供正确的 API key(可以理解为输入了正确的用户名和密码)才能访问,如果提供的 API key 不正确,此时服务器应拒绝访问,并返回错误信息。这样,客户端就知道了为何没能查到信息,修改成正确的 API key 即可。
{
error: "Invalid API key"
}
8. 返回结果
针对不同操作,服务器向用户返回的结果应该符合以下规范。
-
GET /collection:返回资源对象的列表(数组) -
GET /collection/resource:返回单个资源对象 -
POST /collection:返回新生成的资源对象 -
PUT /collection/resource:返回完整的资源对象 -
PATCH /collection/resource:返回完整的资源对象 -
DELETE /collection/resource:返回一个空文档
9. 超媒体链接
RESTful API 最好做到 Hypermedia(即返回结果中提供链接,连向其他 API 方法),使得用户不查文档,也知道下一步应该做什么。
比如,Github 的 API 就是这种设计,访问api.github.com会得到一个所有可用API的网址列表。
{
"current_user_url": "https://api.github.com/user",
"authorizations_url": "https://api.github.com/authorizations",
// ...
}
从上面可以看到,如果想获取当前用户的信息,应该去访问api.github.com/user,然后就得到了下面结果。
{
"message": "Requires authentication",
"documentation_url": "https://developer.github.com/v3"
}
上面代码表示,服务器给出了提示信息,以及文档的网址。
10. 数据格式
服务器返回的数据格式,应该尽量使用 JSON,避免使用 XML。什么是 JSON 呢?什么又是 XML 呢?两种数据格式的简单举例如下:
# JSON
{"name":"XiaoMing",
"age":"12",
"gender":"male"}
# XML
<?xml version="1.0" encoding="UTF-8" ?>
<name>XiaoMing</name>
<age>12</age>
<gender>male</gender>
通过上面的对比可以看出,JSON 数据形式要远比 XML 的数据形式来得简单和易懂,所以现在的 Web 开发中 JSON 数据格式已经开始全面取代 XML 应用在实际开发中。