什么是RESTful架构?

RESTful 架构,是目前最流行的一种互联网软件架构。它基于REST原则,结构清晰、符合标准、易于理解、扩展方便,正得到越来越多网站的采用。

在传统上,软件和网络是两个不同的领域,很少有交集;软件开发主要针对单机环境,网络则主要研究系统之间的通信。互联网的兴起,使得这两个领域开始融合,越来越多的人开始意识到,网站即软件,而且是一种新型的软件。

2000年,Roy Thomas Fielding(HTTP 协议的主要设计者、Apache 基金会的第一任主席)在他的博士论文中提出了「REST」这一概念;对于论文的写作目的,他有如下的描述:

   我这篇文章的写作目的,就是想在符合架构原理的前提下,理解和评估以网络为基础的应用软件的架构设计,得到一个功能强、性能好、适宜通信的架构。

Fielding将他对互联网软件的架构原则,定名为 REST,即 Representational State Transfer 的缩写。这里对这个词组的翻译是「表现层状态转化」。

如果一个架构符合REST原则,就称它为 RESTful 架构;也就是说,只要我们弄清楚了 Representational State Transfer 这个词组的含义,也就弄明白了 RESTful 架构是怎么一回事。

这里先对 REST 原则进行描述,再对 RESTful API 的设计方法展开讨论。

REST 架构原则

前面提到,REST 其实是「Representational State Transfer」词组的缩写,我们可以从下面三个部分入手,理解这个词组的含义:

资源(Resource)

REST 可译作“表现层状态转换“,其实对主语还进行了一层省略:这里的“表现层”指的就是「资源(Resource)」的「表现层」。

在 REST 架构原则看来,网络上的每个具体信息都是一个实体,也就是资源;资源可以是一个文本、一段文字、一张图片、一种服务,总之就是一个具体的实体。我们用一个 URI (唯一资源标示符)来唯一指向特定的某个资源。在使用时,我们应保证 uri 具有良好的可读性和标示性。

下面给出一个 github 的链接示例:

https://github.com/git/git/commit/e3af72cdafab5993d18fae056f87e1d675913d08

上述链接便很清晰明了,标示了 git 上一次特定的 commit 请求。

所谓的“上网”,其实就是与网络上的“资源”进行交互,对“资源”的 uri 进行调用。

表现层(Representation)

上面提到,「Resource」是网络上某种信息的实体,但即使对于同一种资源,我们会希望能够它能以不同的形式呈现在用户面前。而这种将「资源」具体呈现出来的形式,便称为表现层(Representation)

例如,现有一段文字信息,对于这个「文字资源」,在进行数据传输时我们希望它能够以 JSON 进行传输,而在存储时我们又可能希望它能够以 XML 或是 txt 形式存储在本地。

对于一个资源,我们用一个 URI 来标示它的实体,但 URI 不能标示资源的形式。套用上面的例子,文字资源的 uri 只能让服务器或客户端准确地找到这个资源,但服务器和客户端不知道这个资源是哪种形式的。也就是说,用户真正需要看到的并不仅仅是资源,而是资源的特定 Representation

很多网站直接在 URL 中直接指定资源的表现形式,例如 CSDN 和博客园,每篇博客的 URL 后都可以看到「.html」的后缀:

https://www.cnblogs.com/bellkosmos/p/5237146.html

严格意义上来说这样的后缀名是不规范的:因为 URI 应该只代表资源的地址,「.html」后缀代表文字信息的格式,属于「表现层」的范畴;一个资源的具体表现形式,应该在 HTTP 请求的头信息中,使用 Accept 和 Content-Type 字段来指定

下面的 ajax 代码就是一个很好的例子,它通过 Content-Type 字段指定 HTTP 的 body 是一个 JSON 格式、且采用 utf-8 字符集编码的文本信息:

$.ajax({
type: "POST",
url: "/users",
contentType: "application/json;charset=utf-8",
data:{
"name": "test",
"pwd": "123456"
},
dataType: "json",
success:function (message) {
alert("提交成功"+JSON.stringify(message));
},
error:function (message) {
alert("提交失败"+JSON.stringify(message));
}
});

状态转换(State Transfer)

了解了「Resource」和「Presentation」后,我们便可以按照自己的需要获取到我们理想状态的数据,但现实生活中,用户访问网站实际上是一个客户端和服务器之间的互动过程,这个过程中双方不断的建立连接进行通信,势必会涉及到数据和状态的变化。

这里涉及到「状态」这个可能引起困惑的名词,我们将其和 REST 原则中的「无状态通信原则」放在一起讨论。

无状态通信原则

首先需要说明的是,这里说的无状态通信原则,并不是说客户端应用不能有状态,而是指服务端不应该保存客户端状态。同时,互联网的基石,HTTP 协议,也是一个「无状态」协议。

「状态」指的是什么?

实际上,「状态」应该区分为应用状态资源状态,客户端负责维护应用状态,而服务端维护资源状态。「应用状态」便是用户在客户端进行的种种业务操作,例如对数据的更新操作;而在客户端,当对一个数据进行诸如更新等操作后,我们就可以说「资源状态」发生了改变。

什么是「无状态通信」?

从名字的意义相近,在「无状态通信」中,客户端与服务端的交互必须是无状态的,并在每一次请求中包含处理该请求所需的一切信息。

  • 「无状态」的交互

    这样以来,服务端不需要在请求间保留应用状态,只有在接受到实际请求的时候,服务端才会关注应用状态,并且根据应用状态来令资源状态作出相应的改变。

  • 每次请求中包含这次请求所需的所有信息

    这个比较好理解,应为服务端不会保存客户端的状态,所以每次请求对服务端来说都是独立存在的;如果当前请求中的信息不足以让服务端完成这个请求,那么这个请求将无效。

这种无状态通信原则,使得服务端和中介能够理解独立的请求和响应。 在多次请求中,同一客户端也不再需要依赖于同一服务器,方便实现高可扩展和高可用性的服务端。

当然,我们也有需要违法无状态通信的业务场景,例如「登录」操作,如果是遵循无状态通信,那么用户每次进行业务操作都需要重新进行登录,无疑是非常反人类的体验。在这种类似的场景中,我们可以使用「session」和「cookie」来跟踪和保存会话状态。

看到这里,状态转移应该就很好理解了:在客户端和服务器进行交互时,如果客户端的「应用状态」发生变化(如用户注册一个新账号提交了一个表单数据),程序就需要让服务器的「资源状态」按照客户端要求发生「状态转换」(也就是 CREATE 一个新的账号数据)。而这种转换是建立在表现层之上的,所以是「表现层状态转换」,也就是「REST」。

那么,对「REST」这个词组大家应该有一个大致的了解了,那再看看不难猜到「REST(表现层状态转换)」架构原则是做什么的了:对程序员(针对资源表现层的)程序中编写的状态转换方法作出约束,建立一个大家都应该去遵守的标准。

而在当今的互联网架构下,客户端能用到的手段,只有 HTTP 协议。简单来说,就是HTTP协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。它们分别对应四种基本操作:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源。

如果在编程中我们能够做到遵循这一原则,假设我们希望对一辆小车的图片进行查询,我们设计的 API 可能是这样的:

[GET] /img/car

如果我们需要希望删除这个图片,我们只需要将「GET」改成使用「DELETE」方法即可,这种 API 将资源定位和资源操作相分离,能够更加简洁直观地展示指令的需求。

更具体的便是 RESTful API 设计考虑的内容了,我们会在后续进行讨论。

RESUful API 的设计

RESTful 的核心思想就是,客户端发出的数据操作指令都是「动词 + 宾语」的结构。比如,GET /articles这个命令,GET是动词,/articles是宾语。按照之前提到的 REST 原则,「动词」便是「状态转换」,「宾语」就是「表现层」,连起来就是「表现层状态转换」,也就是 REST 了。

动词规约:统一资源接口

对于所有的资源,都是使用相同的接口进行访问,动词指的都是固定五种 HTTP 方法,对应数据的 CRUD 操作,这便是 RESTful架构的「统一接口原则」。

  • GET:读取(Read)
  • POST:新建(Create)
  • PUT:更新(Update)
  • PATCH:更新(Update),通常是部分更新
  • DELETE:删除(Delete)

按照 HTTP 的规范,动词一律采用大写。

宾语规约:必须是名词

宾语就是 API 的 URI,是 HTTP 动词作用的对象。按照 REST 原则,URI 应该只能起到对资源及其表现层进行唯一标示的作用。也就是说,它应该是名词,不能是动词,因此下面 URI 中含有 get、create等动词,都是不符合要求的 URI:

[GET] /getUser/1
[POST] /createUser
[PUT] /updateUser/1
[DELETE] /deleteUser/1

URI 既可以看成是资源的地址,也可以看成是资源的名称。如果某些信息没有使用 URI 来表示,那它就不能算是一个资源, 只能算是资源的一些信息而已。

对 RESTful 进行基础的了解后,我们来看看 URI 的一些设计技巧:

URI 设计技巧

多级 URI 的设计

多级 URI 设计这一块网络上有多种说法,这里我认为 github 的规范应该足以作为我们的标准:

  • 在页面间进行跳转时,使用「/」来表示资源的层级关系

    例如,我们需要查询「octokit」用户的所有仓库:

    [GET] /octokit

    我们会看到如下页面:

    我们点击其第一个仓库「rest.js」,这时浏览器进行了页面跳转:

    可以看到 github 这里进行使用「/」进行分割 URI,上面页面的 URI 是这样的:

    [GET] /octokit/rest.js
  • 在当前页面进行过滤时,使用「?」用来过滤资源

    看下面这样一个 github 页面,它是上面仓库的 issue 页面,它的 URI 是/octokit/rest.js/issues

    假设这时我们希望查看所有 「Closed」的 issue,按照前面分级 URI 的思想,很容易写出这样的 URI:

    [GET] /octokit/rest.js/issues/closed

    这种 URL 不利于扩展,语义也不明确,往往要想一会,才能明白含义。

    更好的做法是,除了第一级,其他级别都用查询字符串表达:

    [GET] /octokit/rest.js/issues?status=closed

    这里用「?」对资源过滤,这种URL通常对应的是一些特定条件的查询结果或算法运算结果。看看 github 中的实现:

    可以看到,github 中使用了「?」来进行过滤,为/octokit/rest.js/issues?q=is%3Aissue+is%3Aclosed,只是过滤字段我们前面预估有一些不同。

HTTP 的 CRUD 动词无法表述的业务需求

如果某些动作是 HTTP 动词表示不了的,你就应该把动作做成一种资源。

比如网上汇款,从账户1向账户2汇款500元,错误的URI是:

[POST] /accounts/1/transfer/500/to/2

正确的写法是把动词「transfer」改成名词「transaction」,资源不能是动词,但是可以是一种服务

[POST] /transaction/from=1&to=2&amount=500.00

对需要获取不同版本的资源

一个常见的设计误区,就是在URI中加入版本号:

http://www.example.com/app/1.0/foo
http://www.example.com/app/1.1/foo
http://www.example.com/app/2.0/foo

因为不同的版本,可以理解成同一种资源的不同表现形式,所以应该采用同一个URI。版本号可以在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

总结

最后,我们再总结一下 RESTful 架构:

  1. 每一个 URI 代表一种资源;
  2. 客户端和服务器之间,传递这种资源的某种表现层;
  3. 客户端通过五个 HTTP 动词,对服务器端资源进行操作,实现「表现层状态转化」。

参考链接:

理解RESTful架构

RESTful API 最佳实践

RESTful 架构详解

http协议无状态中的 "状态" 到底指的是什么?!

REST架构原则初探的更多相关文章

  1. Magento 架构原则

    Magento架构原则 >OOP体系结构和编程原则OOP体系结构和编程原则面向对象编程(OOP)设计允许软件组件具有最大的灵活性和可扩展性,允许您设计和实现高度定制的网站.面向对象原则的优点包括 ...

  2. JavaScript全讲-架构原则解析

    因为近期一直在忙,非常久没有更新,见谅. 上篇我们讲完JavaScript函数式编程的特性,今天我们就来聊聊JavaScript中的架构. 提到JavaScript架构.非常多人会认为不可思议,由于架 ...

  3. APP系统架构设计初探

    一,图片体验的优化. 在手机上显示图片,速度是一个非常重要的体验点,试想,如果您打开一个网站,发现里面的图片一直显示失败或者是x,稍微做得好一点的,可能是一个不消失的loading或者是菊花等等,但不 ...

  4. Twitter的支撑架构:扩展网络与存储并提供服务——架构原则:一次性将事情做对,NFL原则 LSM+B+存储替代cassandra

    Twitter工程团队近期提供了Twitter核心技术的演进和扩展的详细资料,这些核心技术支撑了Twitter自营数据中心的系统架构,用于提供社会媒体服务.他们分享的关键经验包括:超越原始规格和需求进 ...

  5. UML-架构分析-架构原则

    1.高内聚 2.低耦合 3.防止变异(间接性等) 4.关注点分离 方法1: 事物模块化,封装到单独的子系统中 方法2: 装饰者模式 方法3: 面向方面(AOP)

  6. Srinath总结 架构师们遵循的 30 条设计原则

    作者:Srinath 翻译:贺卓凡,来源:公众号 ImportSource Srinath 通过不懈的努力最终总结出了 30 条架构原则,他主张架构师的角色应该由开发团队本身去扮演,而不是专门有个架构 ...

  7. 【转】Apache的架构师们遵循的30条设计原则

    本文作者叫Srinath,是一位科学家,软件架构师,也是一名在分布式系统上工作的程序员. 他是Apache Axis2项目的联合创始人,也是Apache Software基金会的成员. 他是WSO2流 ...

  8. 厉害了,Apache架构师们遵循的 30 条设计原则

    作者:Srinath 翻译:贺卓凡,来源:公众号ImportSource Srinath通过不懈的努力最终总结出了30条架构原则,他主张架构师的角色应该由开发团队本身去扮演,而不是专门有个架构师团队或 ...

  9. Apache架构师的30条设计原则

    本文作者叫 Srinath,是一位科学家,软件架构师,也是一名在分布式系统上工作的程序员. 他是 Apache Axis2 项目的联合创始人,也是 Apache Software 基金会的成员. 他是 ...

随机推荐

  1. Installation Manager1.8安装

    1.下载地址: https://www-01.ibm.com/marketing/iwm/iwm/web/download.do?S_PKG=500005026&source=swerpws- ...

  2. Servlet快速入门:第一个Servlet程序

    Servlet是整个JavaWeb开发的核心,同时也是一套规范,即公共接口.用于处理客户端发来的请求并作出响应.通常情况下我们会发送不同的请求并交由不同的处理程序来处理,例如处理用户信息和处理订单信息 ...

  3. windows 下 redis 的安装及使用

    1.下载及安装redis 下载地址:https://github.com/dmajkic/redis/downloads 找到对应的版本下载安装 打开cmd窗口,用cd命令进入到安装redis的根目录 ...

  4. 他爬取了B站所有番剧信息,发现了这些……

    本文来自「楼+ 之数据分析与挖掘实战 」第 4 期学员 -- Yueyec 的作业.他爬取了B站上所有的番剧信息,发现了很多有趣的数据- 关键信息:最高播放量 / 最强up主 / 用户追番数据 / 云 ...

  5. java - day019 - 反射

    网络程序,难点在线程 反射 reflect 实用 类对象 来执行反射操作 反射获得一个类的定义信息 反射创建对象 反射调用成员变量, 方法 方法 获得类对象的三种方式 A.class Class.fo ...

  6. BZOJ 2321 星器

    星器 思路: 势能分析法. 假设每颗星星的势能为\(x^2+y^2\) 那么对于一行的两颗星星\((i, j), (i, k), j < k\) 它转移到\((i, j+1), (i, k-1) ...

  7. nginx url默认去掉index.php

  8. P2P system: Chord

    DHT= Distributed Hash Table store the objects(files) at nodes (hosts, machines) in a cluster. The cl ...

  9. Prometheus+Grafana监控

    什么是Prometheus? Prometheus是由SoundCloud开发的开源监控报警系统和时序列数据库(TSDB).Prometheus使用Go语言开发,是Google BorgMon监控系统 ...

  10. Tomcat报错:Java.long.nullpointerException,详细是resp.getwriter.write报错

    报错原因: 空指针 在out.write(name);时,name不能为null,哪怕你随便给name赋值为xxoo啥的都可以,不要为null 改正:String name = "fail& ...