本文是「架构风格:你真的懂REST吗?」的补充!

REST全称是Representational State Transfer,目前普遍接受的中文翻译为「表述性状态转移」!

即使翻译过来了,你依然有一堆疑问:

  • 什么是「表述性」的?
  • 什么是状态?
  • 什么是转移?
  • 转移的是什么?

所以本文试图回答如下几个问题:

  • 为什么要叫REST这个名字?
  • 什么是状态、资源、表述?
  • 以及它们之间有什么关系?
  • 什么是转移(Transfer)、变迁(transitions)?转移什么?变迁什么?

为什么叫REST?

为什么Fielding博士要取这么个难以理解的名字呢?其实REST论文的第六章给出了明确的答案:

REST was originally referred to as the "HTTP object model," but that name would often lead to misinterpretation of it as the implementation model of an HTTP server. The name "Representational State Transfer" is intended to evoke an image of how a well-designed Web application behaves: a network of web pages (a virtual state-machine), where the user progresses through the application by selecting links (state transitions), resulting in the next page (representing the next state of the application) being transferred to the user and rendered for their use.

REST本来是想叫「HTTP object model」的,但是这个名字会给人误解,让人误以为REST是一个HTTP服务器的实现。叫REST这个名字的目的是为了暗示一个「设计良好的Web应用」应该有怎样的行为:一个由web页面组成的网(一个虚拟状态机),用户通过选择链接在应用中前进(状态变迁),用户的选择会导致下一个页面(代表应用的下一个状态)被转移到用户端、并被渲染出来以供使用!


Tips:注意上面括号里的单词(state transitions),这里是transitions而不是transfer。transitions表示的是过渡、切换、变迁,比如场景的切换,就是从一个场景到了另一个场景。这里是从一个状态切换到了另一个状态。REST中文文档里,还是将其翻译成了「转移」,应该是不正确的!


网上很多讨论REST的文章或帖子,关注的点有两个:

  • Representational应该怎么翻译?是「表述」还是「表述性」
  • Transfer应该怎么翻译?是「转移」还是「传输」

而从上面这段话,你会发现,重点既不是Representational、也不是Transfer,而是State!你有没有觉得上面所提到的State和你平时所理解的State有差异?或者说比较违和?

我们都知道,要保证服务端的伸缩性,就要确保服务端是无状态的!如果是「无状态」的,那么为什么有「状态的变迁」呢?难道REST没有伸缩性?显然不是,要知道,Web可是现今伸缩性最好的系统!

所以这里所指的State与我们平时所说的State不是一个概念!这里的State是「应用状态」,我们所说的State是「资源状态」(这里所说的资源和REST中所指的资源也是不一样的,下面会说到)!

先说应用状态,在上文中,可以看到。应用状态指的是一个个的Web页面!Web页面上有链接,你点击链接后,这个链接所对应的「应用状态」会从服务器「转移」到客户端,渲染出来,展示给你。你就「切换」到了下一个「应用状态」!

所以「State Transfer」指的是:「应用状态」从服务端「转移」到了客户端,导致客户端的「应用状态」从当前状态「变迁」到了下一个状态

资源与表述

在解释「资源状态」之前,要先来解释一下什么是「资源」?什么是「表述」?

早期URI设计时,「资源」表示的是「文档」!它假设万维网里转移的都是文档!现在看来,显然不是!REST对「资源」进行了抽象!

一般我们对资源的理解是「可以在万维网里转移的任何内容」,比如:网页、图片、视频等!但实际上,REST论文中给出的定义和我们平常所理解的「资源」差异还是很大的!

REST论文中给出的解释:

The resource is not the storage object. The resource is not a mechanism that the server uses to handle the storage object. The resource is a conceptual mapping -- the server receives the identifier (which identifies the mapping) and applies it to its current mapping implementation (usually a combination of collection-specific deep tree traversal and/or hash tables) to find the currently responsible handler implementation and the handler implementation then selects the appropriate action+response based on the request content.

资源不是存储对象!也不是服务器处理存储对象的机制!资源是一个概念上的映射关系:服务器接收到标示符(这些标示符标示了这个映射关系),将其应用到当前的映射实现上(一般是特定集合【深度遍历的树和/或哈希表】的组合)来找到当前负责处理该请求的处理器、这个处理根据请求内容选择合适的动作+响应

我用一段伪代码来解释一下!

var mappingImpl = {'/pathA':handlerA,'/pathB':handlerB,'/pathC':handleC,...}

mappingImpl.get('/pathB').handle(req);
  • mappingImpl就是映射实现,包含了所有URI到处理器实现的映射关系!
  • '/pathB':handlerB这个映射关系就是「资源」!
  • mappingImpl.get('/pathB')就是根据URI获取到负责处理该请求的处理器
  • handle(req)就是根据请求内容来选择合适的动作+响应

按照这个定义的话,实际上REST中所指的「资源」和我们平常所指的「资源」根本不是同一个东西:

  • REST中的资源指的是URI到针对这个URI的处理器之间的映射关系
  • 我们平常所指的资源,对应到REST中实际上是这个处理器处理后的返回结果

你会发现,REST中的资源是个「动态」的东西,而我们所说的资源是个「静态」的东西,或者说就是个类型多样化的「文档」而已!

用伪代码表示的话就是:

var resourceMap = {'/pathA':resourceA,'/pathB':resourceB,'/pathC':resourceC,...}

resourceMap.get('/pathB');

实际上,现代Web应用中,绝大部分URI标示的都是动态的内容!所以在这一点上,REST对资源的定义更加的准确!

你可能会说,这两者的区别可能不是那么大,因为无论资源指的是关系还是处理结果,我们最终都是看到的是一样的内容!你确定吗?

假设资源就是我们平常所理解的「返回结果」:

  • 上面说到的,返回内容中包含了链接,客户端可以通过链接变迁到下一个状态,链接和资源怎么同时返回?
  • 如果URI指定的那个资源不存在的话,应该返回什么呢?
resourceMap.getOrDefault('/pathD','这里该返回什么?');

返回「空资源」?那什么是「空资源」,如何表示「空资源」?null吗?那客户端又如何处理这个特殊的null呢?或者说是一个「Null资源」?「Null资源」是资源吗?既然是没有资源,那还叫资源吗?是不是很拗口?

REST通过表述来解决这个问题:

  • 表述就是处理器返回的结果
  • 包含了链接(操作)和内容(响应)
  • 如果没有内容,那么返回的就是一个没有内容的表述
mappingImpl.getOrDefault('/pathD',defaultHandler).handle(req);
// 如果找不到对应的处理器就用默认处理器处理。
//如果找不到请求内容对应的响应,就返回一个没有内容的表述

上面所说的Web页面就是一种表述,也就是说表述是「应用状态」

所以对REST的理解应该是:通过链接,以表述的方式,将应用状态从服务端转移到客户端

解释完资源和表述,最后来解释一下「资源状态」!

资源状态

很多关于RESTful的博文,主要讲的是:

  • URI要怎么设计
  • 要用POST创建资源
  • 要用PUT更新资源
  • 要用DELETE删除资源
  • 要用GET获取资源

以User为例:

GET /users        // 获取用户列表
GET /user/1 // 获取ID为1的用户信息
POST /user // 创建用户
PUT /user/1 // 更新ID为1的用户信息
DELETE /user/1 // 删除ID为1的用户信息

这里的POST,PUT,DELETE改变的是「资源状态」!和上面所说的「应用状态」是两回事!很多地方都没有明确的区分开来!

记得当初看JVM的时候,也有类似的情况!JVM中有三个栈,虚拟机栈、操作数栈和本地方法栈。很多地方都直接叫做栈,这就导致了理解的混乱!前面说一个方法的调用就是将对应的栈帧入栈;后面又说指令的执行,对应的操作数入栈。实际上前一个栈指的是虚拟机栈,后一个栈指的是操作数栈!

在这里情况类似,状态没有区分开,也会导致理解的混乱!前面说「状态的转移」,这里又说「状态的改变」!一个是「应用状态的转移」,一个是「资源状态的改变」!

如果按照上面对资源的理解的话,这里的状态其实不应该叫「资源状态」,而应该是「内容状态」!

REST完整流程

下面以User的CRUD为例,来说明一下REST的完整流程:

  • 访问/users链接,服务端找到该URI对应的处理器,对请求进行处理,将包含了User列表内容的表述转移到客户端
  • 客户端展示该表述。表述中包含了到新增User页面的链接、到编辑User页面的链接、删除User的链接等操作链接
  • 用户点击到新增User页面的链接,服务端转移新增User的表述。客户端「应用状态」变迁到新增User页面
  • 用户填写信息,点击提交按钮。服务器处理该请求,将一条新的用户信息添加到数据库,导致了User「内容状态」变化。同时将新的包含了User列表内容的表述转移到客户端
  • 客户端「应用状态」又进入到了User列表页面
  • ......

参考资料

REST解惑的更多相关文章

  1. [C#解惑] #2 对象的初始化顺序

    谜题 在上一篇C#解惑中,我们提到了对象的初始化顺序.当我们创建一个子类的实例时,总是会先执行基类的构造函数,然后再执行子类的构造函数.那么实例字段是什么时候初始化的呢?静态构造函数和静态字段呢?今天 ...

  2. [C#解惑] #1 在构造函数内调用虚方法

    谜题 在C#中,用virtual关键字修饰的方法(属性.事件)称为虚方法(属性.事件),表示该方法可以由派生类重写(override).虚方法是.NET中的重要概念,可以说在某种程度上,虚方法使得多态 ...

  3. Python 包管理工具解惑

    Python 包管理工具解惑 本文链接:http://zengrong.net/post/2169.htm python packaging 一.困惑 作为一个 Python 初学者,我在包管理上感到 ...

  4. 《浅谈磁盘控制器驱动》,磁盘控制器驱动答疑解惑![2012.1.29完结]by skyfree

    <浅谈磁盘控制器驱动>,磁盘控制器驱动答疑解惑![2012.1.29完结]  https://www.itiankong.net/thread-178655-1-1.html Skyfre ...

  5. SAE上传web应用(包括使用数据库)教程详解及问题解惑

    转自:http://blog.csdn.net/baiyuliang2013/article/details/24725995 SAE上传web应用(包括使用数据库)教程详解及问题解惑: 最近由于工作 ...

  6. 【解惑】Java动态绑定机制的内幕

    在Java方法调用的过程中,JVM是如何知道调用的是哪个类的方法源代码? 这里面到底有什么内幕呢? 这篇文章我们就将揭露JVM方法调用的静态(static binding) 和动态绑定机制(auto ...

  7. word wrap 解惑

    源起 我们经常需要“修复”一个老生常谈的“bug”,那就是文本的自动换行问题.在专业术语上,这种期望得到的渲染现象被称作“word wrap”,即文本处理器有能力把超出页边的整个词自动传到下一行. 在 ...

  8. (译)iOS Code Signing: 解惑

    子龙山人 Learning,Sharing,Improving! (译)iOS Code Signing: 解惑 免责申明(必读!):本博客提供的所有教程的翻译原稿均来自于互联网,仅供学习交流之用,切 ...

  9. C#中的 ref 传进出的到底是什么 解惑篇

    今天在浏览博文时,看到这篇文章:C#中的ref 传进出的到底是什么 ? 在传对象时使用ref的疑问 另附言: 本文写于早上,就在想发布的那瞬间,靠,公司断网了,原来修改的部分丢失了. 网一断就是一天了 ...

  10. 【解惑】深入jar包:从jar包中读取资源文件

    [解惑]深入jar包:从jar包中读取资源文件 http://hxraid.iteye.com/blog/483115 TransferData组件的spring配置文件路径:/D:/develop/ ...

随机推荐

  1. zookeeper学习实践1-实现分布式锁

    引言 ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件.它是一个为分布式应用提供一致性服务的软件,提 ...

  2. git自动更新网站代码

    1.实现过程在linux上安装git服务.创建源版本库.从源版本库克隆得到网站目录,然后利用git中的hooks机制,在git push推送代码到源版本库的时候,触发编写的shell脚本,更新网站目录 ...

  3. Java基础重点知识之欣欣向然

    题目: 第一题:二重循环: public class Text01 { //完成歌曲排序的效果 public static void main(String[] args) { String[] zi ...

  4. CentOS 7运维管理笔记(6)----Apache 基于 IP 的虚拟主机配置

    Apache 配置虚拟主机支持3种方式:基于IP的虚拟主机配置,基于端口的虚拟主机配置,基于域名的虚拟主机配置.本篇随笔记录自己基于IP的虚拟主机配置. 如果同一台服务器有多个IP,可以使用基于IP的 ...

  5. [MFC]选择目录对话框和选择文件对话框

    在MFC编程中经常会需要用到选择目录和选择文件的界面,以下总结一下本人常用的这两种对话框的生成方法: 选择目录对话框 //选择目录按钮void CDcPackerDlg::OnBnClickedDec ...

  6. git rebase vs git merge详解

    https://medium.com/@porteneuve/getting-solid-at-git-rebase-vs-merge-4fa1a48c53aa#.std3ddz0g 请参考另外一篇文 ...

  7. SQL点点滴滴_修改数据库的兼容级别

    语法 ALTER DATABASE database_name SET COMPATIBILITY_LEVEL = { 80 | 90 | 100 } 参数 database_name 要修改的数据库 ...

  8. 【Leetcode】【Medium】Flatten Binary Tree to Linked List

    Given a binary tree, flatten it to a linked list in-place. For example,Given 1 / \ 2 5 / \ \ 3 4 6 T ...

  9. simotion连接 V90 1FL6 增量型电机,报警20025 编码器细分设置

    V90 1FL6 增量型电机 The configured fine resolution for Gx_XIST1 (Encoder_N.absEncoder.absResolutionMultip ...

  10. zt 我们要找出毒素的藏身处,尽快把它赶出身体

    在中医看来,我们体内有很多毒素,凡是不能及时排出体外.对我们的身体和精神会产生不良作用的物质都可以称为“毒”,例如瘀血.痰湿.寒气.食积.气郁. 上火.这些毒素堆积在五脏之内,就会加速五脏的衰老,然后 ...