RESTful API 简介
RESTful API 简介
想必使用过 PHP、JSP 这一类服务器动态页面技术的程序员应该都还记得,在使用这种传统的动态页面架构构建应用程序的时候,用于描述用户界面的 HTML 页面通常都是在服务器上完成渲染的。在这种情况下,应用程序在客户端的 UI 通常是很难针对用户所使用的软硬件环境做出具体调整的,并且用户在绝大多数时候也只能通过 Web 浏览器这种单一的客户端形式来使用应用程序。这个问题在互联网用户只能使用 PC 的时代是可以忽略不计的,但在如今这个大量使用平板电脑、智能手机以及手表、手环等各种穿戴式智能设备的时代,用户所在的软硬件环境是千差万别的,再继续这种服务端应用的设计方法显然就有些难以为继了。为了解决这个问题,业界相继提出了 SOAP、XML-RPC 等设计现代化网络服务的解决方案,而 REST 正是其中被设计得较为简洁的一种方案。
REST 这套开发现代化互联网应用程序的解决方案最早是由罗伊·托马斯·菲尔丁(Roy Thomas Fielding)在 2000 年发表的博士论文中提出的[1],其设计目标是在应用程序的业务逻辑上实现客户端与服务端的分离,并在它们之间建立相互传递信息的 API 规范,从而为应用程序的分布式部署和运行创造基础。在服务端的设计方法中,我们通常将遵守或兼容了这套 API 设计规范的软件称为基于RESTful 架构的应用程序,而将基于 RESTful 架构所的服务端形式称为 RESTful API。接下来,我们就将基于这一 API 规范来示范如何实现一个应用程序服务端端部分的业务逻辑。当然了,在进行具体的项目实践之前,我们还是需要先对该规范的具体设计要求做一个基本的介绍。
RESTful API 设计规范
REST 这个词是 Representational State Transfer 的英文缩写,在中文中通常被翻译为表现层状态转换,在这个词中,表现层指的是互联网中各种资源实体的表现形式。例如,文本类型资源的表现形式既可以是 TXT 格式的文件,也可以是直接在网络中传递的字符串,图片类型资源的表现形式既可以是 PNG 格式的文件,也可以是存储在数据库中的一段二进制数据,简而言之,资源的表现层指的就是它在某个具体环境中的表现形式。具体到 HTTP 协议中,资源的表现层应该就是我们用来定位资源的统一资源标识符(以下简称 URI)了。但 HTTP 协议是一个无状态协议,这意味着,客户端在使用 URI 请求相关资源的时候,它并不知道,也无需知道这些资源在服务端服务中的具体表现形式,例如我们在向服务器请求某个图片资源的时候,事实上是无法,也不需要知道这个图片资源在服务端服务中是一个存储在服务器磁盘上的 PNG 格式的文件,还是存储在数据库中的一个二进制数据。所有的这一切都需要应用程序的服务端服务对 URI 这种表现形式执行状态转换,将其转换成指定的资源在服务器上的表现形式,然后才能执行一系列响应客户端请求的操作。这里所描述的、服务端服务针对资源表现形式的整个转换过程及其衍生出来的程序设计思路,就是 REST 提出的解决方案。
与 SOAP 本身是一个网络协议不同的是,REST 提出的解决方案本质上只是一套程序员们在编写软件时需要遵守的设计规范,它本身并没有定义任何新的网络协议和数据格式,相反,这套设计规范是建立在 HTTP、URI、XML 和 JSON 等一系列现有的网络协议和数据格式之上的。按照该设计规范的定义,一个基于 RESTful 架构的应用程序应该具备以下特征:
-
应用程序采用的是客户端-服务器(Client-Server)架构,其前服务端在业务逻辑上是各自独立的,它们的具体分工如下:
- 客户端负责的是应用程序的用户界面,它的主要任务是根据用户的操作向服务端请求指定的数据资源,并利用服务端返回的数据为用户提供良好的使用体验。
- 服务端负责的则是应用程序的数据存储和业务运算,它的主要任务是监听并响应客户端的请求,并利用服务器资源为用户提供海量数据存储与大规模运算的服务。
-
应用程序的客户端与服务端之间只能通过 HTTP 协议来进行数据交互,并且在交互数据时应该使用 XML 或 JSON 这一类通用数据格式。在具体交互过程中:
- 客户端在响应用户操作时应该始终以 URI 的形式向其服务端所在的服务器请求服务,并在请求时使用只使用 HTTP 协议提供的 GET、POST、PUT 和 DELETE 方法来传递自己的请求信息。
- 服务端则只能根据其客户端所使用的 HTTP 请求方法和 URI 来对存储在服务端的数据执行增、删、改、查等操作,并将处理结果作为响应数据返回给客户端。然后,由客户端将响应数据以某种友好、可读的方式反馈给用户。
根据上述设计规范,读者基本上可以认为我们在应用程序的服务端提供的 RESTful API 应该具备以下特性:
- 应用程序的客户端应使用 POST、GET、PUT 或 DELETE 等 HTTP 请求方法向服务端发送请求。
- 应用程序的客户端在发送请求时应统一使用直观简短的 URI 来表示自己要请求的资源。
- 应用程序的服务端应对 URI 的表现形式进行状态转换,并根据客户端使用的 HTTP 方法执行响应操作。
- 应用程序的客户端与服务端之间的数据传输应使用 XML、JSON 等通用的互联网媒体格式。
优势与劣势
正因为基于 RESTful 架构的应用程序所具备了上述特征,程序员们在开发和部署它们时才能获得一系列明显的优势,从而让 RESTful API 规范成为了当前设计应用程序服务端的主要解决方案之一。在这里,我们可以简单地将这些优势归纳如下:
-
接口统一:
这是 RESTful API 规范的设计初衷,它致力于让服务端业务逻辑以统一 API 的方式向客户端提供服务,这样就简化了系统架构,降低了应用程序客户端与服务端之间的耦合性,以便于程序员们在开发整个应用程序可进行模块化分工。 -
分层系统:
RESTful API 规范允许人们在应用程序的服务端构建基于多台服务器的分层系统服务。这意味着,应用程序的客户端通常不需要知道自己连接的是最终的服务器,还是某台资源请求路径上的中间服务器。这更有助于我们在部署和维护应用程序时设置更为稳妥的服务器负载策略和其他安全性策略。 -
便于缓存:
正是因为 RESTful API 规范允许人们构建一个分层系统,所以从客户端所在的计算设备到服务端最后一台服务器上所有的节点都可以对一些特定的常用数据进行缓存,以提高客户端 UI 与其服务端响应用户操作的速度。例如,我们可以在客户端对不经常变化的 CSS 样式文件进行缓存,以减少它向服务端发送的请求数量,提升 UI 的加载速度。另外,我们也可以在服务端中对经常要执行的数据库查询建立缓存,以提升其响应客户端请求的速度。 -
易于重构:
正是由于 RESTful API 规范帮助人们实现了应用程序的客户端与服务端在业务逻辑上的分离,降低它们之间的耦合度。这意味着,人们将来对客户端所进行的任何重构都基本上不会对服务端的实现产生影响,反之亦然。例如我们既可以根据智能手机,PC 等不同客户端设备重构出不同的客户端 UI,也可以在用 JavaScript 基于 Node.js 运行环境编写的程序无法满足性能需求时,转而改用 Python、Go 等更适用于大规模科学运算的编程语言重构服务端部分的业务逻辑。
当然了,关于 RESTful API 规范在应用程序设计中到底是呈现出优势还是劣势的问题,最终还得取决于程序员们对应用程序所做的具体设计方案。例如,由于 RESTful API 是基于 HTTP 这种无状态数据传输协议来进行通信的,这样做虽然有助于减低服务器的负担,并让应用程序服务端在业务逻辑上拥有更为独立的实现,但同时也意味着应用程序的服务端无法记录客户端的运行状态,客户端必须自行利用相关机制(例如 Web 浏览器的会话机制)来记录应用程序的运行状态,以便在必要时将运行状态通报给服务端,以减少一些不必要的响应数据,这算是在使用 RESTful 架构时需要会设法回避一个问题。
设计示例
好了,想必读者已经对上面这些概念性的长篇大论感到有些不耐烦了,是时候通过示例来具体演示一下基于 RESTful API 规范的设计过程了。接下来,假设我们要创建一个功能简单的短书评应用,那么从资源角度来考虑,该应用程序在服务端的数据库中至少应该包含用户(users
)、书籍(books
)和书评帖子(posts
)三张数据表,因此我们应该基于 RESTful API 规范为应用程序的客户端提供以下 API:
HTTP 请求方法 | 请求路径 | API 功能说明 |
---|---|---|
POST | /users/session |
用于实现用户登录功能。 |
POST | /users/newuser |
用于实现新用户注册功能。 |
GET | /users/<用户的ID> |
用于实现用户信息查看功能。 |
POST | /users/<用户的ID> |
用于实现用户信息修改功能。 |
DELETE | /users/<用户的ID> |
用于实现用户信息删除功能。 |
POST | /books/newbook |
用于实现添加新书籍的功能。 |
GET | /books/<书籍的ID> |
用于实现书籍信息查看功能。 |
POST | /books/<书籍的ID> |
用于实现书籍信息修改功能。 |
DELETE | /books/<书籍的ID> |
用于实现书籍信息删除功能。 |
GET | /books/list/ |
用于列出所有书籍。 |
POST | /posts/newpost |
用于实现添加新书评的功能。 |
GET | /posts/<书评的ID> |
用于实现书评帖子查看功能。 |
POST | /posts/<书评的ID> |
用于实现书评帖子修改功能。 |
DELETE | /posts/<书评的ID> |
用于实现书评帖子删除功能。 |
GET | /posts/userlist/<用户的ID> |
用于列出指定用户发表的所有书评。 |
GET | /posts/booklist/<书籍的ID> |
用于列出指定书籍下面的所有书评。 |
请注意,上述表格中列出的“请求路径”并非是一个完整的 URI。按照 REST 设计规范,完整的 URI 还应该包含调用 API 所使用的通信协议(通常是 HTTP 或 HTTPS),API 所在服务器的域名与端口号等相关信息。除此之外,如果我们还想兼顾 API 未来被重构之后可能引发的向后兼容问题,有时候也会选择在 URI 中加入版本信息[2]。例如,如果我们将 API 部署在localhost
这个域名下,服务端口为3000
,那么客户端想获取<用户的ID>
值为10
的个人信息,它使用 GET 方法发送 HTTP 请求的 URI 就应该是这样:
http://localhost:3000/v1/users/10
另外需要特别说明的是,人们在设计 RESTful API 时常常会下意识地犯一个设计理念上的错误,那就是将客户端发送的 URI 设计成一个调用服务器函数的“动作”,例如在要获取指定用户发表的所有书评帖子时,我们极有可能将 URI 中的请求路径写成类似于/posts/query?uid=10
这种形式,毕竟我们在 PHP、JSP 的时代一直是这么做的。但在 REST 设计规范中,表达调用的动作通常是由 HTTP 请求方法来传递的,URI 只用来指定客户端需要服务端服务提供的“资源”,所以它应该是一系列的名词,而非动词。
罗伊·托马斯·菲尔丁(Roy Thomas Fielding)是HTTP协议(1.0版和1.1版)的主要设计者,同时也是Apache服务器软件的作者之一,并曾经担任Apache基金会的第一任主席。他于2000年发表了一篇题为Architectural Styles and the Design of Network-based Software Architectures的博士论文,一直以来都被称为是Web服务设计领域的”圣经”,并进而对当今互联网时代的软件设计产生了深远的影响。 ↩︎
当然,更为规范的做法是在 HTTP 请求头信息的
Accect
字段中指定版本信息。因为API的不同版本,也可以被理解成同一种资源的不同表现形式,所以理论上似乎应该采用同一个URI,但通常在实际生产环境中,这些规范未必能得到如此严格的遵守。 ↩︎