REST 表述性状态转移

RESTful API

REST

为什么会有REST呢?

Web服务已经成为异构系统之间的互联于集成的主要手段,过去Web服务几乎都是采用SOAP(Simple Object Access Protocol,简单对象访问协议)来构建的。REST风格的软件架构模式出现后就快速取代了复杂而笨重的SOAP,成为Web API的标准。

什么是REST呢?

REST全称Representational State Transfer表述性状态转移

REST于2000年被Roy Thomas Fielding博士提出,是基于HTTPURIXMLJSON等标准和协议,支持轻量级、跨平台、跨语言的架构设计,是Web服务的一种新的架构风格。

Roy Thomas FieldingREST定位为“分布式超媒体应用(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系统的核心概念,所有的设计都是以资源为中心的。
  • 资源的地址在Web中就是URL统一资源定位符

REST架构原则

  • 对网络上所有资源都有一个资源标识符
  • 对资源的操作不会改变标识符
  • 同一资源有多种表现形式,如XMLJSON...
  • 所有操作都是无状态的(Stateless

REST系统特征

  • 客户端与服务器(Client-Server
    提供服务的服务器和使用服务的客户端需要被隔离对待
  • 无状态(Stateless
    来自客户端的每个请求必须包含服务器处理该请求所需的所有信息,换句话说,服务器不能存储来自某个客户端某个请求中的信息,并在该客户端的其它请求中使用。
  • 可缓存(Cachable
    服务器必须让客户端知道请求是否可以被缓存
  • 分层系统(Layered System
    服务器和客户端之间的通信必须被标准化,即允许服务器和客户端之间的中间层可以代替服务器对客户端的请求进行回应,而且这些客户端来说不需要特别支持。
  • 统一接口(Uniform Interface
    客户端和服务器之间通信的方法必须时统一化的
  • 支持按需代码(Code-On-Demand
    服务器可以提供一些代码或脚本并在客户端的运行环境中执行,这条准则是唯一不必必须满足的。

REST成熟度的层次

  • 第一个层次Level0的Web服务
    使用HTTP作为传输方式,实际只是远程方法调用RPC的一种具体形式,如SOAPXML-RPC
  • 第二个层次Level1的Web服务
    引入资源的概念,每个资源都有与之对应的标识符和表达。
  • 第三个层次Level2的Web服务
    使用不同的HTTP方法来进行不同的操作,并使用HTTP状态码表示不同结果。
  • 第四个层次Level3的Web服务
    使用HATEOAS,在资源的表达中包含了链接信息,客户端可以根据链接来发现可以执行的动作。

简单来说,REST就是使用URL定位资源 + 使用HTTP动词(GET/POST/DELETE/HEAD/PUT)描述操作。

RESTful

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具有URLURN两种主要的表现形式,由于URL具有可寻址性,所以推荐采用URL作为资源的标识。

URI除了可以标识某个独立的资源外,还可以标识一组资源的集合或者是资源的容器。当然,一组同类资源的集合或存放一组同类资源的容器本身也可以视为另一种类型的复合型(Composite)资源。所以,“URI总是标识某个资源”的说法是没错的。

REST是面向资源的,而资源是通过URI进行暴露,URI的设计只负责把资源通过合理的方式暴露出来就可以了。对资源的操作于它无关,操作是通过HTTP动词来体现的。所以REST通过URI暴露资源时,会强调不要在URI中出现动词。

URI只代表资源的实体,并不代表它的形式。严格来说,网址后的文件后缀是不必要的,因为文件后缀标识的是格式,是属于表现层的范畴。URI应该只代表资源的位置,它的具体表现形式,应该在HTTP请求的头信息中使用AcceptContent-Type字段指定,因为这两个字段才是对表现成的描述。

例如:github站点的URI的设计https://github.com/git/git/commit/e3af72cdafab5993d18fae056f87e1d675913d08

URI设计技巧

  1. 使用下划线_或中划线-分隔单词可以让URI可读性更好

例如:http://www.oschina.net/news/38119/oschina-translate-reward-plan

  1. 使用斜线/表示资源的层级关系

例如:/git/git/commit/e3af72cdafab5993d18fae056f87e1d675913d08表示一个多级的资源,表示git用户的git项目的某次提交

例如:/orders/2019/10表示2019年10月的订单记录

  1. 使用问号?用于过滤资源

URI中的?并非只能用于传递参数,?可以用于对资源的过滤。

例如:/git/git/pulls?state=closed表示git项目中已经关闭的推入请求

  1. 使用逗号,或分号;用来表示同级资源的关系

为了表示同级资源的关系,可使用逗号,或分号;进行分隔

例如:/git/git /block-sha1/sha1.h/compare/e3af72cdafab5993d18fae056f87e1d675913d08;bd63e61bdf38e872d5215c07b264dcc16e4febca 比较git中某个文件两次提交记录之间的差,现在GitHub使用...三个点号来比较两次提交记录之间的差异。

统一资源接口

REST是指一组架构约束条件和原则,满足这些约束条件和原则的应用程序或设计就是RESTful

RESTful架构的核心规范与约束是统一接口,又分为四个子约束:

  • 每个资源都拥有一个资源标识,每个资源的资源标识可以用来唯一的标明该资源。
  • 消息的自描述性
  • 资源的自描述性
  • 使用超媒体作为应用状态引擎

Web应用程序中最重要的RESTful原则是:客户端和服务器之间的交互在请求之间是无状态的。

从客户端到服务器的每个请求都必须包含理解请求所必须的信息。如果服务器在请求之间的任何时间点重启,客户端将不会得到通知。另外,无状态请求可以由任何可用服务器应答,这一点十分适合云计算之类的环境,客户端可以缓存数据以改进性能。

在服务器上应用程序状态和功能可以划分为各种资源,资源是一个有趣的概念实体,它向客户端公开。每个资源都使用URI得到一个唯一的地址。所有资源都共享统一的接口,以便在客户端和服务器之间传递状态。

RESTful架构应该遵循统一接口原则,统一接口包括一组受限且预定义的操作,无论是什么样的资源,都是通过使用相同的接口进行资源的访问。接口使用标准的HTTP方法如GET、POST、PUT等,并遵循这些方法的语义。

如果按照HTTP方法的语义来暴露资源,那么接口将会拥有安全性和幂等性的特性。例如:GETHEAD请求都是安全的,无论请求多少次,都不会改变服务器的状态。而GET、HEAD、PUT、DELETE请求又都是幂等的,无论对资源操作多少次,结构总是一样的,后续的请求并不会产生比第一次更多的影响。

关于HTTP请求方法具有两个基本的特性,即“安全性”和“幂等性”。

  • 幂等性(Idempotent)表示对同一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规范动词一律大写。有些客户端只能使用GETPOST两种方法,服务器必须接收POST模拟其它三个方法PUTPATCHDELETE,这时客户端发出的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当成一个传输层协议这样简单。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,294评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,780评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,001评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,593评论 1 289
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,687评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,679评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,667评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,426评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,872评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,180评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,346评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,019评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,658评论 3 323
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,268评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,495评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,275评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,207评论 2 352

推荐阅读更多精彩内容

  • 一说到REST,我想大家的第一反应就是“啊,就是那种前后台通信方式。”但是在要求详细讲述它所提出的各个约束,以及如...
    时待吾阅读 3,422评论 0 19
  • REST本身是一个高度抽象化的架构风格,因而总是很难对它有一个比较深入且印象深刻的理解。写这篇文章的目的,是自己对...
    vito1994阅读 2,851评论 0 26
  • API定义规范 本规范设计基于如下使用场景: 请求频率不是非常高:如果产品的使用周期内请求频率非常高,建议使用双通...
    有涯逐无涯阅读 2,529评论 0 6
  • 网络请求是iOS项目的一个大部分,而且大部分的iOS的项目的网络请求是根据AFN进行的二次封装,我们查看返回的结果...
    FR_Zhang阅读 6,913评论 15 46
  • 披着柔媚的春光,让略带甜意的风,便会领悟到春的气息里,那包含着一种最令人感动的柔情。也会觉得大自然就是一位奇特的朋...
    1702张小潇阅读 161评论 0 1