服务端相关 / 04 RESTful 设计方法和规范

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/studentsGET /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 应用在实际开发中。

11. 小结

本节主要从域名、版本、路径、HTTP动词、过滤信息、状态码、错误信息、返回结果、超媒体链接、数据格式 10 个方面介绍了 RESTful 设计方法和设计规范。为了让更多的人方便使用所设计的 API 接口,以上规范一定要遵守哦!