REST即表述性状态传递(英文:Representational State Transfer,简称REST)是Roy Fielding博士在2000年他的博士论文中提出来的一种
软件架构风格。它是一种针对
网络应用的设计和
开发方式,可以降低开发的复杂性,提高系统的
可伸缩性。
基本含义
REST 这个概念于 2000 年由 Roy Fielding( HTTP规范的主要编写者之一)在就读
加州大学欧文分校期间在学术论文“Architectural Styles and the Design of Network-based Software Architectures”首次提出。论文中对使用 Web 服务作为分布式
计算平台的一系列
软件体系结构原则进行了分析,其中提出的 REST 概念并没有获得太多关注。
REST是在Browser/Server的基础上添加了另外3个规范性的组成,第一个为统一接口,第二个为分层系统,第三个为按需代码。
统一接口为REST定义了对系统资源进行操作统一的方法和链接入口,REST架构的核心就是资源,它将互联网中所有的可访问、操作的数据信息都看作资源进行处理,从而简化了REST对不同数据信息的处理方式和过程,也为REST的高度 重用性以及不同分布式异构系统的高交互性奠定了基础。
分层系统的定义使得web Service的定义和实现Web系 统不同的层次之间具有良好的独立性,从而降低了系统层次依赖耦合性和复杂性,而良好的接口封装、应用功能实现等干扰性大大降低,从而为Web系统的可维护性、扩展性等奠定了良好的基础。
按需代码则是web Service可选的要求,通过按需代码开发者可以在客户端的应用程序进行功能扩展,从而实现对客户需求的满足,从而使得系统更加人性化,提升其友好性。
使用原则
大部分对REST的介绍是以其正式的定义和背景作为开场的。这里提出一个简要的定义:REST定义了Web的使用标准(这和大多数人的实际使用方式有很大不同),例如HTTP和URI。如果在设计应用程序时能坚持REST原则,那就预示着将会得到一个使用了优质Web架构(用户受益)的系统。
REST 并非始终是正确的选择。 它作为一种设计 Web 服务的方法而变得流行,这种方法对专有
中间件(例如某个应用程序服务器)的依赖比基于
SOAP 和 WSDL 的方法更少。 在某种意义上,通过强调URI和HTTP等早期 Internet 标准,REST 是对大型应用程序服务器时代之前的 Web 方式的回归。 正如已经在所谓的基于 REST 的接口
设计原则中研究过的一样,XML over HTTP 是一个功能强大的接口,允许内部应用程序(例如基于 Asynchronous JavaScript + XML (Ajax) 的自定义
用户界面)轻松连接、定位和使用资源。 事实上,Ajax 与 REST 之间的完美配合已增加了当今人们对 REST 的注意力。
通过基于 REST 的 API 公开
系统资源是一种灵活的方法,可以为不同种类的应用程序提供以标准方式格式化的数据。 它可以帮助满足集成需求(这对于构建可在其中容易地组合 (
Mashup) 数据的系统非常关键),并帮助将基于 REST 的
基本服务集扩展或构建为更大的集合。
定义规则
REST中的资源所指的不是数据,而是数据和表现形式的组合,比如“最新访问的10位会员”和“最活跃的10位会员”在数据上可能有重叠或者完全相同,而由于他们的表现形式不同,所以被归为不同的资源,这也就是为什么REST的全名是Representational State Transfer的原因。资源
标识符就是
URI(Uniform Resource Identifier),不管是图片,Word还是
视频文件,甚至只是一种虚拟的服务,也不管是
XML(
标准通用标记语言下的一个子集)格式、
txt文件格式还是其它文件格式,全部通过 URI对资源进行唯一的标识。
对资源使用一致的命名规则(naming scheme)最主要的好处就是不需要提出自己的规则——而是依靠某个已被定义,在全球范围中几乎完美运行,并且能被绝大多数人所理解的规则。想一下构建的上一个应用(假设它不是采用
RESTful方式构建的)中的任意一个高级对象(high-level object),那就很有可能看到许多从使用唯一标识中受益的
用例。比如,如果应用中包含一个对顾客的抽象,那么可以相当肯定,用户会希望将一个指向某个顾客的链接,能通过
电子邮件发送到同事那里,或者加入到浏览器的书签中,甚至写到纸上。更透彻地讲:如果在一个类似于
Amazon的在线商城中,没有用唯一的ID(一个URI)标识它的每一件商品,可想而知这将是多么可怕的
业务决策。
当面对这个原则时,许多人惊讶于这是否意味着需要直接向外界暴露数据库记录(或者数据库记录ID)——自从多年以来面向对象的实践经验,要将持久化的信息作为实现细节隐藏起来之后,哪怕是刚有点想法都
常会使人惊恐。但是这条原则与隐藏实现细节两者之间并没有任何冲突:通常,值得被URI标识的事物——资源——要比数据库记录抽象的多。例如,一个订单资源可以由订单项、地址以及许多其它方面(可能不希望作为单独标识的资源暴露出来)组成。标识所有值得标识的事物,领会这个观念可以进一步引导用户创造出在传统的
应用程序设计中
不常见的资源:一个流程或者流程步骤、一次销售、一次谈判、一份报价请求——这都是应该被标识的事物的示例。同样,这也会导致创建比非RESTful设计更多的持久化实体。
下面是一些用户可能想到的URI的例子:
注:网址中的“*”代表“.”
http://example*com/customers/1234
http://example*com/orders/2007/10/776654
http://example*com/products/4554
http://example*com/processes/salary-
increase-234
正如选择了创建便于阅读的URI——这是个有用的观点,尽管不是RESTful设计所必须的——应该能十分容易
地推测出URI的含义:它们明显地标识着单一“
数据项”。但是再往下看:
http://example*com/orders/2007/11
http://example*com/products?color=green
首先,这两个URI看起来与之前的稍有不同——毕竟,它们不是对一件事物的标识,而是对一类事物集合的标识(假定第一个URI标识了所有在2007年11月份提交的定单,第二个则是绿颜色产品的集合)。但是这些集合自身也是事物(资源),也应该被标识。
注意,使用唯一、全局统一的命名规则的好处,既适用于浏览器中的Web应用,也适用于机对机(machine-to-machine,m2m)通信。
所有链接一起
接下来要讨论的原则有一个有点令人害怕的正式描述:“
超媒体作为应用状态的引擎(Hypermedia as the engine of application state)”,有时简写为HATEOAS。严格地说这个描述的核心是超媒体概念,换句话说:是链接的思想。链接是在
HTML(
标准通用标记语言下的一个应用)中常见的概念,但是它的用处绝不局限于此(用于人们网络浏览)。
应用程序(已经检索过文档)如何“跟随”链接检索更多的信息。当然,如果使用一个遵守专用命名规范的简单“id”属性作为链接,也是可行的——但是仅限于应用环境之内。使用URI表示链接的优雅之处在于,链接可以指向由不同应用、不同服务器甚至位于另一个大陆上的不同公司提供的资源——因为URI命名规范是全球标准,构成Web的所有资源都可以
互联互通。
超媒体原则还有一个更重要的方面——应用“状态”。简而言之,实际上服务器端(也可以叫
服务提供者)为客户端(服务消费者)提供一组链接,使客户端能通过链接将应用从一个状态改变为另一个状态。
对此原则总结如下:任何可能的情况下,使用链接指引可以被标识的事物(资源)。
使用标准方法
在前两个原则的讨论中暗含着一个假设:接收
URI的
应用程序可以通过URI明确地做一些有意义的事情。如果在
公共汽车上看到一个URI,可以将它输入浏览器的
地址栏中并回车——但是浏览器如何知道需要对这个URI做些什么。
它知道如何去处理URI的原因在于所有的资源都支持同样的接口,一套同样的方法(也可以称为操作)集合。在HTTP中这被叫做动词(verb),除了两个大家熟知的(GET和POST)之外,
标准方法集合中还包含PUT、DELETE、HEAD和OPTIONS。这些方法的含义连同行为许诺都一起定义在HTTP规范之中。一名
OO开发人员,就可以想象到
RESTful HTTP方案中的所有资源都继承自类似于这样的一个类(采用类
Java、C#的伪语法描述,请注意关键的方法):
由于所有资源使用了同样的接口,可以依此使用GET方法检索一个表述(
representation)——也就是对资源的描述。因为规范中定义了GET的语义,所以可以肯定当调用它的时候不需要对后果负责——这就是为什么可以“安全”地调用此方法。GET方法支持非常高效、成熟的缓存,所以在很多情况下,甚至不需要向服务器发送请求。还可以肯定的是,GET方法具有幂等性[
译注:指多个相同请求返回相同的结果]——如果用户发送了一个GET请求没有得到结果,可能不知道原因是请求未能到达目的地,还是响应在反馈的途中丢失了。幂等性保证了可以简单地再发送一次请求解决问题。幂等性同样适用于PUT和DELETE方法。POST方法,通常表示“创建一个新资源”,也能被用于调用任意过程,因而它既不安全也不具有幂等性。
如果采用RESTful的方式暴露应用功能(也可以称为服务功能),那这条原则和它的约束同样也适用。如果习惯于另外的设计方式,则很难去接受这条原则。
可以看到,例子中定义了两个
服务程序。这些服务程序的接口都是为了完成任务而定制的。如果客户端程序试图使用这些服务,那它必须针对这些特定接口进行编码——不可能在这些接口定义之前,使用客户程序去有目的地和接口协作。这些接口定义了服务程序的
应用协议(application protocol)。
在RESTful HTTP方式中,将通过组成HTTP应用协议的通用接口访问服务程序。可能会想出像这样的方式:
可以看到,服务程序中的特定操作被映射成为标准的HTTP方法——为了
消除歧义,创建了一组全新的资源。标识一个顾客的URI上的GET方法正好相当于getCustomerDetails操作。有人用
三角形形象化地说明了这一点:
把三个顶点想象为可以调节的按钮。可以看到在第一种方法中,拥有许多操作,许多种类的数据以及固定数量的“实例”。在第二种方法中,拥有固定数量的操作,许多种类的数据和许多调用
固定方法的对象。它的意义在于,证明了通过这两种方式,基本上可以表示任何用户喜欢的事情。
使用标准方法如此重要,从根本上说,它使应用成为Web的一部分——应用程序为Web变成Internet上最成功的应用所做的贡献,与它添加到Web中的资源数量成比例。采用RESTful方式,一个应用可能会向Web中添加数以百万计的客户URI;如果采用
CORBA技术并维持应用的原有设计方式,那它的贡献大抵只是一个“端点(endpoint)”——就好比一个非常小的门,仅仅允许有钥匙的人进入其中的资源域。
统一接口也使得所有理解HTTP应用协议的组件能与应用交互。通用客户程序(generic client)就是从中受益的组件的例子,例如
curl、
wget、代理、缓存、
HTTP服务器、网关还有
Google、Yahoo!、MSN等等。
总结如下:为使客户端程序能与资源相互协作,资源应该正确地实现默认的应用协议(HTTP),也就是使用标准的GET、PUT、POST和DELETE方法。
资源多重表述
客户程序如何知道该怎样处理检索到的数据,比如作为GET或者POST请求的结果。原因是,HTTP采取的方式是允许数据处理和操作调用之间关系分离的。换句话说,如果客户程序知道如何处理一种特定的
数据格式,那就可以与所有提供这种表述格式的资源交互。再用一个例子来阐明这个观点。利用HTTP内容协商(content negotiation),客户程序可以请求一种特定格式的表述:
请求的结果可能是一些由公司专有的
XML(
标准通用标记语言下的一个子集)格式表述的
客户信息。假设客户程序发送另外一个不同的请求,就如下面这样:
结果则可能是VCard格式的客户地址。这说明为什么理想的情况下,资源表述应该采用标准格式——如果客户程序对HTTP应用协议和一组数据格式都有所“了解”,那么它就可以用一种有意义的方式与世界上任意一个RESTful HTTP应用交互。当然以上情况不仅适用于从
服务器端到客户端的数据,反之既然——倘若从客户端传来的数据符合应用协议,那么服务器端就可以使用特定的格式
处理数据,而不去关心客户端的类型。
在实践中,资源多重表述还有着其它重要的好处:如果为
资源提供标准通用标记语言下的子集
HTML和
XML两种表述方式,那这些资源不仅可以被应用所用,还可以被任意标准
Web浏览器所用——也就是说,应用信息可以被所有会使用Web的人获取到。
资源多重表述还有另外一种使用方式:可以将应用的Web
UI纳入到Web
API中——毕竟,API的设计通常是由UI可以提供的功能驱动的,而UI也是通过API执行动作的。将这两个任务合二为一带来了令人惊讶的好处,这使得使用者和调用程序都能得到更好的Web接口。总结:针对不同的需求提供资源多重表述。
无状态通信
无状态通信是最后一个原则。首先,需要着重强调的是,虽然REST包含无状态性(statelessness)的观念,但这并不是说暴露功能的应用不能有状态——事实上,在大部分情况下这会导致整个做法没有任何用处。REST要求状态要么被放入资源状态中,要么保存在客户端上。或者换句话说,服务器端不能保持除了单次请求之外的,任何与其通信的客户端的通信状态。这样做的最直接的理由就是
可伸缩性—— 如果服务器需要保持客户端状态,那么大量的客户端交互会严重影响服务器的内存可用空间(footprint)。(注意,要做到无状态通信往往需要需要一些重新设计——不能简单地将一些session状态绑缚在URI上,然后就宣称这个应用是RESTful。)
但除此以外,其它方面可能显得更为重要:无
状态约束使服务器的变化对客户端是不可见的,因为在两次连续的请求中,客户端并不依赖于同一台服务器。一个客户端从某台服务器上收到一份包含链接的文档,当它要做一些处理时,这台服务器宕掉了,可能是硬盘坏掉而被拿去修理,可能是软件需要升级重启——如果这个客户端访问了从这台服务器接收的链接,它不会察觉到后台的服务器已经改变了。
MVC
第一个问题假设REST是用户应该采用的架构,然后讨论如何使用;第二个问题则要说明REST和当前最普遍应用的MVC是什么关系,互补还是取代?
REST除了给带来了一个崭新的架构以外,还有一个重要的贡献是在开发系统过程中的一种新的
思维方式:通过
url来设计
系统的结构。根据REST,每个url都代表一个resource,而整个系统就是由这些resource组成的。因此,如果url是设计良好的,那么系统的结构就也应该是设计良好的。对于非高手级的开发人员来说,考虑一个系统如何架构总是一个很抽象的问题。
敏捷开发所提倡的Test Driven Development,其好处之一就是可以通过testcase直观地设计系统的接口。比如在还没有创建一个class的时候就编写一个testcase,虽然设置不能通过编译,但是testcase中的方法调用可以很好地从class使用者的角度反映出需要的接口,从而为class的设计提供了直观的表现。这与在REST架构中通过url设计
系统结构非常类似。虽然一个功能都没有实现,但是可以先设计出认为合理的url,这些url甚至不能连接到任何page或action,但是它们直观地表明:系统对用户的访问接口就应该是这样。根据这些url,可以很方便地设计系统的结构。
重申一遍:REST允许通过url设计系统,就像Test Driven Development允许使用testcase设计class接口一样。
网络应用通常都是有
hierarchy的,像棵大树。用户通常希望url也能反映出资源的
层次性。比如对于一个
blog应用:/articles表示所有的文章,/articles/1表示id为1的文章,这都比较直观。因此人们常常会问这样一个问题:RESTful的url能覆盖所有的用户请求吗?比如,
login如何RESTful?search如何RESTful?
从REST的概念上来看,所有可以被抽象为资源的东东都可以使用RESTful的url。因此对于上面的两个问题,如果login和search可以被抽象为资源,那么就可以使用RESTful的url。search比较简单,因为它会返回搜索结果,因此可以被抽象为资源,并且只实现index方法就可以了(只需要显示搜索结果,没有create、destroy之类的东西)。然而这里面也有一个问题:search的关键字如何传给
server?index方法显然应该使用HTTP GET,这会把关键字加到url后面,当然不符合REST的风格。要解决这个问题,可以把每次search看作一个资源,因此要创建create和index方法,create用来在用户点击“搜索”按钮是通过HTTP POST把关键字传给server,然后index则用来显示搜索结果。这样一来,还可以记录用户的搜索历史。使用同样的方法,也可以对login应用REST,即每次login动作是一个资源。
一开始可能想到的是/category/ruby/articles,这种想法很直观。还有一种url形式,它对应到程序中的继承关系。比如product是一个父类,book和computer是其子类。那么所有产品的url应该是/products,所有书籍的url应该是/books,所有电脑的url应该是/computers。这一想法就比较直观了,而且再次验证了url可以帮助进行设计的论点。
再说明一下如果每个
用户需求都可以抽象为资源,那么就可以完全使用REST。
由此看来,使用REST的关键是如何抽象资源,抽象得越精确,对REST的应用就越好。
有了对第一个问题的讨论,第二个问题就容易讨论多了。REST会取代MVC吗?还是彼此是互补关系(就像
AOP对于OOP)?答案是It depends!如果可以把所有的用户需求都可以抽象为资源,那么MVC就可以退出历史的舞台了。如果情况相反,那么就需要混合使用REST和MVC。
当然,这是非常理想的论断。可能无法找到一种方法可以把所有的用户需求都抽象为资源,因为保证这种抽象的完整性(即真的是所有需求都可以)需要形式化的证明。而且即使被证明出来了,由于开发人员的能力和喜好不同,MVC肯定也会成为不少人的首选。但是对于希望拥抱REST的人来说,这些都没有关系。只要开发的系统所设计的
问题域可以被合理地抽象为资源,那么REST就会成为开发利器。
应用场景
RESTful适用于许多不同的应用场景,包括:
1. Web 应用程序:RESTful可以用于构建Web应用程序,如社交媒体网站、博客、在线商店等。
2. 移动应用程序:RESTful可以用于构建移动应用程序,如手机应用程序、平板电脑应用程序等。
3. 后端应用程序:RESTful可以用于构建后端应用程序,如数据库服务器、消息队列服务器等。
4. 物联网设备:RESTful可以用于构建物联网设备,如智能家居设备、智能城市设备等。
RESTful特点
优势
1. 可扩展性:RESTful使用无状态协议,可以轻松地支持并发请求和负载均衡,从而提高系统的可扩展性。
2. 可靠性:RESTful使用HTTP 协议来传输数据,HTTP协议是一种通用的协议,可以保证数据的可靠性和安全性。
3. 易于使用和管理:RESTful使用标准化的概念和规则,如HTTP协议、URL、JSON等,使得开发人员可以更加轻松地设计和实现系统。
4. 跨平台支持:RESTful可以用于构建跨平台的应用程序,如Web应用程序、移动应用程序、后端应用程序等。
缺点
1.真实系统中的资源非常复杂,很难清晰地进行资源的划分。
2.真实系统中的业务很复杂,并不是所有的操作都能简单地对应到PUT、GET、DELETE、POST上。
3.真实系统是在不断进化的,一个操作最开始的时候被设计为幂等的 PUT,但是后来的版本又修改了逻辑,可能该操作就变成了不幂等的。如果调用者继续对这个操作进行重试可能会有副作用。
4.在 Restful 中,资源尽量通过 URL来定位,要尽量避免使用 QueryString 及请求报文体传递数据。
5.HTTP状态码的个数是有限的,特别是用于表示业务相关的错误码主要在 4xx状态码段中,而业务系统中的错误非常复杂,仅通过 HTTP状态码来反映错误有时候会无法满足要求。
6.有的客户端是不支持 PUT、DELETE 请求的。例如: 旧版本的程序。