RESTful API 背后的哲学思想

来源:互联网 发布:网络金融产品都有哪些 编辑:程序博客网 时间:2024/06/06 05:31

RESTful API 背后的哲学思想

作者 许野平 2017-08-12 济南

去年参加 IBM 产品培训,第一次听说 REST API 这个概念。REST API的全称是RESTful API,原以为是 IBM 专有技术,咨询培训讲师后才了解到,这是最近越来越火的一种远程服务调用策略(RPC)。后来读了几本书,了解了一下,发现这种方法极简的形式背后蕴藏着深刻的哲学思想,解决了困惑我多年的心病。听我慢慢道来,相信您也一定能被其打动。

1. 通讯悖论

通讯可靠性自古以来就是无解的难题。有一个通讯悖论故事,说是红军A和C中间驻扎着蓝军B,其中,红军A或者C其中任何一方单独进攻蓝军B,都会失败。红军A和C必须同时进攻蓝军B,才能取得胜利。

红军A决定派人向C送信,约定时间进攻蓝军B。可是,A无法确定C是否收到了信件,于是A要求通信员送到信件后要带回C签收的回执,确保C的确收到了信件。

但是,红军C签发回执后,也很担心A能不能收到回执,如果回执没有安全送达,C也就不可能擅自行动。于是,C也需要A收到回执后,再签发一个回执的回执。可以想象,如此进行下去,这个通讯过程就变成了无穷循环。

这个通讯悖论说明,在两点之间建立完全可靠的通讯渠道是不可能的。

2. C/S 模式破解通讯悖论

很早就有人分析了上面这个通讯悖论。通讯悖论的根本原因是点对点对称方式通讯。只要我们放弃点对点对称的通讯方式,不再确保双方同时都取得可靠的结果,就可以破解这个悖论。

C/S模式,英文全称是Client/Server(客户/服务器),把通讯双方一方定位成客户,另一方定位成服务器。其实意思很简单,客户Client就是上帝,服务器Server就是仆人,通讯过程必须确保客户端能够获得确定性结果,以便客户端准确决定后续操作。

有人说了,既然客户就是上帝,服务器就是仆人,为什么我看到的客户端设备都是些不值钱的手机、平板等,服务器端设备都是些价值不菲、高大上的设备呢?

答案很简单哦,你什么时候见过美国总统手里拿着长枪短炮?美国的空军一号、总统专车、随身行李等等不都是随从们的任务吗?服务器配置高大上,也是为了确保客户端有好的体验更好而已。

当然,在我们的应用中,服务器不是只围绕一个客户转,它要服务成千上万的客户。Server要扮演好仆人这个角色,它必须遵循一定的原则才行。下面介绍两个重要准则:无状态准则、幂等性准则。

3. Server: 无状态服务准则

方清平的相声里说,鱼儿的记忆时间只有几秒。两条小鱼儿见面第一句话总是“初次见面请多多关照”。

其实在C/S模式下,服务器根本没有任何记忆,客户和服务器之间永远都是“单轮对话”。

服务器永远不会记得客户的任何信息,永远也不记得刚才给客户提供了什么服务。工作模式永远是“接受客户请求,答复客户问题”,然后彻底忘记刚才的事情。我们都喜欢逛大型超市,其实服务器的角色非常像超市里的收银员,只负责收款、签章。那怕你一晚上在收银台付过一百次款,收银员也没必要记住你是谁、你买过什么东西。收银员总是在签章之后忘记当前业务,等待下一个业务到来。

另外,既然服务器不记得客户端任何信息,这意味着服务器永远不需要向客户端主动发出任何信息。这就像收银员不会追着顾客收款一样。

聊到这里,我感到非常痛惜。在平常的工作中,我看到太多违反“无状态服务准则“了,很多应用系统构建起来随心所欲,系统可靠性压根不去考虑。

最后需要提醒一下,有人认为服务器端的数据库就是一个庞大的有限状态机,客户端的操作请求一定会改变数据库内容,服务程序怎么可能是无状态的呢?

道理其实很简单,数据库并非服务程序组成部分,数据库和客户端程序一样,都是服务程序的协作组件。服务程序只是把客户端请求通过业务逻辑把相关操作转发数据库,一旦完成客户端请求,服务程序立即就忘掉客户端和数据库的相关信息。

我们看到,服务器完成客户端请求,把应答数据发回给客户端。那么,客户端是否收到了应答数据?服务器如何判断客户端是否收到了应答数据?服务器需要知道客户端是否接受到应答信息吗?现在战狼很火,中午我先去看个电影,回来接着聊。

4. Server: 幂等性服务准则

看完战狼2,心情久久不能平静(怎么感觉像写作文?),感觉这辈子患得患失,慢慢就这么在温水中走向死亡,人生毫无意义。难道人间走一遭就是在这里坐而论道谈人生哲学的?虚度人生是最大的浪费,人生的坛坛罐罐就是应该痛痛快快砸掉,风风火火闯荡一番。哎,写完这篇短文再企划人生吧,咱们继续往下扯。

客户端发出请求,服务器端处理完返回应答。那么客户端如果没有接受到应答信号怎么办?答案很简单——重发。重发还收不到回应怎么办?继续重发。反复若干次还收不到怎么办?检查系统吧,要么网络断了,要么服务器崩了。系统恢复了,再重发,直到收到应答信号为止。

但是这种工作模式容易带来一个问题。如果A向C发送的命令是“将阵地向前推进1千米”,重发了10次才收到应答,那么C实际上会向前推进多少千米呢?很显然,最少推进1千米,但也有可能最多向前推进10千米。

造成这种问题的原因是,“将阵地向前推进1千米”这条命令,执行1次和执行多次,效果是不一样的。客户端向服务器端重发请求,利用这种方法提高通信的可靠性,所发的命令必须满足这样一个条件:命令执行一次和执行多次,效果是完全一样的。就像下面这种数学运算一样:

S×0=S×0nS×1=S×1n

数学中有一个概念叫做幂等律,指的是一个数学运算满足以下关系:

AA=A

例如,1×1=1, 0+0=0, truetrue=true 等等。这里借用了这一数学概念,强调服务器端API设计,应该满足幂等律。这样,在遇到通讯故障时,可以安全地执行重发操作。

如果服务器提供的服务执行一次和执行多次的效果一样,我们说该服务是幂等的,这就是幂等性服务原则。幂等性服务准则的优势是,允许客户端反复重发同一条操作,服务器端不会产生错误的结果。这样客户端就可以通过重发操作,在通讯出现故障时恢复未完成的操作,极大提升了通讯过程的可靠性。

OK,到这里,我们看到了一个安全、可靠的通讯模型:利用C/S策略,无状态服务准则和幂等性服务准则开发服务程序,可以建立安全、可靠的通讯系统。那么,是否已经有人实现了这样一个通讯模型?

5. HTTP协议

上面的C/S模型,似乎就是与生俱来和HTTP黏在一起的。HTTP提供了针对Web资源的四种命令(还有其他辅助命令,本文忽略不计了):

  • POST:创建资源
  • DELETE:删除资源
  • GET:读取资源
  • PUT:修改资源

为什么是这四种操作?有人说删插读写是最基本的操作,这样说当然有道理,更深入一点探讨,会发现它们是按照幂等性准则设计的:

POSTn=POSTDELETEn=DELETEGETn=GETPUTn=PUT

HTTP长久不衰,有商业原因,也有其哲学渊源。

6. RESTful API

先看一段正式的介绍,我百度的结果如下:

REST(英文:Representational State Transfer,简称REST)描述了一个架构样式的网络系统,比如 web 应用程序。它首次出现在 2000 年 Roy Fielding 的博士论文中,他是 HTTP 规范的主要编写者之一。在目前主流的三种Web服务交互方案中,REST相比于SOAP(Simple Object Access protocol,简单对象访问协议)以及XML-RPC更加简单明了,无论是对URL的处理还是对Payload的编码,REST都倾向于用更加简单轻量的方法设计和实现。值得注意的是REST并没有一个明确的标准,而更像是一种设计的风格。

看得一头雾水,对不对?没关系,听我来慢慢解释。

简单来讲,状态就是系统中的数据。我们开发的服务器程序,大部分都有数据库支持,这个数据库中一般都会保存海量数据,我们也可以说数据库中包含了海量的状态。

我们编写的程序代码,主要任务就是修改这些数据,用专业的说法,就是修改系统状态。修改系统状态这件事,我们就称之为状态转移——State Transfer,也就是从一种状态变成了另外一个状态。

作为服务器API,我们主要关心如何向客户端提供访问接口,不关心服务器的内部实现。换句话讲,我们关心客户端能看得见的数据资源,对于服务器内部运转的数据,我们并不关心。用专业的话来讲,我们关心服务器端看得见的状态,也就是Representational State。

服务器对外开放的接口,本质上应该是对于“看得见的状态转移”的描述,也就是Representational State Transfer——REST,翻译成中文就是“表述性状态转移”。这就是 RESTful API 这个概念的来源,实际上它要解决两个问题:(1)确定“状态”;(2)确定如何管理“状态”的生命周期。

Roy Fielding 认为,HTTP的四大操作,恰如其分地解决了这两个问题。基于HTTP协议的Web服务开发技术已经非常成熟,利用这些技术,可以轻而易举地实现RESTful API,而不需要额外的开发新工具。

7. 用 HTTP 实现 RESTful API

这里提供一个实际例子,展示如何设计RESTful API。

最近我们在开发一个服务器端文件管理系统,配置如下:

  1. 服务器操作系统: centOS
  2. Web服务:tomcat,IP地址:192.168.1.1,端口号:80

用 Java 创建了一个名为 RobaseServer 的工程,其中创建了名为 File、Indetification、Password 三个 Serverlet 类,分别代表三种资源(实际资源数量要多一些,这里为了便于理解仅列举三种资源)。于是,这个系统的RESTful API定义如下:

//资源1:身份认证http://192.168.1.1/RobaseServer/Indentification?userid=xxxxPOST,DELETE//资源2:文件访问http://192.168.1.1/RobaseServer/File?fileid=xxxxPOST,DELETE,GET,PUT//资源3:修改密码http://192.168.1.1/RobaseServer/PasswordGET,PUT

资源2、3,都很容易理解,用URL表示资源,其中用?号后面的参数进一步表示定位资源。

有一种说法,说是尽量不用?号后面带参数的方式定位资源,最好都用/号。其实这个和使用的开发工具有关系,如果用HttpServlet开发,肯定要用?号,用Spring等其他框架,可以不用?号。

在POST和PUT的时候,描述资源的数据保存在HTTP协议的body部分,建议采用json格式。关于json格式,这里不展开讨论。

关于资源1的处理方法,下面接着讨论。

通过资源 http://192.168.1.1/RobaseServer/File?fileid=xxxx 读取文件内容,如何验证客户端用户权限?

我们知道,HTTP协议是无状态的协议,不会记录客户端任何信息。因此,当我们用http://192.168.1.1/RobaseServer/File?fileid=xxxx 读取文件内容时,服务器是无法判断客户端用户权限的。于是,服务器需要通过某种方法获取客户端用户信息,于是有两种方式可以实现:

第一种方式,由客户端保存用户信息,cookie就是用来完成这件事的。cookie提供了这样一种机制,允许服务器在客户端硬盘上保存信息,等你下一次再访问服务器的时候,HTTP协议会把cookie内容悄悄送给服务器。这个听起来很可怕,服务器可以偷偷在你硬盘上保存数据,所以这个问题曾经争论过很久,最后决定对其设定了很多限制。这里不重点讨论这个内容,我们只需要知道,HTTP协议允许服务器在客户端硬盘上偷偷写数据,并且在客户端再次访问服务器的时候,还能悄悄获取这些数据。

当http://192.168.1.1/RobaseServer/Indentification?userid=xxxx执行POST操作时, 会根据HTTP协议body中的password验证用户身份,验证通过后,把用户名和密码写入cookie。这样,在以后进行其他操作时,服务器就可以看到request中cookie的内容里面的用户信息。

第二种方式,由服务器保存客户端状态,session是用来做这件事情的。一般来讲,不主张服务器端保存客户端信息,而且即使采用session技术,也需要客户端采用cookie或类似技术来保存客户端id才能正确识别session。

所以我觉得cookie技术更好地诠释了无状态服务的理念。当然,客户端如果禁用cookie,那就玩完了。

9. 面向对象 vs 面向资源

假设我们设计一个游戏,要控制一个机器人前后左右移动,于是服务器端采用面向对象的方法提供了如下接口:

class robat{    int x,y;    robat(){x=0; y=0;}    ~robat();    void moveForward() {++y;}    void moveBack()    {--y;}    void moveLeft()    {--x;}    void moveRight()   {++x;}}

面向对象的方法要解决两个问题,一个是系统有哪些类,另一个是每一个类需要实现哪些方法。

如果采用RESTful API策略,只需要确定系统有哪些资源即可。例如本例,假设机器人位置资源为 http:\192.168.1.1\Robat\Position,则对应的类的伪代码为:

class Position: HttpServlet {    void doPost(){x=0;y=0;}    void doDelete(){}    void doPut{(x,y)=request.body.json("position");}    void doGet{respone.body.json("position")=(x,y);}}

可以看出来,任凭需求千变万化,REST利用四个操作都可以应付,所谓万变不离其宗。这样在分析、设计应用系统时,只需要找出系统资源,至于方法,就这四个。这大大简化了系统设计的复杂度。

面向对象的模型很容易违反幂等性服务准则,上面那个面向对象的例子很显然就是这样,当客户端执行重发操作时,服务器就有可能出现多余的操作。

10. RESTful API的主要优点

RESTful API,安全可靠地实现了C/S通讯,兑现了无状态、幂等性的准则,同时也简化了服务器端的系统分析和系统设计的工作量,难怪越来越受到程序员的喜爱。

11. “假”的 RESTful API

当然,也有很多“假”的RESTful API实现。比如,很多系统借助HTTP协议传送命令,其实服务器端还是面向对象的老思路。例如可能会通过HTTP协议向服务器发送这样一条命令

http://192.168.1.1/Robat?action="moveleft"

这是披着REST外衣的面向对象方法,而且违反了幂等性服务准则。

12. 走向微服务

RESTful API 的出现,成全了另外一个重要技术——微服务。微服务,借助REST技术和Docker容器技术,可以把 RESTful API 服务程序转化成迷你尺度的“虚拟机”,部署的时候只需要把一个个“虚拟机”镜像复制到服务器即可,大大简化系统部署。本文不展开讨论这个内容,有兴趣大家自己继续研究。

原创粉丝点击