我所理解的RESTful Web API [设计篇] 我所理解的RESTful Web API [设计篇] Restful的理解,Restful 优缺点

本文转载自文章:我所理解的RESTful Web API [设计篇]html

其余参考文章有:RESTful 介绍 pptRestful的理解,Restful 优缺点理解RESTful架构RESTful API 设计指南HTTP报文web

Web服务已经成为了异质系统之间的互联与集成的主要手段,在过去一段不短的时间里,Web服务几乎清一水地采用SOAP来构建。构建REST风格的Web服务是最近两三年风行的潮流,因此不少人觉得REST是一个事物。而事实倒是:REST自其诞生之日起到如今(2014年)已经有14年了,它为何叫这么一个“奇怪”的名字呢?算法

目录 
1、为何叫这个“奇怪”的名字?数据库

2、采用URI标识资源 segmentfault

3、采用URI标识资源 后端

4、使用“连接”关联相关的资源 api

5、使用统一的接口 跨域

6、使用标准的HTTP方法。浏览器

7、支持多种资源表示方式 缓存

8、无状态性

9、我的总结(本身加的)

1、为何叫这个“奇怪”的名字?

2000年,Roy Thomas Fielding博士在他那篇著名的博士论文《Architectural Styles and the Design of Network-based Software Architectures》中提出了几种软件应用的架构风格,

REST做为其中的一种架构风格在这篇论文的第5章中进行了归纳性的介绍。我我的建议本书的读者都能读读这篇论文,原文和中文译文均可以从网络上找到。

REST是“REpresentational State Transfer”的缩写,能够翻译成“表现状态转换”,可是在绝大多数场合中咱们只说REST或者RESTful。

为何会起这么一个奇怪的名字呢?咱们能够从上述这篇论文中找到答案。Fielding在论文中将REST定位为“分布式超媒体应用(Distributed Hypermedia System)”的架构风格,它在文中提到一个名为“HATEOAS(Hypermedia as the engine of application state)”的概念。

咱们利用一个面向最终用户的Web应用来对这个概念进行简单阐述:

这里所谓的应用状态(Application State)表示Web应用的客户端的状态,简单起见能够理解为会话状态。

资源在浏览器中以超媒体的形式呈现,经过点击超媒体中的连接能够获取其它相关的资源或者对当前资源进行相应的处理,

获取的资源或者针对资源处理的响应一样以超媒体的形式再次呈如今浏览器上。因而可知,超媒体成为了驱动客户端会话状态的转换的引擎。

借助于超媒体这种特殊的资源呈现方式,应用状态的转换体现为浏览器中呈现资源的转换

若是将超媒体进一步抽象成通常意义上的资源呈现(Representation )方式,那么应用状态变成了可被呈现的状态(REpresentational State)。应用状态之间的转换就成了可被呈现的状态装换(REpresentational State Transfer),这就是REST。

REST在我看来是一种很笼统的概念,它表明一种架构风格。对于多个Web应用采用的架构,咱们只能说其中某一个比其它的更具备REST风格,而不能简单粗暴地说:“它采用了REST架构而其它的没有”。

为了将REST真正地落地,Lenoard Rechardson & Sam Ruby在《RESTful Web Services》一书中提出了一种名为“面向资源的架构(ROA: Resource Oriented Architecture)”。

该书中介绍了一些采用ROA架构的Web服务应该具有的基本特征,它们能够指导咱们如何构架具体的RESTful Web API。

2、采用URI标识资源

SOAP Web API采用RPC风格,它采用面向功能的架构,因此咱们在设计SOAP Web API的时候首相考虑的是应高提供怎样的功能(或者操做)。

RESTful Web API采用面向资源的架构,因此在设计之初首先须要考虑的是有哪些资源可供操做。

资源是一个很宽泛的概念,任何寄宿于Web可供操做的“事物”都可视为资源

资源能够体现为通过持久化处理保存到磁盘上的某个文件或者数据库中某个表的某条记录,也能够是Web应用接受到请求后采用某种算法计算得出的结果。

资源能够体现为一个具体的物理对象,它也能够是一个抽象的流程。

一个资源必须具备一个或者多个标识,既然咱们设计的Web API,那么很天然地应该采用URI来做为资源的标识。

做为资源标识的URI最好具备“可读性”,由于具备可读性的URI更容易被使用,使用者一看就知道被标识的是何种资源,好比以下一些URI就具备很好的可读性。

  • http://www.artech.com/employees/c001(编号C001的员工)
  • http://www.artech.com/sales/2013/12/31(2013年12月31日的销售额)
  • http://www.artech.com/orders/2013/q4(2013年第4季度签定的订单)

除了必要的标志性和可选的可读性以外,标识资源的URI应该具备“可寻址性(Addressability)”。也就是说,URI不只仅指明了被标识资源所在的位置,并且经过这个URI能够直接获取目标资源。经过前面的介绍 咱们知道URI具备URL和URN两种主要的表现形式,只要前者具备可寻址性,因此咱们最好采用一个URL做为资源的标识。

URI除了能够标识某个独立的资源外(好比“http://www.artech.com/employees/c001”),还能够标识一组资源的集合或者资源的容器(好比“http://www.artech.com/orders/2013/q4”)。

固然,一组同类资源的集合或者存放一组同类资源的容器自己也能够视为另外一种类型的复合型(Composite)资源,因此“URI老是标识某个资源”这种说法是没有问题的。

3、使用“连接”关联相关的资源

在绝大多数状况下,资源并不会孤立地存在,必然与其它资源具备某种关联。

既然咱们推荐资源采用具备可寻址性的URL来标识,那么咱们就能够利用它来将相关的资源关联起来。

好比咱们采用XML来表示一部电影的信息,那么咱们采用以下的形式利用URL将相关的资源(导演、领衔主演、主演、编剧以及海报)关联在一块儿。

实际上这能够视为一份超文本/超媒体文档。当用户获得这样一份文档的时候,能够利用自身的内容得到某部影片基本的信息,还能够利用相关的“连接”获得其它相关内容的详细信息。

1: <movie>
   2:   <name>魔鬼代言人</name>
   3:   <genre>剧情|悬疑|惊悚</genre>
   4:   <directors>
   5:     <add ref="http://www.artech.com/directors/taylor-hackford">泰勒.海克福德</add>
   6:   </directors>
   7:   <starring>
   8:     <add ref = "http://www.artech.com/actors/al-pacino">阿尔.帕西诺</add>
   9:     <add ref = "http://www.artech.com/actors/keanu-reeves ">基诺.李维斯</add>
  10:   </starring>
  11:   <supportingActors>
  12:     <add ref = "http://www.artech.com/actors/charlize-theron ">查理兹.塞隆</add>
  13:     <add ref = "http://www.artech.com/actors/jeffrey-jones ">杰弗瑞.琼斯</add>  
  14:     <add ref = "http://www.artech.com/actors/connie-nielsen">康尼.尼尔森</add>  
  15:   </supportingActors>
  16:   <scriptWriters>
  17:     <add ref = "http://www.artech.com/scriptwriters/jonathan-lemkin">乔纳森•莱姆金</add>
  19:     <add ref = "http://www.artech.com/scriptwriters/tony-gilroy">托尼•吉尔罗伊 </add>  
  20:   </scriptWriters>
  21:   <language>英语</language>
  22:   <poster ref = "http://www.artech.com/images/the-devil-s-advocate"/>
  23:   <story>...</story>
  24: </movie>

Fielding在他的论文中将REST定位为“分布式超媒体应用”的架构风格,而超媒体的核心就是利用“连接”相关的信息结成一个非线性的网,因此从一点也能够看出REST和“使用连接关联相关的资源”这个特性使吻合的。

4、使用统一的接口

因为REST是面向资源的,因此一个Web API旨在实现针对单一资源的操做。

咱们在前面已经说个,针对资源的基本操做惟CRUD而已,这是使咱们能够为Web API定义标准接口成可能。

所谓的标准接口就是针对不一样资源的Web API定义一致性的操做来操做它们,其接口能够采用相似于下面的模式。

 1: public class ResourceService
   2: {
   3:     public IEnumerable<Resource>[] Get();
   4:     public void Create(Resource resource);
   5:     public void Update(Resource resource);
   6:     public void Delete(string id);
   7: }

可否采用统一接口是RESTful Web API和采用RPC风格的SOAP Web服务又一区别。

若是采用RPC风格的话,咱们在设计Web API的时候首先考虑的是具体哪些功能须要被提供,因此这样的Web API是一组相关功能的集合而已。

以一个具体的场景为例。如今咱们须要设计一个Web API来管理用于受权的角色,它只须要提供针对角色自己的CRUD的功能以及创建/解除与用户名之间的映射关系。若是咱们将其定义成针对SOAP的Web服务,其服务接口具备相似于以下的结构。

1: public class RoleService
   2: {
   3:     public IEnumerable<string> GetAllRoles();
   4:     public void CreateRole(string roleName);
   5:     public void DeleteRole(string roleName);
   6:  
   7:     public void AddRolesInUser(string userName, string[] roleNames);
   8:     public void RemoveRolesFromUser(string userName, string[] roleNames);
   9: }

以下咱们须要将其定义成一个纯粹的RESTful的Web API,只有前面三个方法在针对角色的CRUD操做范畴以内,可是后面两个方法却能够视为针对“角色委派(Role Assignment)”对象的添加和删除操做。因此这里实际上涉及到了两种资源,即角色和角色委派。为了使Web API具备统一的接口,咱们须要定义以下两个Web API。

 1: public class RolesService
   2: {
   3:     public IEnumerable<string> Get();
   4:     public void Create(string roleName);
   5:     public void Delete(string roleName);
   6: }
   7:  
   8: public class RoleAssignmentsService
   9: {
  10:     public void Create(RoleAssignment roleName);
  11:     public void Delete(RoleAssignment roleName);
  12: }

5、使用标准的HTTP方法

因为RESTful Web API采用了同一的接口,因此其成员体现为针对同一资源的操做。

对于Web来讲,针对资源的操做经过HTTP方法来体现。咱们应该将二者统一块儿来,是Web API分别针对CRUD的操做只能接受具备对应HTTP方法的请求。

咱们甚至能够直接使用HTTP方法名做为Web API接口的方法名称,那么这样的Web API接口就具备相似于以下的定义。对于ASP.NET Web API来讲,因为它提供了Action方法名称和HTTP方法的自动映射,因此若是咱们采用这样的命名规则,就无需再为具体的Action方法设定针对HTTP方法的约束了。

1: public class ResourceService
   2: {
   3:     public IEnumerable<Resource>[] Get();
   4:     public void Post(Resource resource); 
   5:     public void Put(Resource resource);
   6:     public void Patch (Resource resource);
   7:     public void Delete(string id);
   8:  
   9:     public void Head(string id);
  10:     public void Options();
  11: }

上面代码片段提供的7个方法涉及到了7个经常使用的HTTP方法,接下来咱们针对资源操做的语义对它们做一个简单的介绍。

首先GET、HEAD和OPTIONS这三个HTTP方法旨在发送请求以或者所需的信息。

对于GET,相应全部人对它已经很是熟悉了,它用于获取所需的资源,服务器通常讲对应的资源置于响应的主体部分返回给客户端。

HEAD和OPTIONS相对少见。从资源操做的语义来说,一个针对某个目标资源发送的HEAD请求通常不是为了获取目标资源自己的内容,而是获得描述目标资源的元数据信息。

服务器通常讲对应资源的元数据置于响应的报头集合返回给客户端,这样的响应通常不具备主体部分。

OPTIONS请求旨在发送一种“探测”请求以肯定针对某个目标地址的请求必须具备怎样的约束(好比应该采用怎样的HTTP方法以及自定义的请求报头),而后根据其约束发送真正的请求。好比针对“跨域资源”的预检(Preflight)请求采用的HTTP方法就是OPTIONS。

至于其它4中HTTP方法(POST、PUT、PATCH和DELETE),它们旨在针对目标资源做添加、修改和删除操做。

对于DELETE,它的语义很明确,就是删除一个已经存在的资源。咱们着重推荐其它三个旨在完成资源的添加和修改的HTTP方法做一个简单的介绍。

经过发送POST和PUT请求都可以添加一个新的资源,可是二者的不一样之处在于

对于前者,请求着通常不能肯定标识添加资源最终采用的URI,即服务端最终为成功添加的资源指定URI;

对于后者,最终标识添加资源的URI是能够由请求者控制的。

也正是由于这个缘由,若是发送PUT请求,咱们通常直接将标识添加资源的URI做为请求的URI;对于POST请求来讲,其URI通常是标识添加资源存放容器的URI。

好比咱们分别发送PUT和POST请求以添加一个员工,标识员工的URI由其员工ID来决定。若是员工ID由客户端来指定,咱们能够发送PUT请求;若是员工ID由服务端生成,咱们通常发送POST请求。具体的请求与下面提供的代码片段相似,能够看出它们的URI也是不同的。

1: PUT http://www.artech.com/employees/300357 HTTP/1.1
   2: ...
   3:  
   4: <employee>
   5:   <id>300357</id> 
   6:   <name>张三</name>
   7:   <gender><gender>
   8:   <birthdate>1981-08-24</birthdate>
   9:   <department>3041</department>
  10: </employee>
 
   1: POST http://www.artech.com/employees HTTP/1.1
   2: ...
   3:  
   4: <employee>
   5:   <name>张三</name>
   6:   <gender><gender>
   7:   <birthdate>1981-08-24</birthdate>
   8:   <department>3041</department>
   9: </employee>

POST和PUT请求通常将所加资源的内容置于请求的主体。可是对于PUT请求来讲,若是添加资源的内容彻底能够由其URI来提供,这样的请求能够不须要主体。好比咱们经过请求添加一个用于控制权限的角色,标识添加角色的URI由其角色名称来决定,而且不须要指定除角色名称的其它信息,那么咱们只要发送以下一个不含主体的PUT请求便可。

 1: PUT http://www.artech.com/roles/admin HTTP/1.1
   2:  
   3: ...

除了进行资源的添加,PUT请求还能用于资源的修改。因为请求包含提交资源的标识(能够放在URI中,也能够置于保存在主体部分的资源内容中),因此服务端可以定位到对应的资源予以修改。

对于POST和PUT,也存在一种一刀切的说法:POST用于添加,PUT用于修改。我我的比较承认的是:若是PUT提供的资源不存在,则作添加操做,不然作修改。

对于发送PUT请求以修改某个存在的资源,服务器通常会将提供资源将原有资源总体“覆盖”掉。若是须要进行“局部”修改,咱们推荐请求采用PATCH方法,由于从语义上讲“Patch”就是打补丁的意思。

6、安全性与幂等性

关于HTTP请求采用的这些个方法,具备两个基本的特性,即“安全性”和“幂等性”。对于上述7种HTTP方法,GET、HEAD和OPTIONS均被认为是安全的方法,由于它们旨在实现对数据的获取,并不具备“边界效应(Side Effect[1])”。

至于其它4个HTTP方法,因为它们会致使服务端资源的变化,因此被认为是不安全的方法。

幂等性(Idempotent)是一个数学上的概念,在这里表示发送一次和屡次请求引发的边界效应是一致的。在网速不够快的状况下,客户端发送一个请求后不能当即获得响应,因为不能肯定是否请求是否被成功提交,因此它有可能会再次发送另外一个相同的请求,幂等性决定了第二个请求是否有效。

上述3种安全的HTTP方法(GET、HEAD和OPTIONS)均是幂等方法。因为DELETE和PATCH请求操做的是现有的某个资源,因此它们是幂等方法。对于PUT请求,只有在对应资源不存在的状况下服务器才会进行添加操做,不然只做修改操做,因此它也是幂等方法。至于最后一种POST,因为它老是进行添加操做,若是服务器接收到两次相同的POST操做,将致使两个相同的资源被建立,因此这是一个非幂等的方法。

当咱们在设计Web API的时候,应该尽可能根据请求HTTP方法的幂等型来决定处理的逻辑。因为PUT是一个幂等方法,因此携带相同资源的PUT请求不该该引发资源的状态变化,若是咱们在资源上附加一个自增加的计数器表示被修改的次数,这实际上就破坏了幂等型。

不过就我我的的观点来讲,在有的场合下针对幂等型要求能够不须要那么严格。举个例子,我对于咱们开发的发部分应用来讲,数据表基本上都有一个名为LastUpdatedTime的字段表示记录最后一次被修改的时间,由于这是为了数据安全审核(Auditing)的须要。在这种状况下,若是接收到一个基于数据修改的PUT请求,咱们老是会用提交数据去覆盖现有的数据,并将当前服务端时间(客户端时间不可靠)做为字段LastUpdatedTime的值,这实际上也破坏了幂等性。

可能有人说咱们能够在真正修改数据以前检查提交的数据是否与现有数据一致,可是在涉及多个表连接的时候这个“预检”操做会带来性能损失,并且针对每一个字段的逐一比较也是一个很繁琐的事情,因此咱们通常不做这样的预检操做。

7、支持多种资源表示方式

资源和资源的表示(Representaion)是两个不一样的概念:

资源自己是一个抽象的概念,是看不见摸不着的,而看得见摸得着的是资源的表现。

好比一个表示一个财年销售状况的资源,它既能够表示为一个列表、一个表格或者是一个图表。若是采用图表,又可使用柱状图、K线图和饼图等,这一切都是针对同一个资源的不一样表示。

咱们说“调用Web API获取资源”,这句话实际上是不正确的,由于咱们获取的不是资源自己,仅仅是资源的某一种表示而已。

对于Web来讲,目前具备两种主流的数据结构,XML和JSON,它们也是资源的两种主要的呈现方式。在多语言环境下,还应该考虑描述资源采用的语言。

咱们在设计Web API的时候,应该支持不一样的资源表示,咱们不能假定请求提供的资源必定表示成XML,也不能老是以JSON格式返回获取的资源,正确的作法是:

根据请求携带的信息识别提交和但愿返回的资源表示。对于请求提交的资源,咱们通常利用请求的Content-Type报头携带的媒体类型来判断其采用的表示类型。对于响应资源表示类型的识别,能够采用以下两种方式。

  • 让请求URI包含资源表示类型,这种方式使用的最多的是针对多语言的资源,咱们通常讲表示语言(也能够包含地区)的代码做为URI的一部分,好比“http://www.artech.com/en/orders/2013”表示将2013年的订单以英文的形式返回。
  • 采用“内容协商(Content Negotiation)”根据请求相关报头来判断它所但愿的资源表示类型,好比“Accept”和“Accept-language”报头能够体现请求能够接受的响应媒体类型和语言。

对于上述两种资源表示识别机制,咱们不少人会喜欢后者,由于第一种不够“智能”。实际上前者具备一个后者不具备的特性:“浏览器兼容型”[2]

对于Web API开发来讲,浏览器应该成为一种最为经常使用的测试工具。在不借助任何插件的状况下,咱们利用浏览器访问咱们在地址栏中输入的URI时对生成的请求内容不能做任何干预的,若是与资源表示相关的信息(好比语言、媒体类型)被直接包含到请求的URI中,那么全部的状况均可以利用浏览器直接测试。

有人从另外一方面对“URI携带资源表示类型”做了这样的质疑:因为URI是资源的标识,那么这致使了相同的资源具备多个标识。其实这是没有问题的,URI是资源的惟一标识,但不是其“惟一的惟一标识“,相同的资源能够具备多个标识。

8、无状态性

RESTful只要维护资源的状态,而不须要维护客户端的状态。对于它来讲,每次请求都是全新的,它只须要针对本次请求做相应的操做(由于其是面向资源的),不须要将本次请求的相关信息记录下来以便用于后续来自相同客户端请求的处理。

对于上面咱们介绍的RESTful的这些个特性,它们都是要求咱们为了知足这些特征作点什么,惟有这个无状态倒是要求咱们不要作什么,由于HTTP自己就是无状态的。

举个例子,一个网页经过调用Web API分页获取符合查询条件的记录。通常状况下,页面导航均具备“上一页”和“下一页”连接用于呈现当前页的前一页和后一页的记录。那么如今有两种实现方式返回上下页的记录。

  • Web API不只仅会定义根据具体页码的数据查询定义相关的操做,还会针对“上一页”和“下一页”这样的请求定义单独的操做。它自身会根据客户端的Session ID对每次数据返回的页面在本地进行保存,以便可以知道上一页和下一页具体是哪一页。
  • Web API只会定义根据具体页码的数据查询定义相关的操做,当前返回数据的页码由客户端来维护。

第一种貌似很“智能”,其实就是一种多此一举的做法,由于它破坏了Web API的无状态性。设计无状态的Web API不只仅使Web API自身显得简单而精炼,

还因减除了针对客户端的“亲和度(Affinty)”使咱们能够有效地实施负载均衡,由于只有这样集群中的每一台服务器对于每一个客户端才是等效的。

9、我的总结

如何理解RESTful API
  是什么
    是一种基于HTTP协议的一套编写客户端和服务器端交互接口的规范。
  特色
    - 采用的是面向资源的架构方式,因此在设计之初首先要考虑的是有哪些资源可供操做。建议在URL中尽可能使用名词
    - 给用户一个URL,根据不一样的METHOD在后端进行不一样的处理。
    - POST 新建一个资源
    - GET 获取一个资源
    - PUT 修改资源
    - DELETE 删除数据
    - 利用HTTP状态码来反应服务器的处理结果
    - 使用JSON或XML等格式来返回结果
    - 使用查询参数过滤信息,由于有时咱们并不须要所有的数据
      ?limit=10
    - 版本话RESTful API,有些数据或程序可能有多个版本共存
      https://api.example.com/version1/

      https://api.example.com/version2/


[1] 大部分计算机书籍都将Side Effect翻译成“反作用”,而咱们通常将“副(负)做用”理解为负面的做用,其实计算机领域Side Effect表示的做用无所谓正负,因此咱们以为仍是还原其字面的含义“边界效用”。除此以外,对于GET、HEAD和OPTIONS请求来讲,若是服务端须要对它们做日志、缓存甚至计数操做,严格来讲这也算是一种Side Effect,可是请求的发送者不对此负责。

[2] 这里的“兼容”不是指支持由浏览器发送的请求,由于经过执行JavaScript脚本可让做为宿主的浏览器发送任何咱们但愿的请求,这里的兼容体如今尽量地支持浏览器访问咱们在地址栏中输入的URI默认发送的HTTP-GET请求。

相关文章
相关标签/搜索