2.2.1 理解REST
个人会有好恶偏爱,但计算机科学是务实的,有了RPC,还会提出REST,有了面向过程编程之后还会产生面向资源编程,并引起广泛的关注、使用和讨论,说明后者一定是有一些前者没有的闪光点,或者解决、避免了一些前者的缺陷。我们不妨先去理解REST为什么会出现,再来讨论评价它。
REST源于Roy Thomas Fielding在2000年发表的博士论文“Architectural Styles and the Design of Network-based Software Architectures”[1],此文的确是REST的源头,但我们不应该忽略Fielding的身份和他此前的工作背景,这些信息对理解REST的设计思想至关重要。
首先,Fielding是一名很优秀的软件工程师,他是Apache服务器的核心开发者,后来成为著名的Apache软件基金会的联合创始人;同时,Fielding也是HTTP 1.0协议(1996年发布)的专家组成员,后来还晋升为HTTP 1.1协议(1999年发布)的负责人。HTTP 1.1协议设计得极为成功,以至于在发布之后长达十年的时间里,都没有收到多少修订的意见。用来指导HTTP 1.1协议设计的理论和思想,最初是以备忘录的形式供专家组成员之间交流,除了IETF、W3C的专家外,并没有在外界广泛流传。
从时间上看,对HTTP 1.1协议的设计工作贯穿了Fielding的整个博士研究生涯,当起草HTTP 1.1协议的工作完成后,Fielding回到了加州大学欧文分校继续攻读自己的博士学位。第二年,他更为系统、严谨地阐述了这套理论框架,同时以这套理论框架导出了一种新的编程思想,并为这种程序设计风格取了一个很多人难以理解,但是今天已经广为人知的名字——REST(Representational State Transfer,表征状态转移)。
哪怕对编程和网络都很熟悉的同学,也不太可能直接从名字弄明白什么叫“表征”、什么东西的“状态”、从哪“转移”到哪。尽管在论文中确有论述这些概念,但写得相当晦涩[2],所以笔者比较推荐先理解什么是HTTP,再配合一些实际例子来对两者进行类比,以更清楚地了解REST,你会发现REST实际上是“HTT”(Hypertext Transfer)的进一步抽象,两者的关系就如同接口与实现类的关系一般。
HTTP中使用的“超文本”(Hypertext)一词是美国社会学家Theodor Holm Nelson在1967年于“Brief Words on the Hypertext”一文里提出的,下面引用的是他本人在1992年修正后的定义:
额外知识
现在,“超文本”一词已被普遍接受,它指的是能够进行分支判断和差异响应的文本,相应地,“超媒体”一词指的是能够进行分支判断和差异响应的图像、电影和声音(也包括文本)的复合体。
——Theodor Holm Nelson,Literary Machines,1992
以上定义描述的“超文本(或超媒体)”是一种“能够对操作进行判断和响应的文本(或声音、图像等)”,这个概念在20世纪60年代提出时应该还属于科幻的范畴,但是今天大众已经完全接受了它,互联网中一段文字可以点击、可以触发脚本执行、可以调用服务端已毫不稀奇。下面我们继续尝试从“超文本”或者“超媒体”的含义来理解什么是“表征”以及REST中的其他关键概念,这里使用一个具体事例将其描述如下。
·资源(Resource):譬如你现在正在阅读一篇名为《REST设计风格》的文章,这篇文章的内容本身(你可以将其理解为蕴含的信息、数据)称之为“资源”。无论你是通过阅读购买的图书、浏览器上的网页还是打印出来的文稿,无论是在电脑屏幕上阅读还是在手机上阅读,尽管呈现的样子各不相同,但其中的信息是不变的,你所阅读的仍是同一份“资源”。
·表征(Representation):当你通过浏览器阅读此文章时,浏览器会向服务端发出“我需要这个资源的HTML格式”的请求,服务端向浏览器返回的这个HTML就被称为“表征”,你也可以通过其他方式拿到本文的PDF、Markdown、RSS等其他形式的版本,它们同样是一个资源的多种表征。可见“表征”是指信息与用户交互时的表示形式,这与我们软件分层架构中常说的“表示层”(Presentation Layer)的语义其实是一致的。
·状态(State):当你读完了这篇文章,想看后面是什么内容时,你向服务端发出“给我下一篇文章”的请求。但是“下一篇”是个相对概念,必须依赖“当前你正在阅读的文章是哪一篇”才能正确回应,这类在特定语境中才能产生的上下文信息被称为“状态”。我们所说的有状态(Stateful)抑或是无状态(Stateless),都是只相对于服务端来说的,服务端要完成“取下一篇”的请求,要么自己记住用户的状态,如这个用户现在阅读的是哪一篇文章,这称为有状态;要么由客户端来记住状态,在请求的时候明确告诉服务端,如我正在阅读某某文章,现在要读它的下一篇,这称为无状态。
·转移(Transfer):无论状态是由服务端还是由客户端来提供,“取下一篇文章”这个行为逻辑只能由服务端来提供,因为只有服务端拥有该资源及其表征形式。服务端通过某种方式,把“用户当前阅读的文章”转变成“下一篇文章”,这就被称为“表征状态转移”。
通过“阅读文章”这个例子,相信你应该能够理解“表征状态转移”的含义了。借着这个故事的上下文状态,笔者再继续介绍几个现在不涉及但稍后要用到的概念。
·统一接口(Uniform Interface):上面说的服务端“通过某种方式”让表征状态转移,那具体是什么方式呢?如果你真的是用浏览器阅读本文电子版的话,请把本文滚动到结尾处,右下角有下一篇文章的URI超链接地址,这是服务端渲染这篇文章时就预置好的,点击它让页面跳转到下一篇,就是所谓“某种方式”的其中一种方式。任何人都不会对点击超链接网页出现跳转感到奇怪,但你细想一下,URI的含义是统一资源标识符,是一个名词,如何能表达出“转移”动作的含义呢?答案是HTTP协议中已经提前约定好了一套“统一接口”,它包括GET、HEAD、POST、PUT、DELETE、TRACE、OPTIONS七种基本操作,任何一个支持HTTP协议的服务器都会遵守这套规定,对特定的URI采取这些操作,服务器就会触发相应的表征状态转移。
·超文本驱动(Hypertext Driven):尽管表征状态转移是由浏览器主动向服务器发出请求所引发的,该请求导致了“在浏览器屏幕上显示出了下一篇文章的内容”的结果。但是,我们都清楚这不可能真的是浏览器的主动意图,浏览器是根据用户输入的URI地址来找到网站首页,读取服务器给予的首页超文本内容后,浏览器再通过超文本内部的链接来导航到这篇文章,阅读结束时,也是通过超文本内部的链接再导航到下一篇。浏览器作为所有网站的通用的客户端,任何网站的导航(状态转移)行为都不可能是预置于浏览器代码之中,而是由服务器发出的请求响应信息(超文本)来驱动的。这点与其他带有客户端的软件有十分本质的区别,在那些软件中,业务逻辑往往是预置于程序代码之中的,有专门的页面控制器(无论在服务端还是在客户端中)来驱动页面的状态转移。
·自描述消息(Self-Descriptive Message):由于资源的表征可能存在多种不同形态,在消息中应当有明确的信息来告知客户端该消息的类型以及应如何处理这条消息。一种被广泛采用的自描述方法是在名为“Content-Type”的HTTP Header中标识出互联网媒体类型(MIME type),譬如“Content-Type:application/json;charset=utf-8”说明该资源会以JSON的格式来返回,请使用UTF-8字符集进行处理。
除了以上列出的这些概念外,在理解REST的过程中,还有一个常见的误区值得注意:Fielding提出REST时所谈论的范围是“架构风格与网络的软件架构设计”(Architectural Styles and Design of Network-based Software Architecture),而不是现在被人们所狭义理解的一种“远程服务设计风格”,这两者的范围差别就好比本书所谈论的话题“软件架构”与本章谈论话题“访问远程服务”的关系那样,前者是后者的一个很大的超集,尽管基于本节的主题和多数人的关注点考虑,我们确实会以“远程服务设计风格”作为讨论的重点,但至少应该说清楚它们范围上的差别。
[1] 下载地址:https://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm。
[2] 不想读英文的同学从此处获得中文翻译版本:https://www.infoq.cn/article/2007/07/dlee-fielding-rest/。