BT

如何利用碎片时间提升技术认知与能力? 点击获取答案

超越XML格式的多样化REST风格API

| 作者 James Stewart 关注 0 他的粉丝 ,译者 魏泉 关注 0 他的粉丝 发布于 2007年4月27日. 估计阅读时间: 15 分钟 | QCon上海2018 关注大数据平台技术选型、搭建、系统迁移和优化的经验。

Rails在1.2版中坚决地引入了REST风格的资源,在这个REST风格资源的世界中,XML理所当然地成为了通用标准。不过这并没有不允许其它标准的存在,而多亏Rails的灵活性,REST风格的应用能轻而易举地支持XML以外的标准,还能使这些应用面向更多的用户以及(或者)减少它们对带宽的需求。

建立Controller

我打算将目光聚焦于一个应用,该应用仅处理事件这一个资源。应用将融合不同的标准来提供标准的一系列CRUD操作。开始前首先要创建一个新的Rails应用:

% Rails lingua
% cd lingua

随后用scafford_resource生成器来创建一个事件资源:

% script/generate scaffold_resource Event

打开并查看app/controllers/events_controller.rb文件,你将看到respond_to被大量用于为每个action添加XML输出,例如‘show’这个action:

def show
@event = Event.find(params[:id])

respond_to do |format|
format.html # show.rhtml
format.xml { render :xml => @event.to_xml }
end
end

(本文中我们不打算介绍授权和认证。但我强烈建议将restful_authentication插件作为学习授权和认证的入门)。

JSON简介

JSON是最近才走入人们视线的一个标准,这还要多亏JavaScript作为UI开发语言的成熟应用以及AJAX的迅猛发展。以序列化的JavaScript对象为基础的JSON获得了广泛认可,它被认为能以远比XML更好的方式来序列化和传输简单数据结构,而且它更简洁。

因为ActiveRecord早已能将它的记录以JSON格式持久化了,所以让Rails输出JSON格式显得易如反掌。如果我们想要的不过是JSON格式的输出,那么我们只需要对action的代码做如下修改:

def show
@event = Event.find(params[:id])

respond_to do |format|
format.html # show.rhtml
format.xml { render :xml => @event.to_xml }
format.json { render :text => @event.to_json }
end
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 }

def intercept_json
params[:event] = JSON::parse(params[:event])
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|
JSON::parse(post)
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 =  events = HCalendar.find(:all, :text => data)

我们用下面的方式来读取所有Microformats Mofo了解的内容:

require 'mofo'
data =
parsed_data = Microformat.find(:all, :text => data)

随后Mofo将以易于访问的对象形式来返回所有Microformat数据。

Hpricot和Mofo都是非常有用的库,不过仍旧无法依赖它们来解析我们所有的输入,特别是如果我们要考虑标准的web接口时。使用URI概要(作为Web接口)标识Microformats时会有一些不同,但对它们的支持离通用和稳定的目标还很远。但愿随着Microformats的成熟,有更多处理这类情况的规范出现。

就现在而言,最简单的应用莫过于尝试用标准的CGI解析器来解析输入,然后回去检查下POST请求的body,看看它的内容是否是一个单独的字符串或独立的一些参数。我用到了microformat_interceptor这个通用的Proc对象(译者注:Proc是Ruby中的过程对象):

microformat_interceptor = Proc.new do |data|
parsed = CGI.parse(data)

if parsed.collect { |key, value| key + '=' + value[0] }.first == data
Microformat.find(:all, :text => data)
else
CGIMethods.parse_request_parameters(parsed)
end
end

随后为它分配text/html和application/x-www-form-urlencoded两种mime类型:

Mime::FORM = Mime::Type.lookup("application/x-www-form-urlencoded")

ActionController::Base.param_parsers[Mime::FORM] = microformat_interceptor
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发展的最新趋势。

评价本文

专业度
风格

您好,朋友!

您需要 注册一个InfoQ账号 或者 才能进行评论。在您完成注册后还需要进行一些设置。

获得来自InfoQ的更多体验。

告诉我们您的想法

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我
社区评论

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

允许的HTML标签: a,b,br,blockquote,i,li,pre,u,ul,p

当有人回复此评论时请E-mail通知我

讨论

登陆InfoQ,与你最关心的话题互动。


找回密码....

Follow

关注你最喜爱的话题和作者

快速浏览网站内你所感兴趣话题的精选内容。

Like

内容自由定制

选择想要阅读的主题和喜爱的作者定制自己的新闻源。

Notifications

获取更新

设置通知机制以获取内容更新对您而言是否重要

BT