浅谈REST API
说明: 本文部分内容根据其它网络文章编写,如有版权问题请及时通知。
背景
发迹于互联网的REST,在国内国外混得可谓是风生水起,如今又进入电信行业的视野,连TMF都将其作为战略项目Open Digital的一部分。
一种思维方式影响了软件行业的发展。REST软件架构是当今世界上最成功的互联网的超媒体分布式系统。它让人们真正理解我们的网络协议HTTP本来面貌。它正在成为网络服务的主流技术,同时也正在改变互联网的网络软件开发的全新思维方式。
一、 REST API介绍
传统上,软件和网络是两个不同的领域,很少有交集;软件开发主要针对单机环境,网络则主要研究系统之间的通信。
互联网的兴起,使得这两个领域开始融合,即 "互联网软件",比网站、网络游戏、各种非单机版APP等,
这种"互联网软件"采用客户端/服务器(C/S)模式,建立在分布式体系上,通过互联网通信,具有高延时(high latency)、高并发等特点。
那么如何开发在互联网环境中使用的软件呢?
RESTful架构,就是目前非常流行的一种互联网软件架构。
它结构清晰、符合标准、易于理解、扩展方便,所以正得到越来越多网站的采用。
但是,到底什么是RESTful架构,并不是一个容易说清楚的问题。下面,我就谈谈我理解的RESTful架构。
1、起源
REST 这个词,是Roy Thomas Fielding在他2000年的博士论文中提出的。
Fielding是一个非常重要的人,他是HTTP协议(1.0版和1.1版)的主要设计者、Apache服务器软件的作者之一、Apache基金会的第一任主席。
所以,他的这篇论文一经发表,就引起了关注,并且立即对互联网开发产生了深远的影响。
他这样介绍论文的写作目的:
本文研究计算机科学两大前沿----软件和网络----的交叉点。长期以来,软件研究主要关注软件设计的分类、设计方法的演化,很少客观地评估不同的设计选择对系统行为的影响。而相反地,网络研究主要关注系统之间通信行为的细节、如何改进特定通信机制的表现,常常忽视了一个事实,那就是改变应用程序的互动风格比改变互动协议,对整体表现有更大的影响。我这篇文章的写作目的,就是想在符合架构原理的前提下,理解和评估以网络为基础的应用软件的架构设计,得到一个功能强、性能好、适宜通信的架构。
(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. )
2、名称
Fielding将他对互联网软件的架构原则,定名为REST,即Representational State Transfer的缩写。我对这个词组的翻译是"表现层状态转化"。
如果一个架构符合REST原则,就称它为RESTful架构。
要理解RESTful架构,最好的方法就是去理解Representational State
Transfer这个词组到底是什么意思,它的每一个词代表了什么涵义。
如果你把这个名称搞懂了,也就不难体会REST是一种什么样的设计。
3、资源(Resources)
REST的名称"表现层状态转化"中,省略了主语。"表现层"其实指的是"资源"(Resources)的"表现层"。
所谓"资源",用在互联网上就是网络上的一个实体,或者说是网络上的一个具体信息。它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的实在。
用在OSS中”资源”就是eNodeB、小区、电路、基站退服告警、eUtranCell性能统计。
你可以用一个URI(统一资源定位符)指向它,每种资源对应一个特定的URI。
要获取这个资源,访问它的URI就可以,因此URI就成了每一个资源的地址或独一无二的识别符。
所谓"上网"或者“运维”,就是与互联网或OSS中一系列的"资源"互动,调用它的URI。
4、表现层(Representation)
"资源"是一种信息实体,它可以有多种外在表现形式。我们把"资源"具体呈现出来的形式,叫做它的"表现层"(Representation)。
比如,文本可以用txt格式表现,也可以用HTML格式、XML格式、JSON格式表现,甚至可以采用二进制格式;图片可以用JPG格式表现,也可以用PNG格式表现。
URI只代表资源的实体,不代表它的形式。严格地说,有些网址最后的".html"后缀名是不必要的,因为这个后缀名表示格式,属于"表现层"范畴,而URI应该只代表"资源"的位置。它的具体表现形式,应该在HTTP请求的头信息中用Accept和Content-Type字段指定,这两个字段才是对"表现层"的描述。
5、状态转化(State Transfer)
访问一个网站,就代表了客户端和服务器的一个互动过程。在这个过程中,势必涉及到数据和状态的变化。
互联网通信协议HTTP协议,是一个无状态协议。这意味着,所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生"状态转化"(State Transfer)。而这种转化是建立在表现层之上的,所以就是"表现层状态转化"。
客户端用到的手段,只能是HTTP协议。
具体来说,就是HTTP协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。
它们分别对应四种基本操作:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源。
6、综述
综合上面的解释,我们总结一下什么是RESTful架构:
- 每一个URI代表一种资源;
- 客户端和服务器之间,传递这种资源的某种表现层;
- 客户端通过四个HTTP动词(GET、POST、PUT和DELETE方法),对服务器端资源进行操作,实现"表现层状态转化"。
- 通过Representation(客户端)来处理资源(服务器端)。也就是说,客户端不能直接操作服务器端的资源,只能通过对相应的Representation的操作,并发送相应的请求,最后由服务器端来处理资源并返回结果。
- 客户端和服务器端传送的任何一个Message(消息),都应该是自描述的。也就是说处理这个Message所需要的上下文环境都应该包含在这个Message当中。
REST 从资源的角度来观察整个网络,分布在各处的资源由URI确定,而客户端的应用通过URI来获取资源的表征。获得这些表征致使这些应用程序转变了其状态。随着不断获取资源的表征,客户端应用不断地在转变着其状态,所谓表征状态转移(Representational State
Transfer)。
这一观点不是凭空臆造的,而是通过观察当前Web互联网的运作方式而抽象出来的。Roy Fielding 认为:
设计良好的网络应用表现为一系列的网页,这些网页可以看作的虚拟的状态机,用户选择这些链接导致下一网页传输到用户端展现给使用的人,而这正代表了状态的转变。
7、操作——增删改查
需要注意的是,REST是设计风格而不是标准。REST通常基于使用HTTP,URI,和XML以及HTML这些现有的广泛流行的协议和标准。
以eNodeB资源信息和告警信息为例:
HTTP 请求方法在RESTful Web 服务中的典型应用
资源 |
查询 |
修改 |
增加 |
删除 |
GET |
PUT |
POST |
DELETE |
|
单个eNodeB的URI,比如http://net.chinamobile.com/resources/eNodeB/1001 |
获取 名称为1001的eNodeB资源详细信息,格式可以自选一个合适的网络媒体类型(比如:XML、JSON等) |
修改 名称为1001的eNodeB资源信息。 |
创建一个新的名称为1001的eNodeB资源。 |
删除 名称为1001的eNodeB资源数据。 |
一条告警信息的URI,比如http://net.chinamobile.com/alarms/123456789 |
获取 告警标识为123456789的告警信息,格式可以自选一个合适的网络媒体类型(比如:XML、JSON等),例如其它应用系统同步故障管理系统的某条告警或某类告警 |
修改告警标识为123456789的告警状态,例如,用于当故障管理系统中告警的手工清除、确认、派单状态、工单状态、告警关联关系等状态变更时。 |
创建告警标识为123456789的告警,例如,用于故障管理系统向其它系统(如无线网优、业务监控、告警牌)送实时告警。 |
删除告警标识为123456789的告警,例如,用于手工清除故障管理系统的告警。 |
二、 REST API的优点
- 可以利用缓存Cache来提高响应速度
- 通讯本身的无状态性可以让不同的服务器处理一系列请求中的不同请求,提高服务器的扩展性
- 浏览器即可做客户端,简化软件开发的需求
- 相对于其他叠加的HTTP协议之上的机制,REST的软件依赖性更小
- 不需要额外的资源发现机制
- 在软件技术演进中的长期的兼容性更好
重点说一下什么是“无状态”和“扩展性”。
1
在京东上选择了一款华为荣耀6手机,并添加到购物车,然后又选择了一款荣耀手环,也添加到购物车中,最后点击结算按钮,如果是京东网站采用了“无状态”,那么,页面不会呈现出您之前选择的两款产品信息及其价格。很简单,这里的“购物车”实现了“有状态”。
2
但REST API也可以实现有状态,只需在URL里封装购物车信息,或者为购物车创建另一个资源,比如“/carts/1234”。
3
REST API可以不需要与客户端进行会话,通过这些操作(指在URL里封装购物车信息,或者为购物车创建另一个资源,比如“/carts/1234”)后,客户端向服务器发出请求后,哪怕你在服务器上执行卸载平台和操作系统、拆除服务器硬件、重新组装服务器、重新安装操作系统、平台、应用程序备份恢复操作,也不会影响客户端。
4
REST之所以可以提高系统的可扩展性,就是因为它要求所有的操作都是无状态的。由于没有了上下文(Context)的约束,做分布式和集群的时候就更为简单,也可以让系统更为有效的利用缓冲池(Pool)。并且由于服务器端不需要记录客户端的一系列访问,也减少了服务器端的负载。
三、 REST API与其它技术对比
让我们来思考一下:
小明是瓜山村机房的资源管理员,该机房有1座铁塔,2个天面,9根天线。小明现在模拟为一个REST API,而我是使用资源的应用客户端。如果我想用REST来请求当前的机房状态,我仅会问:“State?” 小明会回答:“1座铁塔,2个天面,9根天线”。
这是REST最简单的一个例子。小明使用表征来传输机房状态。表征的句子很简单:“1座铁塔,2个天面,9根天线”。
再往下看,看我如何让小明用REST方式添加2台eNodeB?
按照常理,可以会这样说:小明,请在瓜山村机房添加2台eNodeB。难道这就是REST方式吗?难道就是通过这样的表征来传输状态的吗?不是的!这是一个远程过程调用,过程是给瓜山村机房添加2台eNodeB。
小明很愤怒地响应到:“400,Bad Request”,你到底是什么意思?
所以,让我们重新来一次。我们怎样做到REST方式呢?该怎样重新表征呢?好,让我们再次重新表征……
我:“小明,……1座铁塔,2个天面,9根天线,2台eNodeB!”
小明:“好的”。
我:“小明,现在是什么状态?”
小明:“1座铁塔,2个天面,9根天线,2台eNodeB!”。
我:“好!”
看到了吗?就这样简单。
为什么RPC也不够好?
从逻辑角度来看,为什么会更加青睐REST而不是RPC(Remote Procedure Call,远程过程调用 ),因为它极大的降低了我们沟通的复杂度,通过把表征作为唯一的沟通的方式。无需去讨论过程(添加一台BTS?增加一种传输设备?还是占用所有PTN端口?)我们只需讨论表征,并且使用这个表征来达到我们想要的目标,很简单,不是吗?我不希望和小明的沟通失败,因为我们彼此的理解过程会不一样,所以只需要知道最后的状态就行。但这仅仅是创建RPC会产生许多问题之一。
如果你使用RPC,你需要设计一些程序嵌入到某种结构中。这种结构需要存储参数、错误的代码、返回值等。我已经看到许多公司这样做,他们设计自己的RPC-结构来实现客户端与服务器端的交互,但却产生许多问题。你为什么要这么做?为什么要创建自己的RPC-结构?这样做的好处是?倘若我想要让应用程序使用许多WebService,并且这些WebService带有多个RPC-格式属性?那么我不得不去开发一些类似这样的东西:
如果你们真的需要RPC,至少要选择一个类似SOAP的标准。
但SOAP也很糟糕
即使RPC的标准真的很令人痛苦,但我不得不承认ACID事务,一个完整的标准化服务描述性语言SOAP(Simple Object Access Protocol,简单对象访问协议)在某些环境下表现的还不错。尽管如此,SOAP产品的性能开销很大,它是一个巨大的性能杀手。虽然REST不是一个标准,但在实现RESTful Web服务时可以使用其他各种标准(比如HTTP、URL、XML、PNG等)。
REST是设计风格而不是标准:
REST可以说是一种与DO(分布式对象Distributed
Objects)、RPC(远程过程调用Remote Procedure Call)并列的架构体系,是一种设计分布式网络服务或API时遵循的架构原则以及设计风格。
REST、DO、RPC之间区别对比
REST |
DO |
RPC |
|
核心是资源,按资源建模 |
核心是对象,按对象建模 |
核心是过程,按过程建模 |
|
中立于开发平台和编程语言多种编程语言实现 |
通常与某种编程语言绑定的,跨语言交互实现复杂 |
虽然应用较广泛,但跨语言交互实现复杂 |
|
没有统一接口的概念,不同API接口设计风格不同 |
没有统一接口的概念,不同API接口设计风格不同 |
||
统一接口 |
|||
使用超文本,交互效率比DO更高 |
没有使用超文本,响应的内容中只包含对象本身 |
没有使用超文本,响应的内容中只包含对象本身 |
|
三种风格中客户端与服务器耦合度最小 |
带来客户端与服务器端的紧耦合。在三种架构风格之中DO风格的耦合度最大的 |
使用了平台中立的消息因而耦合度比DO风格要小,但比REST大 |
|
支持数据流和管道 |
不支持数据流和管道 |
不支持数据流和管道 |
REST与CORBA、SNMP、SOAP比较
CORBA |
SNMP |
SOAP |
REST |
|
架构模式 |
面向对象 |
面向方法 |
面向方法 |
面向资源 |
统一的接口约束 |
无(CORBA服务可以任意添加方法) |
无(可以任意添加方法) |
无(可以任意添加方法) |
有(GET/PUT/ POST/DELETE) |
要求无状态 |
否 |
否 |
否 |
是 |
消息体编码格式 (编码效率) |
二进制(高) |
二进制(高) |
XML(低) |
JSON(中) |
编译耦合度 |
高(服务端和客户端联动编译) |
低(解耦) |
低(解耦) |
低(解耦) |
传输协议(效率) |
TCP(高) |
UDP(高) |
HTTP(低) |
HTTP(低) |
在传输协议上封装应用协议 |
是 |
是 |
是 |
否 |
跨语言能力 |
中 |
高 |
高 |
高 |
实现框架 |
重量级 |
轻量级 |
轻量级 |
羽量级 |
是否有归一化的参考实现 |
有 |
有 |
有 |
无 |
原生支持负载均衡 |
否 |
否 |
否 |
是 |
原生支持失效转发 |
否 |
否 |
否 |
是 |
原生支持事件通知 |
是 |
是 |
否 |
否 |
四、 REST API在互联网中的应用
腾讯开放平台REST API导航图:
下面是用户信息类API“获取用户基本资料”的功能说明文档。
1 功能说明
获取登录用户的信息,包括昵称、头像、性别等信息。
本接口是全平台通用的,即发送请求后,可根据请求中传入的“pf”平台参数返回对应平台的用户信息,详见返回字段说明。
例如:如果传入的pf为qzone,则返回的是其QQ空间的昵称和头像。
注意:
1. 本接口返回的各种VIP(例如黄钻等)信息是经过缓存的,有一定的延时。
如果需要VIP信息特别准确的场景(例如黄钻每日礼包场景中,非黄钻用户开通黄钻后,返回应用应该立即可领取礼包),请调用专门的VIP实时信息获取接口。
目前为黄钻提供专门的黄钻实时信息获取接口:v3/user/is_vip,其它VIP实时信息获取接口为:v3/user/total_vip_info。
2. 本接口只返回用户基本个人资料。对于其它更丰富的用户个人资料,出于保护用户隐私的考虑,目前尚不开放。
2 接口调用说明
2.1
URL
http://[域名]/v3/user/get_info
正式环境域名或测试环境IP详见:API3.0文档#请求URL说明。
2.2 格式
json
2.3 HTTP请求方式
GET,
POST
2.4 IP限制
TRUE
2.5 输入参数说明
各个参数请进行URL 编码,编码时请遵守 RFC 1738
(1)公共参数
发送请求时必须传入公共参数,详见公共参数说明。
(2)私有参数
参数名称 |
是否必须 |
类型 |
描述 |
charset |
string |
指定请求及响应的字符集,取值为gbk或utf-8(只有pf=qqgame或pf=3366时,可以输入该参数)。 默认值为utf-8,其他非法取值也认为是utf-8。 |
|
flag |
unsigned int |
pf=qqgame时,必须输入该参数,指定需要获取QQGame中的哪些信息: 1:需要获取游戏昵称和性别; |
2.6 请求示例
http://openapi.tencentyun.com/v3/user/get_info? openid=B624064BA065E01CB73F835017FE96FA& openkey=5F154D7D2751AEDC8527269006F290F70297B7E54667536C& appid=2& sig=VrN%2BTn5J%2Fg4IIo0egUdxq6%2B0otk%3D& pf=qzone& format=json& userip=112.90.139.30
2.7 返回参数说明
参数名称 |
描述 |
ret |
返回码。详见公共返回码说明#OpenAPI V3.0 返回码。 |
msg |
如果错误,返回错误信息。 |
is_lost |
判断是否有数据丢失。如果应用不使用cache,不需要关心此参数。 0或者不返回:没有数据丢失,可以缓存。 |
nickname |
昵称。 |
gender |
性别。 |
country |
国家(当pf=qzone、pengyou或qplus时返回)。 |
province |
省(当pf=qzone、pengyou或qplus时返回)。 |
city |
市(当pf=qzone、pengyou或qplus时返回)。 |
figureurl |
头像URL。详见:前端页面规范#6. 关于用户头像的获取和尺寸说明。 |
openid |
用户QQ号码转化得到的ID(当pf=qplus时返回)。 |
qq_level |
用户QQ等级(当pf=qplus时返回)。 |
qq_vip_level |
用户QQ会员等级(当pf=qplus时返回)。 |
qplus_level |
用户Q+等级(当pf=qplus时返回)。 |
is_yellow_vip |
是否为黄钻用户(0:不是; 1:是)。 (当pf=qzone、pengyou或qplus时返回) |
is_yellow_year_vip |
是否为年费黄钻用户(0:不是; 1:是)。 (当pf=qzone、pengyou或qplus时返回) |
yellow_vip_level |
黄钻等级,目前最高级别为黄钻8级(如果是黄钻用户才返回此参数)。 (当pf=qzone、pengyou或qplus时返回) |
is_yellow_high_vip |
是否为豪华版黄钻用户(0:不是; 1:是)。 (当pf=qzone、pengyou或qplus时返回) |
is_blue_vip |
是否为蓝钻用户(0:不是; 1:是)。 (当pf=qqgame或3366时返回) |
is_blue_year_vip |
是否为年费蓝钻用户(0:不是; 1:是)。 (当pf=qqgame或3366时返回) |
blue_vip_level |
蓝钻等级(如果是蓝钻用户才返回此参数)。 (当pf=qqgame或3366时返回) |
3366_level |
3366用户的大等级。 (当pf=3366时返回) |
3366_level_name |
3366用户的等级名,如小游游、小游仙。 (当pf=3366时返回) |
3366_grow_level |
3366用户的成长等级。 (当pf=3366时返回) |
3366_grow_value |
3366用户的成长值。 (当pf=3366时返回) |
is_super_blue_vip |
是否是豪华蓝钻。 (当pf=qqgame或3366时返回) |
2.8 错误返回码说明
公共错误返回码:公共返回码说明#OpenAPI V3.0 返回码。
本接口私有错误返回码:暂无。
2.9 正确返回示例
JSON示例:
Content-type: text/html; charset=utf-8 { "ret":0, "is_lost":0, "nickname":"Peter", "gender":"男", "country":"中国", "province":"广东", "city":"深圳", "figureurl":"http://imgcache.qq.com/qzone_v4/client/userinfo_icon/1236153759.gif", "is_yellow_vip":1, "is_yellow_year_vip":1, "yellow_vip_level":7, "is_yellow_high_vip": 0 }
2.10 错误返回示例
Content-type: text/html; charset=utf-8 { "ret":1002, "msg":"请先登录" }
五、 REST API的设计
对于开发人员来说,关心的是如何使用REST架构,这里我们来简单谈谈这个问题。REST不仅仅是一种崭新的架构,它带来的更是一种全新的Web开发过程中的思维方式:通过URL来设计系统结构。在REST中,所有的URL都对应着资源,只要URL的设计是良好的,那么其呈现的系统结构也就是良好的。这点和TDD(Test Driven Development)很相似,他是通过测试用例来设计系统的接口,每一个测试用例都表示一系列用户的需求。开发人员不需要一开始就编写功能,而只需要把需要实现的功能通过测试用例的形式表现出来即可。这个和REST中通过URL设计系统结构的方式类似,我们只需要根据需求设计出合理地URL,这些URL不一定非要链接到指定的页面或者完成一些行为,只要它们能够直观的表现出系统的用户接口。根据这些URL,我们就可以方便的设计系统结构。
从REST架构的概念上来看,所有能够被抽象成资源的东西都可以被指定为一个URL,而开发人员所需要做的工作就是如何能把用户需求抽象为资源,以及如何抽象的精确。因为对资源抽象的越为精确,对REST的应用来说就越好。这个和传统MVC开发模式中基于Action的思想差别就非常大。设计良好的URL,不但对于开发人员来说可以更明确的认识系统结构,对使用者来说也方便记忆和识别资源,因为URL足够简单和有意义。按照以往的设计模式,很多URL后面都是一堆参数,对于使用者来说也是很不方便的。
来看下面这个简单的采购方案例子:
可以看到,例子中定义了两个服务程序(没有包含任何实现细节)。这些服务程序的接口都是为了完成任务(正是我们讨论的OrderManagement和CustomerManagement服务)而定制的。如果客户端程序试图使用这些服务,那它必须针对这些特定接口进行编码——不可能在这些接口定义之前,使用客户程序去有目的地和接口协作。这些接口定义了服务程序的应用协议(application protocol)。
在RESTful HTTP方式中,你将通过组成HTTP应用协议的通用接口访问服务程序。你可能会想出像这样的方式:
可以看到,服务程序中的特定操作被映射成为标准的HTTP方法。
六、 REST API的开发
1 REST Web Services框架 JAX-RS
JSR-311(JAX-RS: Java API for RESTful Web Service)是支持RESTful web服务的Java应用程序接口规范,它使用了Java SE 5引入的Java 标注来简化Web服务客户端和服务端的开发和部署。JAVA EE 6引入了对于JSR-311的支持。
以下是几种JSR-311的实现:
- Oracle Jersey—— https://jersey.java.net/
- Jboss的RESTEasy——http://www.jboss.org/resteasy
- Apache的Wink——http://wink.apache.org/
- Spring MVC (Spring 3.0增加了对REST的支持)
2 REST Web Application多层框架
- JERSEY+ EJB3.0
- JERSEY 负责Web Service
- EJB3.0 负责管理bean的生命周期,都是stateless bean
- GUI端选择其他方案或者富客户端方案例如JS
- JERSEY+SPRING MVC
- JERSEY 负责Web Service
- SPRING 负责管理bean的生命周期
- GUI端用SPRING MVC或者其他富客户端方案例如JS
- SPRING MVC
- SPRING MVC负责Web Service
- SPRING 负责管理bean的生命周期
- GUI端用SPRING MVC或者其他富客户端方案例如JS
3 REST 应用场景
- 适合
- 无状态的应用 stateless:判断标准是服务器重启不影响客户端和服务端的交互。
- 缓存机制有利于解决频繁访问的性能问题。
- 客户端和服务端互相了解。如果服务段是封闭的,并且接口描述是固定并记入合同,SOAP (on WSDL)更合适。
- REST WEB SERVICE更适合对于带宽敏感的应用,适合富客户端。
- 对于需要不断开发新的WEB SERVICE并且需要集成到现有的应用里面,REST WEB SERVICE更合适。对于AJAX的应用,REST WEB SERVICE很容易集成。
- 不适合
- 如果软件架构专注于非功能性需求,例如事务,安全性。很多业务超出了简单的CRUD的操作,需要关联上下文以及维护对话状态,这种情况下如果还要用REST,就需要自己额外开发很多辅助功能。
- 异步处理和调用。
浅谈REST API