如何从 Flex3.2 调用 Rails2.3.2 的 Restful API?
所谓 Restful API, 简单的说就是利用已有的四个 HTTP 动作实现对资源的四种操作:
- GET 获取
- POST 创建
- PUT 更新
- DELETE 删除
在这里对 REST 不作更详尽的解释, 相关资料网上有很多, 争议更多. 我们暂且不管这些争论(包括作者自己也在争论), 单从纯技术角度来看, 这种方式对于我们常见的 CRUD 操作实在是再方便不过了.
但是且慢, 如果你拥有一个独立的客户端, 发送这四种动作是不成问题的, 可是如果你是做 RIA 开发(Flex 或 Silverlight), 目前的浏览器宿主只能支持 GET 和 POST. 后两种动作怎么办?
注: 虽然 Flex 里的 HTTPService 组件有8种动作, 但并不起作用.
也许你已经见过很多动态网站系统采用的解决办法了, 那就是加 URL 参数.
http://www.example.com/admin.asp?action=newpost&title=abc
这样做的好处是通过直观的参数与服务器进行对话.
当然坏处也不少, 按照 HTTP 协议, GET 动作是不能对服务器有副作用的, 因为 GET 只负责读取, 另外这样的 URL 如果被搜索引擎收录了, 或者被用户加入收藏夹, 虽然不是什么致命的问题, 但终究还是对 HTTP 协议的一种误读.
OK~, 既然浏览器不支持, 提倡 Restful 的框架又普遍不满意这种 URL 参数的工作方式, 那 rails 是如何解决这个问题的呢?
Rails 也没有能力解决浏览器的问题, 她同样需要使用额外的方法来模拟 PUT 和 DELETE 动作, 查看产生的源代码, 你会发现她采用的方法是读取 POST 表单里的 hidden 域 _method
1 | <input name="_method" type="hidden" value="put" /> |
采用这种办法对于网页的表单来说或许是合适的, 但对于像我这样使用 Flex 开发 RIA 客户端程序的人来说, 会存在下面的问题:
我读取的文档可能是这样的
1 | <user> |
那么我为什么要转换成类似下面的表单来提交?
user[name]=路飞&user[age]=20
如果可以读取也是 xml, 提交亦是 xml, 生活就简单的多了, 不是么?
提交 xml 就意味着 _method 必须放在别的地方(xml 是不能有两个根元素的), rails 2.2.2 之前的版本(具体到哪一版并不清楚), 服务器是接受 GET 参数的, 也就是说可以写成这样:
/users/1?_method=PUT
_method 虽然有了安身之处, 但是且慢! 这样安全吗? 正规吗? 我想 rails 的开发者也对这个东西看不顺眼, 所以在 rails 2.3.2 里, 这种方法被禁止了.
详见:
lib/action_controller/vendor/rack-1.0/rack/methodoverride.rb
新版本只在 POST 表单里读取 _method, My God…
幸运的是, methodoverride.rb 这个文件的内容为我指明了另一个方向:
1 | def call(env) |
这个文件告诉我们, 服务器除了接受 _method 隐藏域之外, 还接受一个叫 “HTTP_X_HTTP_METHOD_OVERRIDE” 的消息头, 太棒了, 让我们迎着光明继续前进~
为了能够看到提交的消息头, 我们需要在 application_controller.rb 里添加点代码. (Rails 2.2.2 之前该文件名为 application.rb)
1 | class ApplicationController < ActionController::Base |
现在回到 Flex 我们创建一个客户端试验一下效果.
1 | var svc:HTTPService = new HTTPService(); |
结果有点长, 删减之后你会发现类似下面这样的内容:
Processing UsersController#create to xml (for 127.0.0.1 at 2009-03-22 11:02:04) [POST]
HTTP_HTTP_X_HTTP_METHOD_OVERRIDE PUT
呃… 为什么会多出个 HTTP_ 前缀来呢? 这个问题留给你来解答吧, 我也实在是说不清楚.
不过没关系, 问题已经明朗了, 重新设计一下我们的消息头就可以了.
1 | // 首先创建服务代理, 这里只是个例子, 实例上你可能更喜欢用 Mxml. |
结果:
Processing UsersController#update to xml (for 127.0.0.1 at 2009-03-22 11:05:13) [PUT]
Parameters: {"id"=>"1"}
.......
HTTP_X_HTTP_METHOD_OVERRIDE PUT
.......
User Load (0.0ms) SELECT * FROM "users" WHERE ("users"."id" = 1)
User Update (2.0ms) UPDATE "users" SET "age" = 30, "updated_at" = '2009-03-22 03:05:13' WHERE "id" = 1
Completed in 164ms (View: 4, DB: 2) | 200 OK [http://192.168.0.1/users/1]
OK~ 看来 Rails 已经收到我们的信号, 并且正确识别了我们想要的动作 PUT. 其他类似的动作可以由我们自由发挥了, 再也不用受 _method 的牵拌.
嗯, 如果你也是一个像我一样的在技术领域无聊的完美主义者, 但愿这个结果能够令你满意.