你真的了解RESTful嘛?

4/8/2021 REST

# 什么是REST

REST是代表性状态转移的首字母缩写(REpresentational State Transfer),它是分布式超媒体系统的体系结构样式,由Roy Fielding于2000年在他的著名论文中首次提出。

# REST的指导原则

如果要将接口称之为RESTful,则需要满足以下6个基本原则

# 1.客户端服务器

这种限制本质上意味着客户端应用程序和服务器应用程序必须能够独立地发展而不会相互依赖。客户端应该只知道资源URI,仅此而已。

可以理解成前后分离

# 2.无状态

所有客户端-服务器交互都变为无状态。服务器将不存储有关客户端发出的最新HTTP请求的任何内容。它将每个请求视为新请求。没有会议,没有历史。

# 3.可缓存

缓存约束要求对请求的响应中的数据被隐式或显式标记为可缓存或不可缓存。 如果响应是可缓存的,则授予客户端缓存以将响应数据重新用于以后的等效请求的权限。

# 4.统一接口

系统中的资源应仅具有一个逻辑URI,并且应提供一种获取相关或附加数据的方式。最好将资源与网页同义

# 5.分层系统

REST允许您使用分层的系统架构,在该架构中,您可以在服务器A上部署API,并在服务器B上存储数据并在服务器C中对请求进行身份验证。客户端通常无法知道自己是直接连接到终端服务器还是中间设备。

# 6.按需代码(可选)

大多数时候,您将以XML或JSON的形式发送资源的静态表示。但是,在需要时,您可以自由地return executable code支持应用程序的一部分,例如,客户端可以调用您的API来获取UI窗口小部件呈现代码。这是允许的。

# REST API设计的最佳做法

# 1.接受并使用JSON进行响应

现在主流基本都是前后分离,返回JSON了,没啥好说的。曾经非常流行的XML现在也很少见了。JSON格式的最大优势就在于JSON结构更容易映射至一般语言的数据结构,更好处理。而在易读性,这个就见仁见智了,但是有一点XML数据会更加冗余,传输效率相对较低,这点是毋庸置疑的。

# 2.在路径中使用名词代替动词

一个简单的例子,以前定义url时,我们常常会加上get,delete之类的动词,而在RESTful API中,我们可以直接使用名词+请求方法类型就能判断出API的含义。

HTTP常见的请求方法有GETPOSTPUTDELETE。其中GET用于检索资源(read)。POST将新数据提交到服务器(create)。 PUT更新现有数据(update)。 DELETE删除数据(delete)。通过这些动词映射到CRUD操作。

例如 :

GET: /students/:id 根据id查询一个学生信息

DELETE: /students/:id 根据id删除一个学生

POST: /students 新增一个学生

PUT: /students/:id 修改一个学生的信息

注意:避免资源URL部分使用复数,这会影响API的统一性。

# 3.使用复数名词命名集合

通常情况下,我们会使用articles获取一个文章列表,如果是需要一篇文章,则可以在资源集合后加个id。eg. /articles/:id。同时,因为我们的数据库表名一般也是复数,所以也可以与数据库的表保持一致性。可以避免出现歧义。

# 4.对分层对象使用嵌套资源

处理嵌套资源的路径应通过将嵌套资源附加为父资源后面的路径的名称来完成。

例如,获取某篇文章的评论

/articles/:articleid/comments

在这种结构下,我们可以很清楚的知道评论资源是文章资源的子对象。

# 5.优雅处理错误并返回标准状态码

发生错误的可能是多样的,为了优雅的处理错误,我们必须定下一些响应码以使API用户在出现错误时不至于困惑。

# HTTP Status Codes

HTTP定义了一些标准状态代码,可用于传达客户请求的结果。状态码分为五类

  • 1xx: 信息 – 交流传输协议级别的信息.
  • 2xx: 成功 – 表示客户的请求已被成功接受.
  • 3xx: 重定向 – 表示客户端必须采取一些其他措施才能完成他们的请求。
  • 4xx: 客户端错误 – 此类错误状态代码通常指向客户端。
  • 5xx: 服务端错误 – 这类错误状态代码通常指向服务端。

下面通过一个表格来详细介绍一些常见的REST状态码

STATUS CODE DESCRIPTION
200 OK 指示请求已成功。
201 Created 指示请求已成功,并且结果是创建了新资源。
204 No Content 服务器已完成请求,但不需要返回响应正文。服务器可以返回更新的元信息。
301 Moved Permanently 所请求资源的URL已永久更改。新的URL由Location响应中的头字段给出。除非另有说明,否则此响应是可缓存的。
302 Found 所请求资源的URL已临时更改。新的URL由Location响应中的头字段给出。仅当由Cache-ControlExpires头字段指示时,此响应才可缓存。
400 Bad Request 由于语法错误,服务器无法理解该请求。客户不应在没有修改的情况下重复请求。
401 Unauthorized 指示该请求需要用户认证信息。客户端可以使用合适的Authorization头字段重复请求
403 Forbidden 未经授权的请求。客户端没有对该内容的访问权限。与401不同,服务器知道客户端的身份。
404 Not Found 服务器找不到请求的资源。
500 Internal Server Error 服务器遇到意外情况,阻止其满足请求。(通常不应该明确地抛出。)
502 Bad Gateway 服务器作为网关获取处理请求所需的响应时,收到无效响应。
503 Service Unavailable 服务器尚未准备好处理该请求。(可能是服务器过载,系统某些部分发生故障)

有了这些状态码,服务端在处理错误时便可以给出一个明确定义的错误码(通常还需要附加一些具体的信息),而客户端也可以通过错误码以及附加信息了解系统错误的原因。

点击展开Java接口模板

请直接使用Spring的HttpStatus

public interface HttpStatusCodes {

    /**
     * https://restfulapi.net/http-status-codes/
     * 1xx: 信息 – 交流传输协议级别的信息.
     * 2xx: 成功 – 表示客户的请求已被成功接受.
     * 3xx: 重定向 – 表示客户端必须采取一些其他措施才能完成他们的请求。
     * 4xx: 客户端错误 – 此类错误状态代码通常指向客户端。
     * 5xx: 服务端错误 – 这类错误状态代码通常指向服务端。
     **/

    // 指示请求已成功。
    Integer SUCCESS = 200;
    // 指示请求已成功,并且结果是创建了新资源。
    Integer CREATED = 201;
    // 服务器已完成请求,但不需要返回响应正文。服务器可以返回更新的元信息。
    Integer N0_CONTENT = 204;
    // 所请求资源的URL已永久更改。新的URL由Location响应中的头字段给出。除非另有说明,否则此响应是可缓存的。
    Integer MOVED_PERMANENTLY = 301;
    // 所请求资源的URL已临时更改。新的URL由Location响应中的头字段给出。仅当由Cache-Control或Expires头字段指示时,此响应才可缓存。
    Integer Found = 302;
    // 由于语法错误,服务器无法理解该请求。客户不应在没有修改的情况下重复请求。
    Integer BAD_REQUEST = 400;
    // 指示该请求需要用户认证信息。客户端可以使用合适的Authorization头字段重复请求
    Integer UNAUTHORIZED = 401;
    // 未经授权的请求。客户端没有对该内容的访问权限。与401不同,服务器知道客户端的身份。
    Integer FORBIDDEN = 403;
    // 服务器找不到请求的资源。
    Integer NOT_FOUND = 404;
    // 服务器遇到意外情况,阻止其满足请求。(通常不应该明确地抛出。)
    Integer INTERNAL_SERVER_ERROR = 500;
    // 服务器作为网关获取处理请求所需的响应时,收到无效响应。
    Integer BAD_GATEWAY = 502;
    // 服务器尚未准备好处理该请求。(可能是服务器过载,系统某些部分发生故障)
    Integer SERVICE_UNAVAILABLE = 503;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

# 6.允许过滤、排序、分页

通常,为了避免一次性返回太多数据而导致的响应时间过长,服务端通常会使用过滤、分页的方式,来使系统每次仅需返回一部分而不是全部数据,以此减少系统响应时间。

# 7.保持良好的安全习惯

客户端和服务器之间的大多数通信应该是私有的,因为我们经常发送和接收私有信息。因此,必须使用SSL / TLS进行安全保护。

SSL证书可以很简单的加载到服务器上,并且成本是免费的或非常低的。没有理由不让我们的REST API通过安全通道进行通信,而不是公开进行通信。

人们不应该能够访问他们要求的更多信息。例如,普通用户不应该能够访问其他用户的信息。他们也不应该能够访问管理员的数据。

为了实施最小权限原则,我们需要为单个角色添加角色检查,或者为每个用户添加更精细的角色。

如果我们选择将用户分组为几个角色,则这些角色应具有覆盖他们所需要的全部权限,而不再需要更多权限。如果我们对用户可以访问的每个功能具有更细化的权限,那么我们必须确保管理员可以相应地向每个用户添加和删除这些功能。另外,我们需要添加一些可以应用于组用户的预设角色,这样我们就不必手动为每个用户执行此操作。

最小权限原则

在计算机科学以及其它领域中,最小权限原则是要求计算环境中的特定抽象层的每个模组如进程、用户或者计算机程序只能访问当下所必需的信息或者资源。

赋予每一个合法动作最小的权限,就是为了保护数据以及功能避免受到错误或者恶意行为的破坏。

最小权限原则也称为最少权限原则。

# 8.缓存数据以提升性能

我们可以添加缓存以从本地内存缓存中返回数据,而不是每次我们想要检索用户请求的某些数据时都查询数据库以获取数据。缓存的好处是用户可以更快地获取数据。

# 9.版本化APIs

如果API即将发生较大变动时,我们可能就需要考虑对API进行版本化控制,尽可能减少API的改变给使用者带来的不适。

我们只需将版本号添加到API路径的开头即可对其进行版本控制。

假设你提供了一个获取文章的API,处于http://localhost/articles/:id

之后你准备对API进行升级,正确的做法时将原有的API改为http://localhost/v1/articles/:id,然后再定义一个新的API为http://localhost/v2/articles/:id

如果你一开始就打算以后升级API的话,那么最好就是刚开始加带上版本号。

# 最后

通过以上对REST的了解,简单做个总结。RESTful接口的核心在于使用JSON返回数据、尽量使用名词而不是动词(主要是请求方法仍大多是GET+POST),合理的嵌套资源(有利于了解对象层级结构),优雅的处理错误(因为客户端服务端是完全独立的,所以良好的规范是必须的)。至于其他的一些细节我觉得就是见仁见智了。

参考资料

What is REST (opens new window)

Best practices for REST API design (opens new window)