为什么会有REST
呢?
Web服务已经成为异构系统之间的互联于集成的主要手段,过去Web服务几乎都是采用SOAP(Simple Object Access Protocol,简单对象访问协议)
来构建的。REST
风格的软件架构模式出现后就快速取代了复杂而笨重的SOAP
,成为Web API
的标准。
什么是REST
呢?
REST
全称Representational State Transfer
表述性状态转移
REST
于2000年被Roy Thomas Fielding
博士提出,是基于HTTP
、URI
、XML
、JSON
等标准和协议,支持轻量级、跨平台、跨语言的架构设计,是Web服务的一种新的架构风格。
Roy Thomas Fielding
将REST
定位为“分布式超媒体应用(Distributed Hypermedia System
)”的架构风格。
例如:在面向终端用户的Web应用中,应用状态表示Web应用中客户端状态,简单理解可视为会话状态。资源在浏览器中以超媒体的形式呈现,通过点击超媒体中的超链接获取其它相关的资源或对当前资源进行相关处理。另外,获取的资源或针对资源处理的响应同样以超媒体的形式再次呈现在浏览器上。因此,超媒体成为了驱动客户端会话状态转换的引擎(Hypermedia as the engine of appliation state, HATEOAS
)。
借助于超媒体这种特殊的资源呈现方式,应用状态的转换体现为浏览器中呈现资源的转换。如果将超媒体进一步抽象成一般意义上的资源呈现(Representation
)方式,那么应用状态变成了“可被呈现的状态(Representational State
)”。因此,应用状态之间的转换就成了“可被呈现的状态转换(Representational State Transfer
)”,也就是REST
。
REST
描述了一种架构样式的网络系统,如Web应用程序。在目前主流的Web服务交互方案中,REST
相比SOAP(Simple Object Access Protocol,简单对象访问协议)
和XML-RPC
更加简单明了,无论是对URL的处理还是对Payload
的编码,REST
都倾向于使用更加简单轻量的设计和实现。
REST
其实是对资源的表述性状态转移,它并没有明确的标准而更像是一种设计风格。
Representational
)是指客户端请求一个资源,服务器拿到的这个资源,就是表述。REST
系统的核心概念,所有的设计都是以资源为中心的。URL
统一资源定位符REST
架构原则
XML
、JSON
...Stateless
)REST
系统特征
Client-Server
)Stateless
)Cachable
)Layered System
)Uniform Interface
)Code-On-Demand
)REST
成熟度的层次
Level0
的Web服务HTTP
作为传输方式,实际只是远程方法调用RPC
的一种具体形式,如SOAP
和XML-RPC
。Level1
的Web服务Level2
的Web服务Level3
的Web服务HATEOAS
,在资源的表达中包含了链接信息,客户端可以根据链接来发现可以执行的动作。简单来说,REST
就是使用URL
定位资源 + 使用HTTP
动词(GET/POST/DELETE/HEAD/PUT
)描述操作。
RESTful
是一种常见的REST
应用,是遵循REST
风格的Web服务,而REST
风格的Web本质则是一种面向资源的架构(Resource Oriented Architecture, ROA
)。
如果一个架构符合REST
原则就称为RESTful
架构,HTTP
就是RESTful
架构风格的一个典型应用。
表述性状态转移中的表述其实指的是资源(Resources
),任何事物只要有被引用到的必要,它就是一个资源。资源可以是实体也可以是一种抽象概念,要让资源可以被识别,就需要具有唯一标识。
资源是一个很宽泛的概念,任何寄宿于Web可供操纵的“事物”都可视为资源,资源可以体现为经过持久化处理保存到磁盘上的某个文件或是数据库中某个表的记录,也可以是Web应用接收请求后采用某算法计算所得的结果。简单来说,资源可以体现为一个具体的物理对象,也可以是一个抽象的流程。
资源是一种信息实体,可以有多种外在表现形式,资源具体呈现出来的形式称为表现层(Representation
)。例如:文本可通过txt
格式表现也可用HTML
表现等。
一个资源必须具有一个或多个标识,在设计Web应用的API
时,由于Web系统中资源的唯一标识是URI(Uniform Resource Identifier,统一资源标识符)
,所以自然采用URI
作为资源的标识。
schema://host[:port]/path[?query-string][#anchor]
schema 指定底层所使用的协议,如HTTP、HTTPS、FTP...
host 服务器的IP地址或域名
port 服务器端口,默认80
path 访问资源的路径
query-string 发送给HTTP服务器的数据
anchor 锚点
URI
既可以看作是资源的地址,也可以看作是资源的名称。如果某些信息没有使用URI
来表示,那它就不能算是一个资源,只能算是资源的信息。
URI
的设计遵循着可寻址性原则,具有自描述性,在形式上需要给人直觉上的关联。因为具有可读性的URI更容易被使用,使用者一看就知道被标识的是何种资源。
除了必要的标志性和可选的可读性之外,标识资源的URI
应该具有“可寻址性(Addressability
)”,也就是说,URI
不仅仅指明了被标识资源所在的位置,通过这个URI
还可以直接获取目标资源。
URI
具有URL
和URN
两种主要的表现形式,由于URL
具有可寻址性,所以推荐采用URL
作为资源的标识。
URI
除了可以标识某个独立的资源外,还可以标识一组资源的集合或者是资源的容器。当然,一组同类资源的集合或存放一组同类资源的容器本身也可以视为另一种类型的复合型(Composite
)资源。所以,“URI总是标识某个资源”的说法是没错的。
REST
是面向资源的,而资源是通过URI
进行暴露,URI
的设计只负责把资源通过合理的方式暴露出来就可以了。对资源的操作于它无关,操作是通过HTTP
动词来体现的。所以REST
通过URI
暴露资源时,会强调不要在URI
中出现动词。
URI
只代表资源的实体,并不代表它的形式。严格来说,网址后的文件后缀是不必要的,因为文件后缀标识的是格式,是属于表现层的范畴。URI
应该只代表资源的位置,它的具体表现形式,应该在HTTP
请求的头信息中使用Accept
和Content-Type
字段指定,因为这两个字段才是对表现成的描述。
例如:github
站点的URI
的设计https://github.com/git/git/commit/e3af72cdafab5993d18fae056f87e1d675913d08
_
或中划线-
分隔单词可以让URI
可读性更好例如:http://www.oschina.net/news/38119/oschina-translate-reward-plan
/
表示资源的层级关系例如:/git/git/commit/e3af72cdafab5993d18fae056f87e1d675913d08
表示一个多级的资源,表示git用户的git项目的某次提交
例如:/orders/2019/10
表示2019年10月的订单记录
?
用于过滤资源URI中的?
并非只能用于传递参数,?
可以用于对资源的过滤。
例如:/git/git/pulls?state=closed
表示git项目中已经关闭的推入请求
,
或分号;
用来表示同级资源的关系为了表示同级资源的关系,可使用逗号,
或分号;
进行分隔
例如:/git/git /block-sha1/sha1.h/compare/e3af72cdafab5993d18fae056f87e1d675913d08;bd63e61bdf38e872d5215c07b264dcc16e4febca
比较git中某个文件两次提交记录之间的差,现在GitHub使用...
三个点号来比较两次提交记录之间的差异。
REST
是指一组架构约束条件和原则,满足这些约束条件和原则的应用程序或设计就是RESTful
。
RESTful
架构的核心规范与约束是统一接口,又分为四个子约束:
Web应用程序中最重要的RESTful
原则是:客户端和服务器之间的交互在请求之间是无状态的。
从客户端到服务器的每个请求都必须包含理解请求所必须的信息。如果服务器在请求之间的任何时间点重启,客户端将不会得到通知。另外,无状态请求可以由任何可用服务器应答,这一点十分适合云计算之类的环境,客户端可以缓存数据以改进性能。
在服务器上应用程序状态和功能可以划分为各种资源,资源是一个有趣的概念实体,它向客户端公开。每个资源都使用URI
得到一个唯一的地址。所有资源都共享统一的接口,以便在客户端和服务器之间传递状态。
RESTful
架构应该遵循统一接口原则,统一接口包括一组受限且预定义的操作,无论是什么样的资源,都是通过使用相同的接口进行资源的访问。接口使用标准的HTTP
方法如GET、POST、PUT
等,并遵循这些方法的语义。
如果按照HTTP
方法的语义来暴露资源,那么接口将会拥有安全性和幂等性的特性。例如:GET
和HEAD
请求都是安全的,无论请求多少次,都不会改变服务器的状态。而GET、HEAD、PUT、DELETE
请求又都是幂等的,无论对资源操作多少次,结构总是一样的,后续的请求并不会产生比第一次更多的影响。
关于HTTP请求方法具有两个基本的特性,即“安全性”和“幂等性”。
REST
接口的多次访问得到的资源状态都是相同的REST
接口访问将不会使服务器资源的状态发生改变实际上状态分为应用状态和资源状态,客户端负责维护应用状态,服务器维护资源状态。客户端与服务器的交互必须是无状态的,在每次请求中包含处理该请求所需的一切信息。服务器不需要在请求之间保留应用状态,只有在接收到实际请求时服务器才会关注应用状态。这种无状态的通信原则,使得服务器和中介能够立即独立的请求和响应。在多次请求中,同一客户端也不再需要依赖于同一台服务器,方便实现高可扩展和高可用性的服务器。简单来说,这种无状态的通信原则并不是说客户端应用不能有状态,而是指服务器不应该保存客户端的状态。
会话状态不是作为资源状态保存在服务器的,而是被客户端作为应用状态进行跟踪的。客户端应用状态在服务器提供的超媒体的指引下发生变迁。服务器通过超媒体高速客户端当前状态有那些后续状态可以进入。
客户端与服务器的一个互动过程中势必涉及到数据和状态的变化,由于HTTP本身是无状态的,这意味着所有的状态都保存在服务器上。因此,如果客户端想要操作服务器,必须通过某种手段让服务器发生“状态转化(State Transfer
)”。而这种转化是建立在表现层之上的,所以就是“表现层状态转化”。
客户端能够用到的手段只能是HTTP协议,具体来说是HTTP协议中四个表示操作方式的动词:GET、POST、PUT、DELETE
,它们分别对应四种基本操作:
GET
用来获取资源POST
用于新建资源,也可以用于更新资源。PUT
用于更新资源DELETE
用于删除资源RESTful对于资源的操作主要包括增删改查CURD
,由HTTP动词或谓词表示。
POST
:用于在服务器中新建一个资源,对应资源操作是增加INSERT
,非幂等且不安全。DELETE
:用于从服务器删除资源,对应资源操作是删除DELETE
,幂等且不安全。PUT
:用于在服务器中更新资源,客户端提供改变后的完整资源,对应资源操作是修改UPDATE
,幂等且不安全。GET
:用于从服务器中取出资源,对应资源操作是查询SELECT
,幂等且安全。客户端通过HTTP方法可获取资源,准确来说客户端获取的只是资源的表述而已。资源在外界的具体呈现方式可以有多种表现形式,在客户端和服务器之间传输的也是资源的表述,而不是资源本身。例如:文本资源可以采用HTML、XML、JSON等格式,图片可以使用PNG、JPG等格式展现。
资源的表述包括数据和描述数据的元数据,例如:HTTP头信息的Content-Type
字段就是这样的一个元数据属性。
问题是客户端如何知道服务器提供那种表述形式呢?答案是通过HTTP内容协商,客户端可以通过Accept
头请求一种特定格式的表述,服务器则通过Content-Type
告知客户端资源的表述形式。
例如:请求组织资源的JSON格式的表述形式
# HTTP请求头
GET http://api.github.com/orgs/github HTTP/1.1
Accept: application/json
# HTTP响应头
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
在URI中带上版本号
例如http://api.example.com/1.0/foo
,如果把版本号理解成资源的不同表述形式的话,那就应该只是用一个URL
,并通过Accept
字段来区分,以Github
为例其格式为application/vnd.github[.version].param[+json]
。
Accept: vnd.example-com.foo+json; version=1.0
资源的URL设计,可通过URL来表示资源,首先资源可分为主资源和子资源,主资源表示一类独立的资源,索引主资源应该直接放到相对路径下。一个子资源可能是一个集合也可能是一个单一的子资源。
例如:表示主资源的实例/goods/1
,表示子资源的集合/goods/1/pictures
。
RESTful的核心思想是客户端发出的数据操作指令都是“动词+宾语”的结构
比如:GET /users
这个命令,其中GET
是动词,/users
是宾语。
动词通常就是五种HTTP方法对应着CURD操作,根据HTTP规范动词一律大写。有些客户端只能使用GET
和POST
两种方法,服务器必须接收POST
模拟其它三个方法PUT
、PATCH
、DELETE
,这时客户端发出的HTTP请求中就需要附加上X-HTTP-Method-Override
属性,它会覆盖掉POST
方法,用以告知服务器应该使用哪一个动词。
例如:下列请求的方法是PUT
而非POST
POST /api/users/1 HTTP/1.1
X-HTTP-Method-Override: PUT
宾语必须是名词
宾语是API的URL,是HTTP动词作用的对象,它应该是名词,不能是动词。既然URL是名词,那么应该使用复数还是单数呢?这个没有统一的规定,常见的操作是读取一个集合时使用复数。为了统一起见建议都使用复数的URL。
避免多级URL
当资源需要多级分类时就会很容易出现多级URL,比如获取某个用户的某类数据:GET /users/10/categories/2
,这种URL不利于扩展而且语义也不明确,往往需要想一下才能明白含义。更好的做法时除了第一级其它级别都使用查询字符串表示,比如GET /users/10?categories=2
。
比如要查询某个状态的数据时会设计成这样GET /users/locked
,但使用查询字符串的写法明显会更好 GET /users?locked=true
。
REST很好地利用了HTTP本身的一些特性,如HTTP动词、HTTP状态码、HTTP报头等。REST API是基于HTTP的,所以设计的API应该去使用HTTP的一些标准,这样所有的HTTP客户端才能够直接理解你的API并有利于缓存等。RSET实际上也非常调用应该利用好HTTP本来就有的特性,而不是只把HTTP当成一个传输层协议这样简单。