Rails 在 1.2 版中坚决地引入了 REST 风格的资源,在这个 REST 风格资源的世界中,XML 理所当然地成为了通用标准。不过这并没有不允许其它标准的存在,而多亏 Rails 的灵活性,REST 风格的应用能轻而易举地支持 XML 以外的标准,还能使这些应用面向更多的用户以及(或者)减少它们对带宽的需求。
建立 Controller
我打算将目光聚焦于一个应用,该应用仅处理事件这一个资源。应用将融合不同的标准来提供标准的一系列 CRUD 操作。开始前首先要创建一个新的 Rails 应用:
% Rails lingua<br></br> % cd lingua
随后用 scafford_resource 生成器来创建一个事件资源:
% script/generate scaffold_resource Event
打开并查看 app/controllers/events_controller.rb 文件,你将看到 respond_to 被大量用于为每个 action 添加 XML 输出,例如‘show’这个 action:
def show<br></br> @event = Event.find(params[:id])<p> respond_to do |format|</p><br></br> format.html # show.rhtml<br></br> format.xml { render :xml => @event.to_xml }<br></br> end<br></br> end
(本文中我们不打算介绍授权和认证。但我强烈建议将 restful_authentication 插件作为学习授权和认证的入门)。
JSON 简介
JSON 是最近才走入人们视线的一个标准,这还要多亏 JavaScript 作为 UI 开发语言的成熟应用以及 AJAX 的迅猛发展。以序列化的 JavaScript 对象为基础的 JSON 获得了广泛认可,它被认为能以远比 XML 更好的方式来序列化和传输简单数据结构,而且它更简洁。
因为 ActiveRecord 早已能将它的记录以 JSON 格式持久化了,所以让 Rails 输出 JSON 格式显得易如反掌。如果我们想要的不过是 JSON 格式的输出,那么我们只需要对 action 的代码做如下修改:
def show<br></br> @event = Event.find(params[:id])<p> respond_to do |format|</p><br></br> format.html # show.rhtml<br></br> format.xml { render :xml => @event.to_xml }<br></br> format.json { render :text => @event.to_json }<br></br> end<br></br> end
那么现在对 /events/1.json 的 GET 请求将获得以 JSON 格式返回的事件。
可若要实现真正的多种标准并存,我们还要理解(解析)JSON 而不是简单地介绍它。我们有许多方式可以做到这一点。首先我们需要能在 Ruby 中解析 JSON。
归根到底,合法的 JSON 格式也是合法的 YAML 格式。因此由于早期 Rails 对 YAML 的支持,最简单的解析器莫过于已经作为符号存在于 Rails 中的 YAML 解析器:
ActionController::Base.param_parsers[Mime::YAML] = :yaml
不过 JSON 允许一些额外的结构(特别是注释),它们可能会被一些不那么小心的 JSON 编写者误输入了。那些现成的 JSON 解析器破坏了 Rails 的 JSON 生成过程,因为在提供解析和生成 JSON 方法的同时,它们重定义了 to_json 方法。我的解决方案是仅提取解析的功能,并将代码以 json.rb 文件的形式存储在我的 lib 文件夹下。代码太长所以不适合粘贴在这里,不过你可以在以下地址找到它:
http://jystewart.net/code/json/json.txt
一旦我们有了解析 JSON 的方式,我们接下来就需要将方法应用到 Rails 中。最直观的应用就是为我们的 controller 添加 before_filter,用它来截取 JSON 格式并进行解析,再将解析结果作为标准的参数 hash 提交给 action。我们可以这样去做:
before_filter :intercept_json, :if => Proc.new { |p| p.content_type == Mime::JSON }<p> def intercept_json</p><br></br> params[:event] = JSON::parse(params[:event])<br></br> end
但如果我们打算添加其它资源,这就显得不那么 DRY 了(译者注:DRY——Don’t repeat yourself,不要重复你自己)。而如果我们添加了对更多格式的支持,我们将需要面对每个 controller 中的大量代码。Rails 将 XML 格式的输入透明地提交给 controller,而它或许也能够这样处理 JSON。谢天谢地,这的确可以!
Rails1.2 还引入了可插入的 param_parsers,它使我们能够定义如何处理每种 MIME 类型。事实上,XML 解析就是这样定义,代码如下:
ActionController::Base.param_parsers[Mime::XML] = :xml_simple
标准的 Rails 参数解析过程(由 CGI 实现)捕获了解析过程中的任何异常,因此特定的解析器能够被用于任何 mime 类型。我们将自己的解析器添加到 environment.rb 中。要添加 JSON 解析器,我们需要做的是:
ActionController::Base.param_parsers[Mime::JSON] = Proc.new do |data|<br></br> JSON::parse(post)<br></br> end
那么现在我们的参数 hash 将成为不可序列化的 JSON 请求。
这就是这个应用所做的一切。只要我们真的按照步骤构建了数据库(译者注:由于我们现在没有构建数据库,本文的示例还无法正常运行),我们的事件 controller 现在就能呈现为一个 REST 风格的资源,该资源既能以 XML 格式也能以 JSON 格式读写。而我们选择 JSON 可能是因为它简单到仅需用到 JavaScript,我们能够非常容易地将行为层从应用逻辑中分离出来,并使用 JSON 来衔接二者,又或者因为大部分主流编程语言都提供轻量级的 JSON 库,JSON 可以被作为一种低带宽占用的方式来接收 REST 风格 API 的输入。
Microformats 简介
Microformats 与 JSON 相似,它最近才越来越受人关注,但这些关注来源于一个与 JSON 迥然不同的角度。在 Web 开发的世界中,我们基于标准来使用 HTML 便能获得很好地支持,使 HTML 具有语义(在结构上使用 ID 和类名称)的目标似乎触手可及。简单地将右侧导航栏作为页面右侧最大的区域,或者甚至将它的 ID 标注为“右边栏”的做法已经过时了,现在我们可以在 Microformats 中将它标识为“二级导航”,然后开始关注内容而非呈现。
Microformat 尝试将常用元素的语义标准化,随之衍生了一系列新名词,如针对事件的 hCalendar(取材于 iCalendar 标准),针对个人和商务信息的 hCard(取材于 vcard),针对新闻的存储格式 hAtom(取材于 atom 联合格式)等。
添加 Microformat 输出甚至比生成 JSON 格式更简单,我们只需要确保将 class 名称约定应用到我们的.rhtml 视图文件中。解析它们则需要花一些脑筋,因为(以 HTML 的形式存在的)Microformats 没有一个特定的、被接受的 mime 类型来将它们与标准的请求(译者注:这里主要指 http 请求)区分开来。我待会继续讨论这个话题,不过首先我们来看看如果我们已经标识了 Microformats,应该如何来解析它的输入。
Ruby 开发者幸运地拥有了几个最灵活的 Microformat 解析库之一——Chris Wanstrath 编写的 Mofo。Mofo 基于 why 编写的 hpricot 库(译者注:hpricot 是 Ruby 编写的 HTML 解析库),它提供了描述 Microformats 的 DSL。它分发了类,这些类提供了大部分通用 Microformats 的定义。因此要将 HTML 字符串中的 hCalendar 事件解析并展示出来,我们需要做的是:
require 'mofo' data = <our data="" html=""></our> events = HCalendar.find(:all, :text => data)
我们用下面的方式来读取所有 Microformats Mofo 了解的内容:
require 'mofo'<br></br> data = <our data="" html=""></our><br></br> parsed_data = Microformat.find(:all, :text => data)<br></br>
随后 Mofo 将以易于访问的对象形式来返回所有 Microformat 数据。
Hpricot 和 Mofo 都是非常有用的库,不过仍旧无法依赖它们来解析我们所有的输入,特别是如果我们要考虑标准的 web 接口时。使用 URI 概要(作为 Web 接口)标识 Microformats 时会有一些不同,但对它们的支持离通用和稳定的目标还很远。但愿随着 Microformats 的成熟,有更多处理这类情况的规范出现。
就现在而言,最简单的应用莫过于尝试用标准的 CGI 解析器来解析输入,然后回去检查下 POST 请求的 body,看看它的内容是否是一个单独的字符串或独立的一些参数。我用到了 microformat_interceptor 这个通用的 Proc 对象(译者注:Proc 是 Ruby 中的过程对象):
microformat_interceptor = Proc.new do |data|<br></br> parsed = CGI.parse(data)<p> if parsed.collect { |key, value| key + '=' + value[0] }.first == data</p><br></br> Microformat.find(:all, :text => data)<br></br> else<br></br> CGIMethods.parse_request_parameters(parsed)<br></br> end<br></br> end
随后为它分配 text/html 和 application/x-www-form-urlencoded 两种 mime 类型:
Mime::FORM = Mime::Type.lookup("application/x-www-form-urlencoded")<p> ActionController::Base.param_parsers[Mime::FORM] = microformat_interceptor</p><br></br> ActionController::Base.param_parsers[Mime::HTML] = microformat_interceptor
因为我们的 action 需要一个单独的事件作为它的输入,我们还需要从它生成的数组中抽取第一个事件,随后我们将需要修改事件的 model,使之能在 contructor 方法中接收 HCalendar,但考虑到我们 model 的字段与 microformat 的属性名是共享的,修改 model 就不是什么大问题了。
那么假定我们已经支持了标准的 POST 请求、XML 和 JSON,为什么我们还要在 Rails 应用中支持 Microformats 呢?站在前沿的角度看,这个问题没必要伤脑筋。如果我们以标准的格式来发布我们的信息,这个格式对人、Web 浏览器和其它解析器有意义。我们肯定能够在搜索引擎中获得更高的评价,使我们的数据被纳入搜索引擎的索引中,从而让这些信息在互联网上广泛传播。
而它对数据接收也非常有意义,因为它发掘了我们的诉求。对你的 HTML 专家而言,学习 Microformats 不过是时间问题。相比之下,对付 JSON 或一个新的 XML schema 则是更复杂的任务。Microformats 在某个系统中生成的页面能够被作为你的 Web 服务的输入。一旦存在 Web 页面或系统间沟通的需要,数据就会被创建,而随着越来越多的发布工具开始支持 Microformats,使用你服务的用户将越来越多。
查看英文原文: Versatile RESTful APIs Beyond XML - - - - - -
作者简介:James Stewart 是一个醉心于 Rails 的 Web 开发者。他现在生活在美国,不过他正在迁往英国的过程中。他的博客主要关注 web 开发领域,地址为 http://jystewart.net/process/ 。 译者简介:魏泉,具有多年企业级开发经验,曾担任过博文视点出版公司的技术编辑,是《Spring 技术手册》和《Spring 专业开发指南》的责任编辑。武汉大学 Google Camp 的创建者之一,关注 Web 发展的最新趋势。
评论