当HTTP被发明出来的时候,其实REST就已经存在了。可惜这么多年来,WEB开发模式却越来越背离HTTP的本质,舍本逐末的追求起RPC之类的东西。此时REST重新回到人们的视线里,无疑让大家开始反思过去走过的弯路。
本文并不想从头介绍REST,只是想举例说明一下需要注意的问题:
先来看看人们对REST的困惑:
REST什么样子?
最一般的REST例子,类似下面的样子:
2 |
DELETE /articles/123 删除 |
3 |
PUT /articles/123 更新或创建 |
顺便说说几个知识点:
GET操作是安全的。所谓安全是指不管进行多少次操作,资源的状态都不会改变。比如我用GET浏览文章,不管浏览多少次,那篇文章还在那,没有 变化。当然,你可能说每浏览一次文章,文章的浏览数就加一,这不也改变了资源的状态么?这并不矛盾,因为这个改变不是GET操作引起的,而是用户自己设定 的服务端逻辑造成的。
PUT,DELETE操作是幂等的。所谓幂等是指不管进行多少次操作,结果都一样。比如我用PUT修改一篇文章,然后在做同样的操作,每次操作后的结果并没有不同,DELETE也是一样。顺便说一句,因为GET操作是安全的,所以它自然也是幂等的。
POST操作既不是安全的,也不是幂等的,比如常见的POST重复加载问题:当我们多次发出同样的POST请求后,其结果是创建出了若干的资源。
安全和幂等的意义在于:当操作没有达到预期的目标时,我们可以不停的重试,而不会对资源产生副作用。从这个意义上说,POST操作往往是有害的,但很多时候我们还是不得不使用它。
还有一点需要注意的就是,创建操作可以使用POST,也可以使用PUT,区别在于POST是作用在一个集合资源之上的(/articles), 而PUT操作是作用在一个具体资源之上的(/articles/123),再通俗点说,如果URL可以在客户端确定,那么就使用PUT,如果是在服务端确 定,那么就使用POST,比如说很多资源使用数据库自增主键作为标识信息,而创建的资源的标识信息到底是什么只能由服务端提供,这个时候就必须使用 POST。
浏览器不支持PUT/DELETE方法怎么办?
大部分浏览器只支持GET/POST方法,这使得我们无法完美的实现REST。对于这样的情况,大致有几种解决方法,一种是在表单里加入一个 _method之类名字的隐藏字段,用于表示真正的方法,另一种是使用X-HTTP-METHOD-OVERRIDE头信息来重载POST。
HTTP方法够用么?
从上面的例子,我们可以看到,通过使用已有的HTTP方法:POST,DELETE,PUT,GET就可以完成资源的增删改查,但在实际情况 中,我们需要做的操作往往并不仅仅局限在简单的增删改查操作中,比如说我们要把一篇文章“置顶”,但是HTTP方法里没有一个和“置顶”操作相对应的方 法,这时候该怎么办呢?REST对类似问题的解决方案是:创建一个新的资源!在上面的例子里,我们可以这样:
通过创建出一个新的资源(toparticles),我们就可以使用简单的HTTP方法通吃一切操作了。
REST反对使用Session么?
牢记一点,REST拒绝Session!这是因为REST强调无状态性。这里的状态指的的应用状态,也可以称之为会话状态。一旦在服务端保持了这样的状态,那么架构的可扩展性将大打折扣。在REST看来,任何类似的状态本身都应该是一个独立的资源。
Cookie对REST有害么?
一分为二的看,如果Cookie里保存的是应用状态的话,就没有问题。因为应用状态本来就属于客户端。但如果使用Cookie保存类似PHPSESSIONID之类的东西就不对了,因为这样的数据并不属于客户端状态,它只不过是使用Session的借口而已。
再来看看REST在PHP中的现状:
PHP里的REST实现案例不多,有点影响都就是CakePHP和Zend,下面分别看看他们的实现:
CakePHP:
设定路由:
1 |
Router::parseExtensions( 'xml' ); |
2 |
Router::mapResources( 'articles' ); |
编写控制器:
1 |
class ArticlesController extends AppController { |
2 |
var $components = array ( 'RequestHandler' ); |
3 |
function view( $id = null) { |
4 |
$article = $this ->Article->findById( $id ); |
5 |
$this ->set(compact( 'article' )); |
视图:
2 |
<?php echo $xml ->serialize( $article ); ?> |
差不多就这样了,相应的,还可以实现其他的功能,于是,如下REST操作便成为可能:
2 |
DELETE /articles/123.xml |
总体看,CakePHP的REST实现基本上是按Rails风格来实现的,大体还过得去。
ZendFramework:
ZendFramework通过Zend_Rest组件来实现Rest功能:
服务端:
1 |
require_once 'Zend/Rest/Server.php' ; |
2 |
function sayHello( $who , $when ) |
4 |
return "Hello $who, Good $when" ; |
6 |
$server = new Zend_Rest_Server(); |
7 |
$server ->addFunction( 'sayHello' ); |
客户端:
1 |
require_once 'Zend/Rest/Client.php' ; |
3 |
$client ->sayHello( 'Davey' , 'Day' ); |
这时候,我们看一下Web服务器的日志,会发现生成了一条如下的记录:
1 |
GET /path/to/servier/script?method=sayHello&arg0=Davey&arg1=Day&rest=1 HTTP/1.1 |
我们发现,实际操作方法是由URL中的method=sayHello指定的,而HTTP固有方法(GET/POST等)则成为了摆设,这是典 型的RPC风格,如果大家对比Zend_Rest和Zend_XmlRpc文档的话,会明显发现它们根本就是一个东西,所以说,Zend_Rest是一个 REST伪实现。
这个基本 REST 设计原则建立了创建、读取、更新和删除(create, read, update, and delete,CRUD)操作与 HTTP 方法之间的一对一映射。 根据此映射:
1)若要在服务器上创建资源,应该使用 POST 方法。
2)若要检索某个资源,应该使用 GET 方法。
3)若要更改资源状态或对其进行更新,应该使用 PUT 方法。
4)若要删除某个资源,应该使用 DELETE 方法。