关于REST和restful

什么是REST

Representational State Transfer

越来越多的人开始意识到,网站即软件,而且是一种新型的软件。

这种"互联网软件"采用客户端/服务器模式,建立在分布式体系上,通过互联网通信,具有高延时(high latency)、高并发等特点。

网站开发,完全可以采用软件开发的模式。但是传统上,软件和网络是两个不同的领域,很少有交集;软件开发主要针对单机环境,网络则主要研究系统之间的通信。互联网的兴起,使得这两个领域开始融合,现在我们必须考虑,如何开发在互联网环境中使用的软件。

RESTful架构,就是目前最流行的一种互联网软件架构。它结构清晰、符合标准、易于理解、扩展方便,所以正得到越来越多网站的采用。

一、起源

REST这个词,是Roy Thomas Fielding在他2000年的博士论文中提出的。

Fielding是一个非常重要的人,他是HTTP协议(1.0版和1.1版)的主要设计者、Apache服务器软件的作者之一、Apache基金会的第一任主席。

在Fielding的这篇名为Architectural Styles and the Design of Network-based Software Architectures的博士论文(中文版名为《架构风格与基于网络的软件架构设计》)中,提出了一整套基于网络的软件(即所谓的“分布式应用”)的设计方法。

他的这篇论文一经发表,就引起了关注,并且立即对互联网开发产生了深远的影响。

他这样介绍论文的写作目的:

"本文研究计算机科学两大前沿----软件和网络----的交叉点。长期以来,软件研究主要关注软件设计的分类、设计方法的演化,很少客观地评估不同的设计选择对系统行为的影响。而相反地,网络研究主要关注系统之间通信行为的细节、如何改进特定通信机制的表现,常常忽视了一个事实,那就是改变应用程序的互动风格比改变互动协议,对整体表现有更大的影响。我这篇文章的写作目的,就是想在符合架构原理的前提下,理解和评估以网络为基础的应用软件的架构设计,得到一个功能强、性能好、适宜通信的架构。"

(This dissertation explores a junction on the frontiers of two research disciplines(研究科学) in computer science: software and networking. Software research has long been concerned with(关注于) the categorization of software designs and the development of design methodologies, but has rarely been able to objectively(客观地) evaluate(评价) the impact of various design choices on system behavior. Networking research, in contrast, is focused on the details of generic communication behavior between systems and improving the performance of particular communication techniques, often ignoring the fact that changing the interaction(互动,相互影响) style of an application can have more impact on performance than the communication protocols used for that interaction. My work is motivated(激发) by the desire to understand and evaluate the architectural design of network-based application software through principled use of architectural constraints, thereby obtaining the functional, performance, and social properties desired of an architecture. )

二、概念

Fielding将他对互联网软件的架构原则,定名为REST,即Representational State Transfer的缩写。我对这个词组的翻译是"表现层状态转化"。

如果一个架构符合REST原则,就称它为RESTful架构。

要理解RESTful架构,最好的方法就是去理解Representational State Transfer这个词组到底是什么意思,它的每一个词代表了什么涵义。如果你把这个名称搞懂了,也就不难体会REST是一种什么样的设计。

1、资源(Resources)

REST的名称"表现层状态转化"中,省略了主语。"表现层"其实指的是"资源"(Resources)的"表现层"。

资源是一种看待服务器的方式,即,将服务器看作是由很多离散的资源组成。每个资源是服务器上一个可命名的抽象概念,它可以是一段文本、一张图片、一首歌曲、一种服务。资源是以名词为核心来组织的,首先关注的是名词。一个资源可以由一个或多个URI(统一资源定位符)来标识,URI既是资源的名称,也是资源在Web上的地址。

所谓"上网",就是与互联网上一系列的"资源"互动,调用它的URI。严格地说,有些网址最后的".html"后缀名是不必要的,因为这个后缀名表示格式,属于"表现层"范畴,而URI应该只代表"资源"的位置。

2、表现层(Representation)

我们把"资源"具体呈现出来的形式,叫做它的Representation,是一段对于资源在某个特定时刻的状态的描述。可以有多种格式,例如HTML/XML/JSON/纯文本/二进制格式/图片/视频/音频等等。它的具体表现形式,应该在HTTP请求的头信息中用Accept和Content-Type字段指定,这两个字段是对Representation的描述。

3、状态转移(State Transfer)

访问一个网站,就代表了客户端和服务器的一个互动过程——在客户端和服务器端之间转移(transfer)代表资源状态的表述。而互联网通信协议HTTP协议,是一个无状态协议,所以所有的状态都需保存在服务器端。

REST要求,必须通过统一的接口来对资源执行各种操作,对于每个资源只能执行一组有限的操作。

以HTTP/1.1协议为例,HTTP/1.1协议定义了一个操作资源的统一接口,主要包括以下内容:

  • 7个HTTP方法:GET/POST/PUT/DELETE/PATCH(在服务器更新资源,客户端提供改变的属性)/HEAD(获取资源的元数据)/OPTIONS(获取信息,关于资源的哪些属性是客户端可以改变的)(分别进行CRUD操作)

  • HTTP头信息(可自定义)

  • HTTP响应状态代码(可自定义)

  • 一套标准的内容协商机制

  • 一套标准的缓存机制

  • 一套标准的客户端身份认证机制

这时候HTTP头和有效载荷都包含业务逻辑,例如HTTP方法对应CRUD操作,HTTP状态码对应操作结果的状态。

4、综述

综合上面的解释,我们总结一下Representational State Transfer

  (1)每一个URI代表一种资源;资源表示一种实体,所以应该是名词(动词应该放在HTTP协议中)。

  (2)客户端和服务器之间,转移这种资源的某种表现层;

  (3)客户端通过统一的接口,对服务器端资源进行操作,实现"表现层状态转移。

三、HATEOAS (The Hypermedia As The Engine Of Application Statue)— 超媒体驱动

2008年10月Fielding写了一篇博 客,做出了一个非常明确的断言:REST APIs must be hypertext-driven!

将Web应用看作是一个由很多状态(应用状态)组成的有限状态机。资源之间通过超链接相互关联,超链接既代表资源之间的关系,也代表可执行的状态迁移。在超媒体之中不仅仅包含数据,还包含了状态迁移的语义。以超媒体作为引擎,驱动Web应用的状态迁移。通过超媒体暴露出服务器所提供的资源,服务器提供了哪些资源是在运行时通过解析超媒体发现的,而不是事先定义的。

它的重要性在于打破了客户端和服务器之间严格的契约,使得客户端可以更加智能和自适应,client不用事先知道服务或者工作流中不同步骤,不用再为不同的资源硬编码URI了;而 REST 服务本身的演化和更新也变得更加容易。 

Github的API就是这种设计,访问api.github.com会得到一个所有可用API的网址列表。


{
  "current_user_url": "https://api.github.com/user",
  "authorizations_url": "https://api.github.com/authorizations",
  // ...
}

从上面可以看到,如果想获取当前用户的信息,应该去访问api.github.com/user,然后就得到了下面结果。


{
  "message": "Requires authentication",
  "documentation_url": "https://developer.github.com/v3"
}

文档中有一个link属性。rel表示这个API与当前网址的关系(collection关系,并给出该collection的网址),href表示API的路径,title表示API的标题,type表示返回类型。


{"link": {
  "rel":   "collection https://www.example.com/zoos",
  "href":  "https://api.example.com/zoos",
  "title": "List of zoos",
  "type":  "application/vnd.yourformat+json"
}}

订购一杯咖啡所需要的点单、付款、取咖啡:

GET https://api.example.com/trades
{
  "coffee_trade_url": "https://api.example.com/ trade/coffee",

  "tea_trade_url": "https://api.example.com/trade/tea"
}

GET https://api.example.com/trade_coffee

{

  "trade_coffee_order_url":"https://api.example.com/order/coffee",

  "trade_coffee_payment_url":"https://api.example.com/payment/coffee",

  "trade_coffee_supply_url":"","https://api.example.com/supply/coffee",

}

下载媒体文件:

GET https://api.example.com/profile
{
  "name": "Steve",
  "picture": {
    "large": "https://somecdn.com/pictures/1200x1200.png",
    "medium": "https://somecdn.com/pictures/100x100.png",
    "small": "https://somecdn.com/pictures/10x10.png"
  }
}

4.REST架构描述

REST架构风格的推导过程如下图所示:

图1:REST所继承的架构风格约束

在图1中,每一个椭圆形里面的缩写词代表了一种架构风格,而每一个箭头边的单词代表了一种架构约束。

REST架构风格最重要的架构约束有6个:

  • spearated:客户-服务器(Client-Server)—— 通信只能由客户端单方面发起,表现为请求-响应的形式。
  • stateless:无状态—— 通信的会话状态(Session State)应该全部由客户端负责维护。
  • 缓存(Cache)—— 响应内容可以在通信链的某处被缓存,以改善网络效率。
  • uniform interface:统一接口—— 通信链的组件之间通过统一的接口相互通信,以提高交互的可见性。
  • 分层系统(Layered System)—— 通过限制组件的行为(即,每个组件只能“看到”与其交互的紧邻层),将架构分解为若干等级的层。
  • 按需代码(Code-On-Demand,可选)—— 支持通过下载并执行一些代码(例如Java Applet、Flash或JavaScript),对客户端的功能进行扩展。

 在论文中推导出的REST架构风格如下图所示:

 图2:REST架构风格

而HTTP/1.1协议作为一种REST架构风格的架构实例,其架构如下图所示:

图3:一个基于REST的架构的过程视图

 

用户代理处在三个并行交互(a、b、c)中间,用户代理的客户端连接器缓存无法满足请求,因此它根据每个资源标识符的属性和客户端连接器的配置,将每个请求路由到资源的来源。

请求(a)被发送到一个本地代理,代理随后访问一个通过DNS查找发现的缓存网关,该网关将这个请求转发到一个能够满足该请求的来源服务器,服务器的内部资源由一个封装过的对象请求代理(object request broker)架构来定义。

请求(b)直接发送到一个来源服务器,它能够通过自己的缓存来满足这个请求。

请求(c)被发送到一个代理,它能够直接访问WAIS(Wide Area Information System,一种与Web架构分离的信息服务),并将WAIS的响应翻译为一种通用的连接器接口能够识别的格式。

“HTTP不是RPC”、“HTTP不是一种传输协议”

5.常见的分布式应用架构横向对比

从架构风格的抽象高度来看,常见的分布式应用架构风格有三种:

  • 分布式对象(Distributed Objects,简称DO)—— 架构实例有CORBA/RMI/EJB/DCOM/.NET Remoting等等
  • 远程过程调用(Remote Procedure Call,简称RPC)—— 架构实例有SOAP/XML-RPC/Hessian/Flash AMF/DWR等等
  • 表述性状态转移(Representational State Transfer,简称REST)—— 架构实例有HTTP/WebDAV

REST与DO的差别在于:

  • REST支持抽象(即建模)的工具是资源DO支持抽象的工具是对象。在不同的编程语言中,对象的定义有很大差别,所以DO风格的架构通常都是与某种编程语言绑定的。跨语言交互即使能实现,实现起来也会非常复杂。而REST中的资源,则完全中立于开发平台和编程语言,可以使用任何编程语言来实现。

  • DO中没有统一接口的概念。不同的API,接口设计风格可以完全不同。DO也不支持操作语义对于中间组件的可见性。

  • DO中没有使用超文本,响应的内容中只包含对象本身。REST使用了超文本,可以实现更大粒度的交互,交互的效率比DO更高。

  • REST支持数据流和管道,DO不支持数据流和管道。

  • DO风格通常会带来客户端与服务器端的紧耦合。在三种架构风格之中,DO风格的耦合度是最大的,而REST的风格耦合度是最小的。REST松耦合的源泉来自于统一接口+超文本驱动。

REST与RPC的差别在于:

  • REST支持抽象的工具是资源,RPC支持抽象的工具是过程。REST风格的架构建模是以名词为核心的,RPC风格的架构建模是以动词为核心的。简单类比一下,REST是面向对象编程,RPC则是面向过程编程。

  • RPC中没有统一接口的概念。不同的API,接口设计风格可以完全不同。RPC也不支持操作语义对于中间组件的可见性。

  • RPC中没有使用超文本,响应的内容中只包含消息本身。REST使用了超文本,可以实现更大粒度的交互,交互的效率比RPC更高。

  • REST支持数据流和管道,RPC不支持数据流和管道。

  • 因为使用了平台中立的消息,RPC风格的耦合度比DO风格要小一些,但是RPC风格也常常会带来客户端与服务器端的紧耦合。支持统一接口+超文本驱动的REST风格,可以达到最小的耦合度。

6.总结

REST风格的架构所具有的6个的主要特征:

  • 面向资源(Resource Oriented)

  • 可寻址(Addressability)

  • 连通性(Connectedness)

  • 无状态(Statelessness)

  • 统一接口(Uniform Interface)

  • 超文本驱动(Hypertext Driven)

面向资源是REST最明显的特征,即,REST架构设计是以资源抽象为核心展开的;可寻址是指每一个资源在Web之上都有自己的地址;连通性即应该尽量避免设计孤立的资源,除了设计资源本身,还需要设计资源之间的关联关系,并且通过超链接将资源关联起来。这6个特征是REST架构设计优秀程度的判断标准。

比较了三种常见的分布式应用架构风格之间的差别后,从面向实用的角度来看,REST架构风格可以为Web开发者带来三方面的利益:

  • 简单性——采用REST架构风格,对于开发、测试、运维人员来说,都会更简单。可以充分利用大量HTTP服务器端和客户端开发库、Web功能测试/性能测试工具、HTTP缓存、HTTP代理服务器、防火墙。这些开发库和基础设施早已成为了日常用品,不需要什么火箭科技(例如神奇昂贵的应用服务器、中间件)就能解决大多数可伸缩性方面的问题。
  • 可伸缩性——充分利用好通信链各个位置的HTTP缓存组件,可以带来更好的可伸缩性。其实很多时候,在Web前端做性能优化,产生的效果不亚于仅仅在服务器端做性能优化,但是HTTP协议层面的缓存常常被一些资深的架构师完全忽略掉。
  • 松耦合——统一接口+超文本驱动,带来了最大限度的松耦合。允许服务器端和客户端程序在很大范围内,相对独立地进化。对于设计面向企业内网的API来说,松耦合并不是一个很重要的设计关注点。但是对于设计面向互联网的API来说,松耦合变成了一个必选项,不仅在设计时应该关注,而且应该放在最优先位置。

七、设计

1.API与用户的通信协议,总是使用HTTPs协议

2.应该尽量将API部署在专用域名之下。

https://api.example.com

可以将API的版本号放入URL(另一种做法是,将版本号放在HTTP头信息中,但不如放入URL方便和直观)。Github采用这种做法。

https://api.example.com/v1/

3.endpoint,表示API的具体网址。

在RESTful架构中,每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词,而且所用的名词往往与数据库的表格名对应。一般来说,数据库中的表都是同种记录的"集合"(collection),所以API中的名词也应该使用复数

举例来说,有一个API提供动物园(zoo)的信息,还包括各种动物和雇员的信息,则它的路径应该设计成下面这样。

  • https://api.example.com/v1/zoos
  • https://api.example.com/v1/animals
  • https://api.example.com/v1/employees

4.HTTP动词

下面是一些例子。

  • GET /zoos:列出所有动物园
  • POST /zoos:新建一个动物园
  • GET /zoos/ID:获取某个指定动物园的信息
  • PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息)
  • PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息)
  • DELETE /zoos/ID:删除某个动物园
  • GET /zoos/ID/animals:列出某个指定动物园的所有动物
  • DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物

针对不同操作,服务器向用户返回的结果应该符合以下规范。

  • GET /collection:返回资源对象的列表(数组)
  • GET /collection/resource:返回单个资源对象
  • POST /collection:返回新生成的资源对象
  • PUT /collection/resource:返回完整的资源对象
  • PATCH /collection/resource:返回完整的资源对象
  • DELETE /collection/resource:返回一个空文档

5.过滤信息(Filtering)

如果记录数量很多,服务器不可能都将它们返回给用户。API应该提供参数,过滤返回结果。

下面是一些常见的参数。

  • ?limit=10:指定返回记录的数量
  • ?offset=10:指定返回记录的开始位置。
  • ?page=2&per_page=100:指定第几页,以及每页的记录数。
  • ?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。
  • ?animal_type_id=1:指定筛选条件

参数的设计允许存在冗余,即允许API路径和URL参数偶尔有重复。比如,GET /zoo/ID/animals 与 GET /animals?zoo_id=ID 的含义是相同的。

6.状态码(Status Codes)

服务器向用户返回的状态码和提示信息,常见的有以下一些(方括号中是该状态码对应的HTTP动词)。

  • 200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。
  • 201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
  • 202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)
  • 204 NO CONTENT - [DELETE]:用户删除数据成功。
  • 400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。
  • 401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。
  • 403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。
  • 404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。
  • 406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
  • 410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。
  • 422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。
  • 500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。

状态码的完全列表参见这里

如果状态码是4xx,就应该向用户返回出错信息。一般来说,返回的信息中将error作为键名,出错信息作为键值即可。

{
    error: "Invalid API key"
}

6.其他

(1)API的身份认证应该使用OAuth 2.0框架。

(2)服务器返回的数据格式,应该尽量使用JSON,避免使用XML。

 

原文有下:

理解RESTful架构 - http://www.ruanyifeng.com/blog/2011/09/restful.html

RESTful API 设计指南 - http://www.ruanyifeng.com/blog/2014/05/restful_api.html

什么才是真正的 RESTful 架构 - http://blog.csdn.net/lz0426001/article/details/52370193

理解本真的REST架构风格 - http://www.infoq.com/cn/articles/understanding-restful-style/

使用 Spring HATEOAS 开发 REST 服务 - https://www.ibm.com/developerworks/cn/java/j-lo-SpringHATEOAS/

其他参考资料:

Roy Fielding博士论文英文版

Roy Fielding博士论文中文版

HTTP/1.1协议RFC2616RFC2617

posted @ 2018-03-14 16:40  wzbin  阅读(1673)  评论(0编辑  收藏  举报