推荐大家学习一下 RESTful API

260 阅读11分钟

本篇文章不是给大家面面俱到的讲 RESTful API 的原理和设计,这是我在阅读的一些 API 设计指南做的笔记,希望下面的内容能给你打开一个知识面,也推荐了一些好的 Guideline 给你们学习,一起卷起来吧!

什么是 REST?

表现层状态转换(英语:Representational State Transfer,缩写:REST),它是 Roy Thomas Fielding 博士在 2000 年提出的一种便于不同程序或应用在互联网上互相传递信息的软件架构风格,值得强调的是,它是一种设计风格而不是标准。使用 HTTP 协议时,资源名称映射到网址,方法映射到 HTTP 的 POST、GET、PUT、PATCH 和 DELETE上。

什么是 RESTful API?

符合 REST 风格设计的 Web API 成为 RESTful API,资源通过资源名称被引用,并通过一组“方法”进行控制。

为什么要使用 RESTful API?

从上面推荐的 Guidealines,我们可以看到这种 API 设计风格是受许多大厂青睐的,或者可以说算是一个标准和规范了。再引用耗子叔说的:“Restful API算是一个HTTP的规范和标准了,你要说是最佳实践也好,总之,它是一个全世界对HTTP API的一个共识。在这个共识上,你可以无成本地享受很多的技术红利,比如:CDN,API网关,服务治理,监控……等等。这些都是可以让你大幅度降低研发成本,避免踩坑的原因。”

在这里,我也总结了几点:

  • 对于 RESTful API 已然达成了一种共识,大家都在用,成为了一种标准和规范,规范是大家约定俗成的标准,如果大家都遵守这套标准,那么沟通成本将大大降低。
  • 在写代码的时候,我们需要写大量的 CRUD,HTTP 动词能很容易映射到这上面。
  • 比如说,现在我们这套代码在 Web 端、IOS 和安卓,在同时使用,但是我们发现现有 API 提供的功能,在 Web 端并不能很好的起效果,这就需要我们去升级 API,但是 IOS 和安卓的用户又不愿意升级版本,那这时我们就可以使用 RESTful API 中的版本号机制:
    • GET /v1/users/{user_id} // 版本 v1 的查询用户列表的 API 接口
    • GET /v2/users/{user_id} // 版本 v2 的查询用户列表的 API 接口

那我们就可以不改变版本 v1 的查询用户列表的 API 接口的情况下,新增版本 v2 的查询用户列表的 API 接口以满足 Web 端的改造,然后又不影响 IOS 和安卓用户的使用。

API 设计

接下来,我将 API 的设计简单的分为了输入和输出,输入就是 API 的资源路径设计、API 要选择的 HTTP 方法、参数设计、版本控制,输出就是 API 的响应状态码的选择、返回有意义的错误响应。

资源路径

资源可以是任何类型的对象、数据或服务,RESTful API 通常表现为资源层次结构,每个层级是一个“简单资源”或“集合资源”,比如说 /users 就是一个“集合资源”,表示所有用户集合,/user/1 是一个“简单资源”,表示用户ID 为 1 的用户。

我们在设计资源路径的时候,值得注意的一些细节:

  • 资源最好是用名词来表示,而不是用动词,因为名词更像是一种资源,而动词是对资源执行的操作。如果是一组集合资源,使用复数名词。
// 推荐
POST http://example.com/classes
// 不推荐
POST http://example.com/createClasses
  • 避免请求复杂度超过集合/项目/集合的资源 URI。举个例子,我们可以使用该资源路径 /classes/1/students/2/courses 来找到编号为 1 的班级下的序号为 2 的同学学的所有科目的分数,这个 URI 可以通过多个级别来导航我们要的资源,但是,如果后面我们要修改资源之间的关系,那这个 URI 的复杂性就很难维护。我们应该拆分这个 URI,使它相对简单,我们可以拆成两部分,先通过 /classes/1/students 找到编号为 1 的班级下的所有同学,然后再通过 students/2/courses 找到序号为 2 的同学学的所有科目的分数。

HTTP Method

方法描述是否幂等
GET用于查询操作。
PUT用于所有的信息更新。
DELETE用于删除操作。
POST用于新增操作。
HEAD用于确认 URI 的有效性及资源更新的日期时间等或是用于探测API 是否健康。
PATCH用于局部信息的更新。
OPTIONS获取 API 的相关的信息 或用来查询针对请求 URI 指定的资源支持的方法。

在表格中还提及了一栏幂等,幂等这个对于 API 调用来说,是一件很重要的事情,幂等就是指 API 无论是执行一次还是多次,它的结果是一致的。比如说,我们使用 POST 请求创建一个新的班级,每个班级都是不一样的,所以这肯定不能是幂等的。

咱们还是以班级和学生为例子:

资源GETPUTDELETEPOST
/classes获取所有班级批量更新班级删除所有班级创建新班级
/classes/1获取班级 1如果班级 1 存在,更新它的详细信息删除班级 1错误
/classes/1/students获取班级 1 的所有学生批量更新班级 1 的学生删除班级 1 的所有学生创建班级 1 的新学生

参数设计

咱们提一提在复杂查询的时候常见的分页和排序操作。

分页

随着程序的不断运行,数据集也是在不断的增长的,那我们的服务从一开始就需要设计支持分页,避免请求的数据集太大导致响应缓慢也消耗服务器资源。另外,我们需要考虑每页没返回的总数量,以防拒绝服务攻击,并为分页参数设置有意义的的默认值,比如说 pageNo 设置为 1,pageSize 设置为 10。

排序

我们可以使用 sortBy 和 sortOrder 两个参数来来控制排序规则,比如说:

// 按照创建时间倒序排序
http://example.com/classes?sortBy=createTime&sortOrder=desc
// 按照创建时间倒序排序再按照id正序排序
http://example.com/classes?sortBy=createTime,id&sortOrder=desc,asc

响应状态码

GET

成功的 GET 方法通常返回 HTTP 状态代码 200(正常)。 如果找不到资源,该方法应返回 404(未找到)。

如果请求已完成,但 HTTP 响应中没有包含响应正文,则应返回 HTTP 状态代码 204(无内容);例如,不产生匹配项的搜索操作可能会通过此行为实现。

POST

如果 POST 方法创建了新资源,则会返回 HTTP 状态代码 201(已创建)。 新资源的 URI 包含在响应的 Location 标头中。 响应正文包含资源的表示形式。

如果该方法执行了一些处理但未创建新资源,则可以返回 HTTP 状态代码 200,并在响应正文中包含操作结果。 或者,如果没有可返回的结果,该方法可以返回 HTTP 状态代码 204(无内容)但不返回任何响应正文。

如果客户端将无效数据放入请求,服务器应返回 HTTP 状态代码 400(错误的请求)。 响应正文可以包含有关错误的其他信息,或包含可提供更多详细信息的 URI 链接。

PUT

与 POST 方法一样,如果 PUT 方法创建了新资源,则会返回 HTTP 状态代码 201(已创建)。 如果该方法更新了现有资源,则会返回 200(正常)或 204(无内容)。 在某些情况下,可能无法更新现有资源。 在这种情况下,可考虑返回 HTTP 状态代码 409(冲突)。

PATCH

客户端可以使用 PATCH 请求向现有资源发送一组修补文档形式的更新。 服务器将处理该修补文档以执行更新。 修补文档不会描述整个资源,而只描述一组要应用的更改。

14 个常见的状态码

  • 200 OK:表示从客户端发来的请求在服务器端被正常处理了。
  • 204 No Content:代表服务器接收的请求已经处理了,但在返回的响应报文中不包含实体的主体部分。
  • 206 Partial Content:表示对服务器进行了范围请求,而服务器成功执行了这部分 GET 请求。
  • 301 Moved Permanently:永久性重定向,表示请求的资源已被分配了新的 URI,以后应该使用资源现在所指的 URI。
  • 302 Found:临时性重定向,表示该资源已分配了新的 URI,希望用户这次能使用新的 URI 访问。
  • 303 See Other:表示由于请求对应的资源存在着另一个 URI,应使用 GET 方法定向获取请求的资源。
  • 304 Not Modified:表示客户端发送附带条件的请求 A 时,服务器端允许请求访问资源,但未满足条件的情况。
  • 307 Temporary Redirect:临时重定向。该状态码与 302 Found 有着相同的含义。尽管 302 标准禁止 POST 变换成 GET,但实际使用时大家并不遵守。307 会遵照浏览器标准,不会从 POST 变成 GET。
  • 400 Bad Request:表示请求报文中存在语法错误。
  • 401 Unauthorized:表示发送的请求需要有通过 HTTP 认证(BASIC 认证、DIGEST 认证)的认证信息。另外若之前已进行过 1 次请求,则表示用户认证失败。
  • 403 Forbidden:状态码表明对请求资源的访问被服务器拒绝了。未获得文件系统的访问授权,访问权限出现某些问题。
  • 404 Not Found:该状态码表明服务器上无法找到请求的资源。
  • 500 Internal Server Error:表明服务器端在执行请求时发生了错误,可能是 Web应用存在的 bug 或某些临时的故障。
  • 503 Service Unavailable:表明服务器暂时处于超负载或正在进行停机维护,现在无法处理请求。

更多细节可以查阅这里RFC 7231: Hypertext Transfer Protocol (HTTP/1.1): Semantics and Content

有意义的错误响应

如果程序出现了异常,我们的代码应该将异常捕获下来并返回正确的状态码及有意义的错误描述,不应该只为了图方便,全部都只返回 500(服务器异常)。我觉得一个好的错误响应应该包括如下信息:

  • 正确的 HTTP 响应状态码(通用)
  • 规范的错误代码(业务异常使用)
  • 易于理解的、简洁的错误消息,如果有必要,还可以提供一个链接给用户,读者可以在链接中查看更详细的错误信息以及解决办法,也可以在链接中提问。

需不需要专门去优化?

这个话题是我在一个帖子上看到的,比如说获取用户列表,一方站在继续使用 GET /getUserList?current=1&size=10 这一风格上,一方站在优化成 GET /users?current=1&size=10 上,双方各执一词。对于这个话题,我的看法是,如果团队中,已经在大量使用第一种风格了,我们就没有必要去全部更改成 RESTful 风格的 API 了,后面在开发的时候,也应该按照之前的风格继续写下去,不然这可能会导致后面的同事在看代码的时候感到很困惑,增加沟通成本。我们知道数据库设计,一旦设计好,未来要更改就很难了,API 设计也如此。所以,我们在一开始设计的时候,我们可以去设计 RESTful 风格的 API,上面提到的 Guidelines 都很不错,但是已经在使用的,我们更改之后,还要驱动使用你的 API 的前端和厂商去升级,费时费力。

参考(推荐指南)