主要资料来源:
1、https://zhuanlan.zhihu.com/p/34289466
2、http://blog.sina.com.cn/s/blog_493a84550102wmsc.html
3、https://segmentfault.com/a/1190000011357198
4、https://www.cnblogs.com/hyhnet/p/5624422.html
5、http://21cto.com/article/1854
6、https://www.cnblogs.com/Irving/p/4964489.html
7、http://blog.nsfocus.net/rest-api-design-safety/
一、什么是web service?
web service是两个设备跨越万维网的连接方式。W3C给web service给出的定义是:支持跨越网络的机器和机器之间交互的软件系统。他有机器可理解格式表达的接口(比如WSDL)。与之交互的其他系统根据预先定义的规则使用SOAP消息来交互,典型的,使用HTTP协议使用XML序列化来和其他系统互联。
二、什么是 SOAP 和 REST?
SOAP(简单对象访问协议)和REST(Representational State Transfer)都是Web服务的通信协议。SOAP长期以来一直是Web服务接口的标准方法,近年来它开始由REST主导,根据Stormpath统计,有超过70%的公共API使用 REST API 。
1、什么是SOAP?
SOAP (Simple Object Access Protocol) 顾名思义,是一个严格定义的信息交换协议,用于在Web Service中把远程调用和返回封装成机器可读的格式化数据。
SOAP 的消息格式使用XML。定义了一整套复杂的标签,以描述调用的远程过程、参数、返回值和出错信息等等。而且随着需要的增长,又不得增加协议以支持安全性,这使SOAP变得异常庞大,背离了简单的初衷。另一方面,各个服务器都可以基于这个协议推出自己的API,即使它们提供的服务及其相似,定义的API也不尽相同,这又导致了WSDL的诞生。WSDL (Web Service Description Language) 也遵循XML格式,用来描述哪个服务器提供什么服务,怎样找到它,以及该服务使用怎样的接口规范,简言之,服务发现。现在,使用Web Service的过程变成,获得该服务的WSDL描述,根据WSDL构造一条格式化的SOAP请求发送给服务器,然后接收一条同样SOAP格式的应答,最后根据先前的WSDL解码数据。绝大多数情况下,请求和应答使用HTTP协议传输,那么发送请求就使用HTTP的POST方法。
SOAP 和传输协议是不相关的(可以使用HTTP,FTP,TCP,UDP,或者命名管道)。基于SOAP的服务严格的定义了来回传递的消息格式。一个SOAP消息上面含有数据和操作,头信息和失败的错误信息。由WS-Security提供安全标准,然后它是端对端的。SOAP提供了让服务想客户端描述自己的机制(WSDL),以及发现(UDDI)。SOAP同样提供了可靠的消息机制(WS-ReliableMessageing),内建了重试并提供了端到端的可靠机制。
SOAP协议规范由4个主要的部分组成。
第一部分:SOAP封装(Envelop)定义了一个的框架(描述消息的内容多少、谁发送、谁应当接受、处理,以及如何处理它们)。
第二部分:SOAP编码规则(Encoding Rules)定义了可选数据编码规则,用于表示应用程序定义的数据类型和直接图表,以及一个用于序列化非语法数据模型统一标准。
第三部分:SOAP RPC表示(RPC Representation)定义一个远程调用风格(请求/响应)信息交换的模式。
第四部分:SOAP绑定(Binding)定义了SOAP和HTTP之间的绑定和使用底层协议的交换。
SOAP协议可以简单地理解为:SOAP=RPC+HTTP+XML,即采用HTTP作为通信协议,RPC(Remote Procedure Call Protocol - 远程过程调用协议)作为一致性的调用途径,XML作为数据传送的格式,从而允许服务提供者和服务客户经过防火墙在Internet上进行通信交互。
2、什么是REST(表示状态转移)?
REST (REpresentational State Transfort) 形式上应该表述为客户端通过申请资源来实现状态的转换,在这个角度系统可以看成一台虚拟的状态机。抛开R. T. Fielding博士论文里晦涩的理论不说,REST应该满足这样的特点:
1)客户端和服务器结构;2)连接协议具有无状态性;3)能够利用Cache机制增进性能;4)层次化的系统;
5)按需代码。
说到底,REST只是一种架构风格,而不是协议或标准。但这种新的风格(也许已经历史悠久?)对现有的以SOAP为代表的Web Service造成的冲击也是革命性的,因为它面向资源,甚至连服务也抽象成资源,因为它和HTTP紧密结合,因为它服务器无状态。
REST是一种架构风格,其核心是面向资源,REST专门针对网络应用设计和开发方式,以降低开发的复杂性,提高系统的可伸缩性。
REST提出设计概念和准则为:
1.网络上的所有事物都可以被抽象为资源(resource)
2.每一个资源都有唯一的资源标识(resource identifier),对资源的操作不会改变这些标识
3.所有的操作都是无状态的
REST简化开发,其架构遵循CRUD原则,该原则告诉我们对于资源(包括网络资源)只需要四种行为:创建,获取,更新和删除就可以完成相关的操作和处理。您可以通过统一资源标识符(Universal Resource Identifier,URI)来识别和定位资源,并且针对这些资源而执行的操作是通过 HTTP 规范定义的。其核心操作只有GET,PUT,POST,DELETE。
由于REST强制所有的操作都必须是stateless的,这就没有上下文的约束,如果做分布式,集群都不需要考虑上下文和会话保持的问题。极大的提高系统的可伸缩性。
REST网络API(或REST网络服务)是使用HTTP和REST 原则实现的网络API。RESTful API更关心用户接口(而不是数据存储)。提供了跨平台和简化服务组件(无状态),每一个来自客户端的请求都包括全部的状态信息,服务器不会再session中保存客户的上下文。
对于谈Restful接口,有两个重点,一个是http无状态接口,一个是ROA面向资源。
因此要了解清楚Restful接口,首先还是要理解清楚一个Http请求的结构和响应输出结构。HTTP请求是指从客户端到服务器端的请求消息。包括:消息首行中,对资源的请求方法、资源的标识符及使用的协议。从客户端到服务器端的请求消息包括,消息首行中,对资源的请求方法、资源的标识符及使用的协议。
HTTP请求信息由3部分组成:
l 请求方法URI协议/版本
l 请求头(Request Header)
l 请求正文
HTTP应答与HTTP请求相似,HTTP响应也由3个部分构成,分别是:
l 状态行
l 响应头(Response Header)
l 响应正文
在接收和解释请求消息后,服务器会返回一个HTTP响应消息。状态行由协议版本、数字形式的状态代码、及相应的状态描述,各元素之间以空格分隔。格式: HTTP-Version Status-Code Reason-Phrase CRLF
可以看一个Http Get请求的例子,如下
1.GET /index.jsp HTTP/1.1
2.Accept-Language: zh-cn
3.Connection: Keep-Alive
4.Host: 192.168.0.106
5.Content-Length: 37
6.userName=new_andy&password=new_andy
请求包的第一行是方法-URI-协议/版本:GET就是请求方法,根据HTTP标准,HTTP协议请求可以使用多种请求方法?HTTP 1.1支持七种请求方法:GET,POST,PUT,DELETE,HEAD,OPTIONS和TRACE等。
而对于Http Rest接口则用的前面标准的四种Http请求方法。
/index.jsp表示URI,URI指定了要访问的网络资源,后面的HTTP/1.1是协议和协议的版本信息。最后一行userName=new_andy&password=new_andy为正文。
资源和ROA面向资源的架构方法
要理解Http Restful接口和架构,另外一个核心就是资源,资源本身就是一个完整的实体信息,可以是一段文本,也可以是图像或者图片。同时可以用XML,JSON,二进制等多种方式表示资源。而当前对于结构化数据的Rest接口,更多的采用Json做为数据表达格式。
如果是结构化数据,对于资源容易理解成数据库中的数据表,但是可以看到很多业务对象是多张数据库表的组合,简单理解成数据库表不合适。其次,资源是否可以理解为类似SOA架构中的业务对象或实体,这个理解也不完整,因为还存在一种情况一个资源表达的信息可能并没有完整的业务实体含义,仅仅是一个实体信息而已。
所以最简单定义还是资源是一个实体信息,里面包含了我们需要的数据信息。
REST架构风格和Http Rest接口基础
基于前面谈到的,要认识Http Rest接口核心的就是其一是基于Http的标准接口,其二是ROA面向资源。
a)对于Http的标准接口,当前支持四种标准的Http接口
GET(SELECT):从服务器取出资源(一项或多项)。
POST(CREATE):在服务器新建一个资源。
PUT(UPDATE):在服务器更新资源(客户端提供完整资源数据)。
DELETE(DELETE):从服务器删除资源。
即我们场景的CRUD操作,都可以通过以上四种标准的Http接口完成。
b) URI资源标识
可以用一个URI(统一资源定位符)指向资源,即每个URI都对应一个特定的资源。要获取这个资源,访问它的URI就可以,因此URI就成了每一个资源的地址或识别符。一般的,每个资源至少有一个URI与之对应,最典型的URI即URL。
因为SOAP并不假定传输数据的下层协议,因此必须设计为能在各种协议上运行。即使绝大多数SOAP是运行在HTTP上,使用URI标识服务,SOAP也仅仅使用POST方法发送请求,用一个唯一的URI标识服务的入口。
举一个图书馆在线查询管理系统为例,服务提供者必须为每一本书提供一个内部标识,然后可能定义一个listBooks操作来返回一系列图书,一个getBook操作来返回指定的图书,一个createBook操作来向数据库加入新增的图书,一个deleteBook操作来删除作废的图书,每个操作都有各自的参数,尤其是用内部标识来标识操作的图书。这种设计被诟病之处,在于deleteBook操作也要用POST方法来发送,而其实HTTP协议有更和逻辑的DELETE方法可用。REST正是这样设计的,REST为每一个资源(此处是图书)指定一个唯一的URI,而用HTTP的4种方法GET、POST、PUT、DELETE直观地表示获取、创建、更新和删除图书。同时图书集合也是和单本的图书不同的资源,如果用/books来代表图书列表,/books/ID来代表标识为ID的图书,那么对/books的GET操作就代表返回整个图书列表,对/books/ID的DELETE操作代表删除指定的图书,等等。
REST的优点
REST简单而直观,把HTTP协议利用到了极限,在这种思想指导下,它甚至用HTTP请求的头信息来指明资源的表示形式(如果一个资源有多种形式的话,例如人类友善的页面还是机器可读的数据?),用HTTP的错误机制来返回访问资源的错误。由此带来的直接好处是构建的成本减少了,例如用URI定位每一个资源可以利用通用成熟的技术,而不用再在服务器端开发一套资源访问机制。又如只需简单配置服务器就能规定资源的访问权限,例如通过禁止非GET访问把资源设成只读。
服务器无状态带来了更多额外好处,因为每次请求都包含响应需要的所有信息,所有状态信息都存储在客户端,服务器的内存从庞大的状态信息中解放出来。而且现在即使一台服务器突然死机对客户的影响也微乎其微,因为另一台服务器可以马上代替它的位置,而不需要考虑恢复状态信息。更多的缓存也变成可能,而之前由于服务器有状态,对同一个URI的请求可能导致完全不同的响应。
总体结果是,网络的容错性和延展性都增强了,这些本来是WEB设计的初衷,日趋复杂和定制的WEB把它们破坏了,现在REST又返璞归真,试图把Web Service带回简单的原则中来。
REST是万能的吗?
但是REST就是万能的吗?无状态带来了巨大的优势,同时也带来了难以解决的问题,例如,怎样授权特定用户才能使用的服务?怎样验证用户身份?如果坚持服务器无状态,也就是不记录用户登录状态,势必要求每一次服务请求都包含完整的用户身份和验证信息。在这种情况下,怎样避免冒认?怎样避免用户信息泄漏?事实上,构建REST附属的安全机制已经在讨论中,其结果无非导致另一个SOAP:复杂的需求摧残了易用性。
REST的支持者声称REST的请求和应答数据简单可读,而SOAP则需要一系列繁琐的封装;即使如此,SOAP仍然不能达到接口的一致性,不同的厂商有各自的接口,而REST只使用HTTP定义的方法,因此是通用的。事实确实如此吗?试想用REST实现两数求和的服务,如果按照建议的做法,把服务(此处是加法)作为一个资源,参数(此处是两个加数)作为请求的参数,结果以XML或JSON语法返回,是否比SOAP更简单易用?通用接口仍然没法达到,因为资源的名称、参数的名称、结果的格式仍然是服务提供者定义的。
为了解决这个问题,提出了WASL(Web Application Description Language)来描述REST接口。WADL就像是WSDL的REST版,随着REST被应用到复杂的领域,SOAP的影子无处不在。
面向资源和面向事务(非常明显的说明了Rest的试用范围,请求地图数据就可以认为主要是请求一种特殊的资源)
REST在面向资源的应用中左右逢源,但在面向事务的应用中却未如人意。面向资源的应用操作简单,无非创建、读取、改变、删除几项,但是面向事务的应用不允许用户直接操作资源,用户只需向系统提交一个事务说明要求,然后等待事务的完成,就如一个网上银行的用户不直接修改账户和存款,而是提交一个事务告诉银行自己要转账。如果把这样的服务看成一种资源,通过向资源发送POST请求完成事务,那不过是SOAP的翻版而已,无论是这样,还是通过PUT来创建事务,都改变了系统的状态(资源本身未改变,此处是改变了用户的余额),显然违背了REST直观的初衷。
事实上,一些Web Service提供者提供的REST API只有REST的外壳,传输的请求和应答全然是简化了的SOAP,这种新瓶装旧酒的做法只是加深了标准的分歧而已。归根结底REST无法简单地解决一些应用,因此我们只能看到SOAP在REST外壳下的借尸还魂。没有一项技术能一劳永逸地解决所有问题,只需要在预定的约束下优美地解决所在领域的问题就足够了。一项新技术推出的时候总是引来无数的跟风和吹捧,只有当尘埃落定之后才能得到中肯的评价。
三、SOAP 和 REST的异同
1、简述
从目前的两种技术发展来看,两种方法都是webservice( RPC ) 的实现,调用一个接口,然后取得一些参数,进行跨系统的通信。
从标准上看,REST是一种思想,在http(s)上套了一些操作守则;而SOAP是带有强规范 WS-(X) 的标准。
所以,SOAP肯定会有相应的软件组件来构建、验证监测webservice,而REST的实现就五花八门了,其实现在很多大公司的OPEN API ,很多都像是在SOAP的基础上改造的,不大符合REST的思想。
2、对于REST接口服务和SOAP的核心区别点
SOAP一个主要的有点是WSDL服务描述。可以自动发现服务并生成服务代理客户端。在版本2.0中,WSDL支持所有的HTTP谓词并能很好的提供RESTful服务文档,但是WSDL相对来说比较臃肿。
HTTPS对RESTful服务的安全提供支持,仅是point-to-point的。缺乏标准的信息系统和异常处理。而SOAP具有内建的成功/重试机制,且提供end-to-end的基于SOAP的可靠机制。
RESTful服务一个主要的有点是其数据表示的灵活性,比如你可以序列化你的数据到XML或JSON格式。RESTful API是易于理解的,一个使用URI的元素+HTTP谓词。
同时RESTful服务也是轻量的,相比xml,没有额外的标签。调用RESTful API时,只需要一个浏览器或任何一台联网的设备。
要理解SOAP,首先要进一步了解RPC远程对象访问协议和框架。对于早期的Web Service服务接口,基于是基于XML-RPC方式,到了SOAP WebSerivce则变化为Http+XML+RPC的一个组合。
即RPC协议可以基于TCP,也可以基于HTTP,而对于SOAP WebService一般则是基于Http协议的。而对于Http Restful WebService也是基于Http协议。
而对于SOAP本身又是对RPC框架的进一步封装,有相当严格的消息头,报文信封和报文内容格式的要求,也有明确的调用方法接口,接口传递数据格式的约束。而对于REST接口则是准确Http标准请求即可。
其次SOAP的服务接口一般只启用了HTTP POST,正是由于这个原因可以看到对于任何一个数据对象的CRUD操作,我们需要开发和定义四个不同的CRUD接口方法才能完成。而对于REST接口则更加简单,只需要定义清楚资源实体和URI标识,然后通过传递HTTP请求的不同类型来完成CRUD操作。
对于SOAP和REST我们经常谈到的区别是内容是走XML还是JSON格式,是否有很强的契约规范约束方面,但是从上面的分析可以看到,其最核心的区别还是在ROA面向资源架构风格上面。
资源和领域对象的概念类似,但是不能完全划等号,同时资源的定义和设计仍然体现了SOA架构中常谈到的可复用特性。这是在资源设计中的一个重点。
具体更加详细的内容参考如下资料:
Restful API编写指南:http://blog.igevin.info/posts/restful-api-get-started-to-write/
Restful 架构风格:http://blog.igevin.info/posts/restful-architecture-in-general/
HTTP协议入门课程:
https://yq.aliyun.com/articles/64416?utm_campaign=wenzhang&utm_medium=article&utm_source=QQ-qun&utm_content=m_8116
3、应用场景
SOAP偏向于面向活动,有严格的规范和标准,包括安全,事务等各个方面的内容。
SOAP强调操作方法和操作对象的分离,有WSDL文件规范和XSD文件分别对其定义。
而REST强调面向资源,只要我们要操作的对象可以抽象为资源即可以使用REST架构风格。
如何确定使用REST:
若本身只是简单的CRUD业务操作,那么抽象资源就比较容易。
而对于复杂的业务活动抽象资源并不是一个简单的事情,比如校验用户等级,转账,事务处理等。
如何确定使用SOAP:
若有严格的规范和标准定义要求,且前期需要指导多个业务系统集成和开发的时,
因SOAP风格有清晰的规范标准定义,SOAP更适合。
我们可以在开始和实现之前就严格定义相关的接口方法和接口传输数据。
一句话:
简单数据操作,无事务处理,开发和调用简单使用REST架构风格较好。
再者:
REST核心是url和面向资源,url代替了原来复杂的操作方法。
REST允许我们通过url设计系统,就像测试驱动开发使用测试用例设计类接口一样。
所有可以被抽象为资源的东西都可以使用RESTful的url。
REST关键:
使用REST的关键是如何抽象资源,抽象的越精确,对REST的应用越好。
4、关键点比较
(1)成熟度(总的来说SOAP在成熟度上优于REST)
SOAP对于异构环境服务发布和调用,以及厂商的支持都已经达到了较为成熟的情况。
REST国外很多大网站都发布了自己的开发API,很多都提供了SOAP和REST两种Web Service,
但是由于REST只是一种基于Http协议实现资源操作的思想,因此各个网站的REST实现都自有一套。
REST实现的各种协议仅仅只能算是私有协议,当然需要遵循REST的思想。
(2)效率和易用性(REST更胜一筹)
SOAP协议对于消息体和消息头都有定义,同时消息头的可扩展性为各种互联网的标准提供了扩展的基础,
WS-*系列就是较为成功的规范。但是也由于SOAP由于各种需求不断扩充其本身协议的内容,导致在SOAP
处理方面的性能有所下降。同时在易用性方面以及学习成本上也有所增加。
REST被人们的重视,其实很大一方面也是因为其高效以及简洁易用的特性。
这种高效一方面源于其面向资源接口设计以及操作抽象简化了开发者的不良设计,
同时也最大限度的利用了Http最初的应用协议设计理念。
同时,REST很好的融合当前Web2.0的很多前端技术来提高开发效率。
例如:很多大型网站开放的REST风格的API都会有多种返回形式(XML,JSON,RSS,ATOM)等形式。
(3)安全性
SOAP在安全方面使用XML-Security和XML-Signature两个规范组成了WS-Security来实现安全控制的,
当前已经得到了各个厂商的支持,.net ,php ,java 都已经对其有了很好的支持。
REST 开放REST风格API的网站主要分成两种:
一种是自定义了安全信息封装在消息中,
另外一种就是靠硬件SSL来保障,这只能够保证点到点的安全,如果是需要多点传输的话SSL就无能为力了。
安全这块其实也是一个很大的问题。
REST安全性依赖于传输,而SOAP安全性则不依赖。
在大多数情况下,用REST或SOAP都可以实现相同的结果(两者都可以无限扩展),两者在配置方式上还存在一些差异。
SOAP最初是由微软创建的,大概有十四五年的时间,它可比REST的资历要长得多,有传统协议的优势。REST现在也已经存在了一段时间,它使用HTTP协议,以一种比使用SOAP更简单的方式访问Web服务的方式来实现 API。
REST 比 SOAP 好用的几点:
除了使用HTTP的简单性之外,REST还提供了许多与SOAP相比的其它好处:
1)REST允许更多类型的数据格式,而SOAP只能使用 XML
2)与JSON(JSON可以更好地处理数据并提供更快的解析)相结合,让REST更易于使用。
我们要感谢JSON,REST为浏览器客户端提供了更好的支持。
3)REST提供了卓越的性能,特别是通过缓存来存储不经常改动的信息。
REST被雅虎,Ebay,亚马逊和谷歌等主要API服务的协议。
REST速度更快并且使用更少的网络带宽。它可以更轻松地与现有网站、APP 等应用集成,无需重构站点基础结构。这使得开发人员能够更快地工作,而不是花时间从头开始重写网站。另外,人们可以简便地添加其他功能。
但是,SOAP仍然存在于某些应用的首选协议中。在大部分应用中,REST仍是应用的首选协议,除非有令人信服的理由使用SOAP协议。
SOAP 好于 REST的一些特性
人们可以使用任一协议实现大多数的结果,有时候是个人偏好的问题。有一些用例更适合SOAP。比如,我们的应该需要很强的安全性,SOAP协议的WS-Security支持就可以派上用场了,它为数据隐私和数据完整性提供了非常好的保证,它还通过第三方提供身份验证支持,而不仅仅是SSL提供的点对点(SOAP和REST协议都支持SSL)协议。
SOAP的另一个优点,它提供了内置的重试逻辑来修复通信出错的问题。而REST没有内置的消息传递系统,如果通信失败,客户端需要重试来处理,REST也没有标准的规则集合,服务器和消费者需要理解内容与上下文。
再说一说SOAP的其它好处:
SOAP的标准HTTP协议可以跨防火墙和代理服务器进行操作,且无需修改 SOAP协议本身。由于 SOAP 使用的是复杂的XML格式,与ICE和COBRA等中间件相比,它的速度比较慢。
某些用例需要比使用HTTP 所能实现的事务,要求可靠性更高的可靠性,以及需要符合ACID标准的事务, 那么 SOAP 就是我们的不二之选。
在某些情况下,与REST相比,设计SOAP服务实际上也不那么复杂,对于支持复杂操作的Web服务,需要维护内容和上下文。设计SOAP服务需要在应用程序层中对事务、安全性、信任和其它元素进行较少的编码。
SOAP可以通过其它协议技术支持非常高的安全性和扩展性。除了WS-Security之外,SOAP还支持WS-Addressing,WS-Coordination,WS-ReliableMessaging等其他Web服务标准,这些标准可以在W3C上找到。
五、关于REST API设计
硅谷的apigee公司给出一份对REST API的设计指导原则,可以说这家公司在api开发,管理的成绩有目共睹。其提供的指导原则,可以说结合了其自身实际开发经验,诸多大型平台的实际运营经验和标准http规范。非常值得一读。首先,你需要对REST API有一个基本的概念认知,然后再深入阅读:1. 基于业务领域的数据建模,而非基于功能建模。
例如,取得所有的dog
GET /api/dogs
取得一个特定的dog
GET /api/dogs/{id}
取得特定名字的dogs
GET /api/dogs/?name=xxx
创建一个dog
POST /api/dogs
更改一个dog
PUT /api/dogs/{id}
删除一个dog
DELETE /api/dogs/{id}
标准的HTTP的方法已经提供了一套约定俗成的操作语义。而一个基于功能建模的api,通常会是下面的样子:
/getAllDogs
/getDogsByNames
/getAllBabyDogs
/createDogs
/createThreeDogs
/saveDogs
以上这些经常在新手的代码里看到。这样做的代码,没错,可以运行,但是不‘标准’ why?基于功能建模的api,首先会造成学习曲线的增长,不容易上手,也往往意味着需要记忆大量的url(swagger会解决一部分这个问题,但不是全部)。使用HTTP的标准方法作为操作数据的基本语义,胜在其标准的普适性。这一点上,大的平台Github,Heroku等,做的是最好的。2. 设计数据的表现形式毫无疑问,使用JSON,今天JSON已经是事实上的web数据标准,简单易懂。但是JSON也有其缺点,比如如何表达Date和Timestamp,一个简单的做法是用string来表达,根据上下文来判断如何解释。尽量保持JSON数据的简洁性。并且使用link去表达资源之间的联系:
例如:
{
"id": "12345678",
"kind": "Dog"
"name": "Lassie",
"furColor": "brown",
"ownerID": "98765432"
}
更好的方法:
{
"id": "12345678",
"kind": "Dog"
"name": "Lassie",
"furColor": "brown",
"ownerID": "98765432",
"ownerLink": "https://dogtracker.com/persons/98765432"
}
更简洁的表示:
{
"id": "12345678",
"kind": "Dog"
"name": "Lassie",
"furColor": "brown",
"owner": "https://dogtracker.com/persons/98765432"
}
这样做的好处是客户端不需要自己重新构建URL去获取owner,更方便使用。
Google Drive API 和 Github 均使用这种方式。但这样做也不是没有坏处,最容易想到的是,如果url改变了怎么办?production,staging,testing用的是不一样的domain哟。
一个方法是:使用相对路径,而不是绝对路径。但这也不能解决全部问题。
设计URL的表现形式两大原则:规范的(regular),可预测的(predictable)。只使用名词要有切入点要合适的选择id形式要表达资源间的联系要支持查询要支持返回部分资源处理更复杂的计算逻辑只使用名词:在URL中使用名词,避免动词,一旦使用动词,意味着你是在对功能建模,而非数据。
要有切入点:原则上来讲,一个api应该有一个root path ‘/’, 其返回一个url map,包括了所有的resouces所对应的url。这样客户端更容易去发现和使用api。要合适的选择id形式:例如api/dogs/{id} 中的{id}如何表达,是类似于‘/dogs/1’,还是‘/dogs/haha’? 一般来说取决于后端的数据库,大多数情况下,使用RDBMS,像mysql之类的主键自增功能,我比较倾向于使用自增主键的整数直接作为entity的id,避免很多问题,如果使用MongoDB的话,不妨试一试用字符串作为entity的id,可读性会提高,但是如何维护一个全局唯一的字符主键,你得三思。要表达资源间的联系:比如 GET /persons/1/dogs 返回所有属于person 1的狗。这种模式可以表述为:/{relationship-name}[/{resource-id}]/…/{relationship-name}[/{resource-id}]
要有切入点:原则上来讲,一个api应该有一个root path ‘/’, 其返回一个url map,包括了所有的resouces所对应的url。这样客户端更容易去发现和使用api。要合适的选择id形式:例如api/dogs/{id} 中的{id}如何表达,是类似于‘/dogs/1’,还是‘/dogs/haha’? 一般来说取决于后端的数据库,大多数情况下,使用RDBMS,像mysql之类的主键自增功能,我比较倾向于使用自增主键的整数直接作为entity的id,避免很多问题,如果使用MongoDB的话,不妨试一试用字符串作为entity的id,可读性会提高,但是如何维护一个全局唯一的字符主键,你得三思。
要表达资源间的联系:比如 GET /persons/1/dogs 返回所有属于person 1的狗。这种模式可以表述为:/{relationship-name}[/{resource-id}]/…/{relationship-name}[/{resource-id}]要有切入点:原则上来讲,一个api应该有一个root path ‘/’, 其返回一个url map,包括了所有的resouces所对应的url。这样客户端更容易去发现和使用api。要合适的选择id形式:例如api/dogs/{id} 中的{id}如何表达,是类似于‘/dogs/1’,还是‘/dogs/haha’? 一般来说取决于后端的数据库,大多数情况下,使用RDBMS,像mysql之类的主键自增功能,我比较倾向于使用自增主键的整数直接作为entity的id,避免很多问题,如果使用MongoDB的话,不妨试一试用字符串作为entity的id,可读性会提高,但是如何维护一个全局唯一的字符主键,你得三思。要表达资源间的联系:比如 GET /persons/1/dogs 返回所有属于person 1的狗。
这种模式可以表述为:/{relationship-name}[/{resource-id}]/…/{relationship-name}[/{resource-id}]
要支持查询:GET /persons;1/dogs GET /persons;name=blabla/dogs
这种模式可以表述为:/{relationship-name}[;{selector}]/…/{relationship-name}[;{selector}]
更复杂的查询条件:GET /dogs?color=red&state=running&location=park
注意这里的三个查询子条件之间的关系是‘与(and)’,如果要表达是‘或(or)’的逻辑,那就得设计更复杂的query解释机制。但其实实际使用中,表达‘或’的查询条件很少使用。要支持返回部分资源: /dogs?fields=name,color,location
返回的resource中,只包含name,color,location三种信息。处理更复杂的计算逻辑: 有很多例子,比如货币转换,大多数开发人员给出的方案是:/convert/100/EUR/CNY 或者 /convert?quantity=100&unit=EUR&in=CNY 切记:URL是用来表述资源resource,而不是表述计算的过程。在URL中使用名词,避免动词。
改进的方案1:
GET /monetary-amount/100/EUR HTTP/1.1
Host: Currency-Converter.com
Accept-Currency:CNY //在http的header中添加Accept-Currency来指明货币的种类。
改进的方案2:
POST /currency-converter HTTP/1.1
Host: Currency-Converter.com
Content-Length: 69
{
"amount": 100,
"inputCurrency": "EUR",
"outputCurrency": "CNY"
}
两个方案都可行,但是方案2有两个注意的地方:POST返回的结果可能无法再server端缓存;你是在构建一个计算的过程,而非资源的表述,如何理解?就像数据库操作中的‘store procedure’,你可以使用,并且功能强大,但是接口变得复杂,逻辑变得耦合。4. 反思-设计数据的表现形式。添加self link集合数据 数据分页数据格式添加self link:self link提供了一个上下文环境,客户端可以更容易理解当前的resource的位置
{
"user": {
"html_url": "octocat (The Octocat)",
"type": "User",
"url": "https://api.github.com/users/octocat"
}
}
集合数据:方案1:集合collection也是一种resource,也具有self和kind属性,这样所有的单独entity和collection都具有更加统一的规范
{
"self": "https://dogtracker.com/dogs",
"kind": "Collection",
"contents": [
{
"self": "https://dogtracker.com/dogs/12344",
"kind": "Dog",
"name": "Fido",
"furColor": "white"
},
{
"self": "https://dogtracker.com/dogs/12345",
"kind": "Dog",
"name": "Rover",
"furColor": "brown"
}
]
}
方案2:看起来更加简洁,但是客户端可能需要去添加额外的逻辑去处理
collection[
{
"self": "https://dogtracker.com/dogs/12344",
"kind": "Dog",
"name": "Fido",
"furColor": "white"
},
{
"self": "https://dogtracker.com/dogs/12345",
"kind": "Dog",
"name": "Rover",
"furColor": "brown"
}
]
还有一种做法是,针对一个collection,使用自定义的media type header(比如‘Collection+JSON’)这个方法可行,但是会让客户端的处理逻辑复杂。数据分页:当数据返回的集合变大时,显然不可能一次性把所有数据都返回给客户端,最好能分批的返回,比如:
GET https://dogtracker.com/dogs?limit=25,offset=0
返回
{
"self": "https://dogtracker.com/dogs?limit=25,offset=0",
"kind": "Page",
"pageOf": "https://dogtracker.com/dogs",
"next": "https://dogtracker.com/dogs?limit=25,offset=25",
"contents": [...】
}
在返回的数据中,加入‘pageOf’来指明查询的起点,‘next’指明下一页的url,当返回第二页的时候,还需加入‘previous’来指明上一页。
数据格式:现在大多数的api几乎只支持JOSN格式的数据来作为input和output,如果要支持更多的数据格式,那么应该要支持Http Accept Header。
能否用HTML作为输出的格式?可以,但是这样就丧失的rest API的灵活性。现代的web应用,大多使用REST API + SPA的设计,SPA端使用Angular等框架,自己渲染HTML,REST API只提供数据服务,前端后端通过JSON数据来交流,从而实现了前后端的彻底解耦。
如果选择JSON作为唯一的数据格式,那么最好支持Http的patch方法,现在有两种patch的模式:JSON Patch和JSON Merge Patch,选择一个来用于资源的更新操作。
现在也有很多API只提供PUT来更新资源,这意味着每次请求都必须发送整个resource enrity,势必会消耗更多的payload,但是实现起来更容易。
错误处理总的原则:使用标准http的status code来表示错误的类型。具体的错误内容,也要被返回。
认证和授权人生苦短,使用OAuth2。
token的鉴权模式。
Versioning
REST API的版本控制问题是一个非常有争议的话题,网上的提议有很多,在这里我们不是简单的给定具体的方法,而是提供几种可行的想法,具体的实施还需自己拿捏:不(显式)支持多版本使用Http Accept Header第一种,什么都不做,不支持多版本的api。这个想法的背后依据是,根据调研发现,大多数的中小型规模的平台服务,客户规模都在一个可控的范围,api的升级不会很频繁,你只需通知你的客户,在某个时间点api会更新,然后再server端做一些兼容性的数据迁移,比如增加或删除某个数据库中的表的某个列的名字。大多数情况下,支持多版本api费力不讨好,测试和部署的成本很大,收益却很小。你要做就是保持唯一个可用api服务的兼容性,而不是提供多个版本的api让用户使用。第二种,如果你一定要支持versioning,那么就在http的accept header中添加version信息,不要在url中使用version信息,千万不要用/api/v1/xxx。
六、关于RESTAPI的安全性
在之前的文章REST API 安全设计指南与使用 AngularJS & NodeJS 实现基于 token 的认证应用两篇文章中,[译]web权限验证方法说明中也详细介绍,一般基于REST API 安全设计常用方式有:
1、HTTP Basic
Basic admin:admin
Basic YWRtaW46YWRtaW4=
Authorization: Basic YWRtaW46YWRtaW4=
由于HTTP协议是无状态的,所有每次请求都得带上身份信息,基于Http basic验证就是简单的将用户名和密码base64编码放到header中,一般需要HTTPS,安全性较低,实现的方式可以基于代码实现也可以基于web容器配置apache,nginx等web服务器即可实现。
2、HTTP Digest
摘要认证 digest authentication,服务器端以nonce进行质询,客户端以用户名,密码,nonce,HTTP方法,请求的URI等信息为基础产生的response信息进行认证的方式。
※ 不包含密码的明文传递
摘要认证步骤:
(1)客户端访问一个受http摘要认证保护的资源。
(2)服务器返回401状态以及nonce等信息,要求客户端进行认证。
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Digest
realm="[email protected]",
qop=“auth,auth-int”,
nonce=“dcd98b7102dd2f0e8b11d0f600bfb0c093”,
opaque=“5ccc069c403ebaf9f0171e9517f40e41”
(3) 客户端将以用户名,密码,nonce值,HTTP方法, 和被请求的URI为校验值基础而加密(默认为MD5算法)的摘要信息返回给服务器。
认证必须的五个情报:
・ realm : 响应中包含信息
・ nonce : 响应中包含信息
・ username : 用户名
・ digest-uri : 请求的URI
・ response : 以上面四个信息加上密码信息,使用MD5算法得出的字符串。
Authorization: Digest
username="Mufasa", ← 客户端已知信息
realm="[email protected]", ← 服务器端质询响应信息
nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", ← 服务器端质询响应信息
uri="/dir/index.html", ← 客户端已知信息
qop=auth, ← 服务器端质询响应信息
nc=00000001, ← 客户端计算出的信息
cnonce="0a4f113b", ← 客户端计算出的客户端nonce
response="6629fae49393a05397450978507c4ef1", ← 最终的摘要信息 ha3
opaque="5ccc069c403ebaf9f0171e9517f40e41" ← 服务器端质询响应信息
(4) 如果认证成功,则返回相应的资源。如果认证失败,则仍返回401状态,要求重新进行认证。
注意事项:
(1)避免将密码作为明文在网络上传递,相对提高了HTTP认证的安全性。
(2).当用户为某个realm首次设置密码时,服务器保存的是以用户名,realm,密码为基础计算出的哈希值(ha1),而非密码本身。
(3) 如果qop=auth-int,在计算ha2时,除了包括HTTP方法,URI路径外,还包括请求实体主体,从而防止PUT和POST请求表示被人篡改。
(4)但是因为nonce本身可以被用来进行摘要认证,所以也无法确保认证后传递过来的数据的安全性。
※ nonce:随机字符串,每次返回401响应的时候都会返回一个不同的nonce。
※ nounce:随机字符串,每个请求都得到一个不同的nounce。
※ MD5(Message Digest algorithm 5,信息摘要算法)
1)用户名:realm:密码 ⇒ ha1
2)HTTP方法:URI ⇒ ha2
3)ha1:nonce:nc:cnonce:qop:ha2 ⇒ ha3
3、WSSE(WS-Security)
WSSE UsernameToken
服务器端以nonce进行质询,客户端以用户名,密码,nonce,HTTP方法,请求的URI等信息为基础产生的response信息进行认证的方式。
※ 不包含密码的明文传递
WSSE认证步骤:
1. 客户端访问一个受WSSE认证保护的资源。
2. 服务器返回401状态,要求客户端进行认证。
HTTP/1.1 401 Unauthorized
WWW-Authenticate: WSSE
realm="[email protected]",
profile="UsernameToken" ← 服务器期望你用UsernameToken规则生成回应
※ UsernameToken规则:客户端生成一个nonce,然后根据该nonce,密码和当前日时来算出哈希值。
3. 客户端将生成一个nonce值,并以该nonce值,密码,当前日时为基础,算出哈希值返回给服务器。
Authorization: WSSE profile="UsernameToken"
X-WSSE:UsernameToken
username="Mufasa",
PasswordDigest="Z2Y......",
Nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
Created="2010-01-01T09:00:00Z"
4. 如果认证成功,则返回相应的资源。如果认证失败,则仍返回401状态,要求重新进行认证。
4、API KEY
API Key就是经过用户身份认证之后服务端给客户端分配一个API Key,
类似:
http://example.com/api?key=dfkaj134
client端向服务端注册,服务端给客户端发送响应的api_key以及security_key,注意保存不要泄露,然后客户端根据api_key,secrity_key,timestrap,rest_uri采用hmacsha256算法得到一个hash值sign,构造途中的url发送给服务端。 服务端收到该请求后,首先验证api_key,是否存在,存在则获取该api_key的security_key,接着验证timestrap是否超过时间限制,可依据系统成而定,这样就防止了部分重放攻击,途中的rest_api是从url获取的为/rest/v1/interface/eth0,最后计算sign值,完之后和url中的sign值做校验。这样的设计就防止了数据被篡改。 通过这种API Key的设计方式加了时间戳防止了部分重放,加了校验,防止了数据被篡改,同时避免了传输用户名和密码,当然了也会有一定的开销。
一般的处理流程如下: 一个简单的设计示例如下:
一般会分配app_key,sign_key两个值,将通知过来的所有参数,除去sign本身,以及值是空的参数,按参数名字母升序排序,然后按参键值…参数n值n的方式进行连接,
得到一个字符串然后在连接后得到的字符串前面加上通知验证密钥(sign_key, 不同于app_key),然后计算sha1值,转成小写
比如请求的参数为:
?sign=9987e6395c239a48ac7f0d185c525ee965e591a7&verifycode=123412341234&app_key=ca2bf41f1910a9c359370ebf87caeafd&poiid=12345&time
stamp=1384333143&poiname= 海底捞(朝阳店)&v=1
去掉sign参数,其余的按参数名升序排列:app_keyca2bf41f1910a9c359370ebf87caeafdpoiid12345poiname海底捞(朝阳店)timestamp1384333143v
1verifycode123412341234
假设sign_key为21be83530509abc81aa945a02bec37601cf3cc21,我们把sign_key放在上面的字符串的前面:21be83530509abc81aa945a02bec37601c
f3cc21app_keyca2bf41f1910a9c359370ebf87caeafdpoiid12345poiname海底捞(朝阳店)timestamp1384333143v1verifycode123412341234
计算sha1()结果为:9987e6395c239a48ac7f0d185c525ee965e591a7
微信V3支付与美团券(以上是美团核销券文档摘录)采用此种方式,对外有一个统一的服务网关,一般如果设计到安全性重要的接口,再加上数字证书(如微信支付的退款接口),适用服务端对服务端的应用。
另外,比如图灵机器人开放API,API key如下:
具体见:http://www.tuling123.com/help/h_cent_webapi.jhtml?nav=doc
此外,比如高德Web API,
申请Web服务API服务需要注意什么?
有两点需要注意:
1、申请高德Web服务API,为了您的使用安全,建议为Key绑定IP白名单。这样,只有来自于IP白名单的请求,才会被正常响应。此处IP白名单中的IP,是指使用者服务器出口的IP。
2、请各位合作伙伴注意:使用高德Web服务API,严禁做压力测试,如擅自做压力测试,系统立即会识别出来,并自动封停服务。由此引发的损失高德概不负责。
5、OAUTH 2.0
OAuth协议适用于为外部应用授权访问本站资源的情况。其中的加密机制与HTTP Digest身份认证相比,安全性更高。使用和配置都比较复杂。
OATH2设计更侧重对资源的授权,OAUTH2规范定义了Authorization Code,Resource Owner Password Credentials,Client Credentials ,Implicit Grant几种实现方式,具体看:OAuth2.0基础概述 ,Authorization Code 模式侧重对第三方用户资源的授权(如QQ联合登录,微博开放平台等),Resource Owner Password Credentials 侧重对个人用户资源授权(如App),Client Credentials 侧重对客户端的资源授权,Implicit Grant 是一种最简化模式,如网页中JS中调用,适用场景比较局限。
6、HMAC
ASP.NET Web API Security Filters
1) 先由客户端向服务器发出一个验证请求。
2) 服务器接到此请求后生成一个随机数并通过网络传输给客户端(此为质疑)。
3) 客户端将收到的随机数提供给ePass,由ePass使用该随机数与存储在ePass中的密钥进行HMAC-MD5运算并得到一个结果作为认证证据传给服务器(此为响应)。
4) 与此同时,服务器也使用该随机数与存储在服务器数据库中的该客户密钥进行HMAC-MD5运算,如果服务器的运算结果与客户端传回的响应,结果相同,则认为客户端是一个合法用户
7、URL过滤
在进入逻辑处理之前,加入对URL的参数过滤,如/site/{num}/policy 限定num位置为整数等,如果不是参数则直接返回非法参数,设定一个url清单,不在不在url清单中的请求直接拒绝,这样能防止开发中的api泄露。rest api接口一般会用到GET,POST,PUT,DELETE,未实现的方法则直接返回方法不允许,对于POST,PUT方法的数据采用json格式,并且在进入逻辑前验证是否json,不合法返回json格式错误。
8、重要功能加密传输
第一步推荐SSL加密传输,同时对于系统中重要的功能做加密传输,如证书,一些数据,配置的备份功能,同时还得确保具备相应的权限,这一步会在授权中涉及。
9、应对大规模访问的对策
这个是对抗DDOS攻击的有效方式。如果不设定容量管理,很可能成为攻击的对象。
具体来讲:
(1)限制每个用户的访问
(2)限速的单位
(3)应对超出上限值的情况
(4)向用户告知访问限速的信息
具体来说过,请求速率限制,根据api_key或者用户来判断某段时间的请求次数,将该数据更新到内存数据库(redis,memcached),达到最大数即不接受该用户的请求,同时这样还可以利用到内存数据库key在特定时间自动过期的特性。在php中可以使用APC,Alternative PHP Cache (APC) 是一个开放自由的PHP opcode 缓存。它的目标是提供一个自由、 开放,和健全的框架用于缓存和优化PHP的中间代码。在返回时设置X-Rate-Limit-Reset:当前时间段剩余秒数,APC的示例代码如下:
// php
Route::filter('api.limit', function()
{
$key = sprintf('api:%s', Auth::user()->api_key);
// Create the key if it doesn't exist
Cache::add($key, 0, 60);
// Increment by 1
$count = Cache::increment($key);
// Fail if hourly requests exceeded
if ($count > Config::get('api.requests_per_hour'))
{
App::abort(403, 'Hourly request limit exceeded');
}
});
10、错误处理
对于非法的,导致系统出错的等请求都进行记录,一些重要的操作,如登录,注册等都通过日志接口输出展示。有一个统一的出错接口,对于400系列和500系列的错误都有相应的错误码和相关消息提示,如401:未授权;403:已经鉴权,但是没有相应权限。如不识别的url:{“result”:“Invalid URL!”},错误的请求参数{“result”:“json format error”},不允许的方法:{“result”:“Method Not Allowed”},非法参数等。上面所说的都是单状态码,同时还有多状态码,表示部分成功,部分字符非法等。示例如下:
11、重要ID不透明处理
在系统一些敏感功能上,比如/user/1123 可获取id=1123用户的信息,为了防止字典遍历攻击,可对id进行url62或者uuid处理,这样处理的id是唯一的,并且还是字符安全的。
12、其他注意事项
(1)请求数据,对于POST,DELETE方法中的数据都采用json格式,当然不是说rest架构不支持xml,由于xml太不好解析,对于大部分的应用json已经足够,近一些的趋势也是json越来越流行,并且json格式也不会有xml的一些安全问题,如xxe。使用json格式目前能防止扫描器自动扫描。
(2)返回数据统一编码格式,统一返回类型,如Content-Type: application/json; charset=”UTF-8″
(3)在逻辑实现中,json解码之后进行参数验证或者转义操作,第一步json格式验证,第二步具体参数验证基本上能防止大部分的注入问题了。
(4)在传输过程中,采用SSL保证传输安全。
(5)存储安全,重要信息加密存储,如认证信息hash保存。
总之,尽量使用SSL。
13、JWT
JWT 是JSON Web Token简写,用于发送通过签名和认证的东西,服务端可通过解析该值来验证是否有操作权限,是否过期等安全性检查等。