Ruby on Rails(ROR)的成功主要在于它的简单化假设(simplifying assumptions)。Rails并不是向你提供一大堆用以解决各种问题的工具,而是为你提供了一种用以解决各类常见问题的方式。可以非常讯速地创建 Rails应用,只要:尝试把关系数据库中的数据暴露出来,数据库表有确定的名称与结构,你愿意采用一种模型-视图-控制器(Model-View- Controller)架构,等等。因为在Web应用领域中,很多问题均符合这些假设,所以问题一般都能顺利解决,很少会遇到麻烦。
Rails过去的版本暴露的是经典的REST-RPC混合服务,不过自Rails 1.2版起,它开始更加注重REST式设计了。或许这是必然的:HTTP统一接口(uniform interface)是另一个简单化假设(simplifying assumptions)。我在第7章已经向你展示过Rails框架是如何做到用少量代码实现复杂的REST式服务了,在这一节,我将对此做一个回顾,并 概括性地讲述Rails的REST式架构。
当Rails收到一个HTTP请求时,它会根据该请求的目标URI,把请求路由给适当的控制器类(controller class)处理。如示例12-1所示,config/routes.rb文件定义了Rails应如何对给定请求进行处理。
示例12-1:一个简单的routes.rb文件
# routes.rb
ActionController::Routing::Routes.draw do |map|
map.resources :weblogs do |weblog|
weblog.resources :entries
end
end
config/routes.rb文件可以相当复杂。第7章中展示的那个(见示例7-3)相对来说算复杂的:我有很多资源,必须克服简单化假设,以得到想要的URI结构。示例12-1是一个较简单的、接纳了简单化假设的routes.rb文件。
通过该文件可知有两个控制器类(WeblogsController和EntriesController),该文件告诉Rails如何把收到的请 求路由给这两个类。WeblogsController类处理发给 /weblogs 及 /weblogs/{id} 的请求。这里的路径变量{id}是放在params[:id]里的。
EntriesController类处理发给/weblogs/{weblog_id}/entries及/weblogs /{weblog_id}/entries/{id}的请求。这里的路径变量{weblog_id}是放在params[:weblog_id]里的, {id}是放在params[:id]里的。
{id}、{weblog_id}等变量常用于进行资源(resource)与系统里特定对象(object)的关联。它们常常跟数据库里的记录 ID相对应,并经常被代入ActiveRecord的find方法。在我的社会性书签服务(见第7章)里,试过给它们像{username}这样具备描述 性的名称,并用它们来标识名称而不是ID。
正如我在第7章中所讲述的那样,每个Rails控制器可以暴露两种资源。你可以有一个“列表”或“工厂”资源(响应GET和/或POST请求)和许 多“对象”资源(响应GET、PUT和DELETE请求)。一般来说,列表资源跟数据库表相对应,而对象资源跟数据库表里的记录相对应。
每个控制器都是一个Ruby类;于是,向一个类“发送”HTTP请求,就意味着调用某个特定的方法。Rails为每个控制器定义了五个标准方法,并 通过HTTP GET暴露了两个特殊的视图模板。在示例12-1中调用的map.resources :weblogs 使得下面这七种HTTP请求成为可能。
你也许不会为创建的所有控制器都暴露这七个接口。特别是,多半不会使用那些特殊视图,除非打算把Web服务作为一个Web网站来运营——没问题,你只要别实现那些不打算暴露的方法或视图就行了。
Rails使我们能够更容易实现“根据客户端的请求,返回一个资源的不同表示”。示例12-2所示的Ruby代码可以返回一个博客的三种不同表示。 它将根据客户端请求的目标URI或Accept报头决定返回哪个表示。若客户端向/weblogs/1.html发出请求,就会得到HTML版表示;若客 户端向/weblogs/1.png发出请求,就会得到PNG版表示。respond_to函数负责解释客户端的能力与需求。你所需要做的,就是按优先级 实现被支持的选项。
示例12-2:返回多个表示中的一个
respond_to do |format|
format.html { render :template => 'weblogs/show' }
format.xml { render :xml => weblog.to_xml }
format.png { render :text => weblog.generate_image,
:content_type => "image/png" }
end
HTML和ActiveResource XML序列化格式(serialization format)是两种特别常见的表示格式(representation formats)。HTML表示是用Rails视图来展现的(就像在面向人类用户的Web应用程序中一样)。要把一个ActiveRecord对象暴露为 一个XML文档,你只要调用该对象(或对象列表)的to_xml方法即可。
通过Rails插件,我们可以轻易地把具有其他表示格式的数据暴露出来。在第7章,我安装了atom-tools Ruby gem,用以为书签列表生成Atom提要(feed)。示例7-8里有一个respond_to代码块,它用于根据请求决定返回Atom还是普通XML表 示。
Rails的工作就是根据收到的表示(incoming representation)生成一组关键字-值对(key-value pairs),并以params hash的形式来提供这些关键字-值对。默认情况下,它知道如何解析Web浏览器发送的表单编码的(form-encoded)文档,以及to_xml生 成的XML文档。
如果希望它能够解析自己的表示格式,你可以在ActionController::Base.param_ parsers hash里添加一个新的Proc对象。该Proc对象是一段代码,它的作用是处理服务器收到的具有给定媒体类型的表示。关于param_parsers hash的详细情况,请参阅Rails文档。
Rails 1.2在融合human web与programmable web方面做得相当好。正如我在第3章中展示的,Rails自带一个叫做scaffold_resource的代码生成器,它可以把数据库表暴露为一组资 源。你可以用Web浏览器来访问这些资源,也可以用Web服务客户端(如ActiveResource)来访问这些资源。
如果你用Web浏览器来访问scaffold_resource服务的话,你会得到数据库对象的HTML表示,以及用于操作它们的HTML表单(由 前面提到的new.rhtml和edit.rhtml生成)。你可以通过发送表单编码格式(form-encoded format)的表示来创建、修改或删除资源。PUT和DELETE请求是通过重载的POST(overloaded POST)来模拟的。
如果你用一个Web服务客户端来访问scaffold_resource服务的话,你会得到数据库对象的XML表示。你可以修改该XML文档,并通过PUT请求把它发回去。非重载的(non-overloaded)POST与DELETE请求的工作方式与你预期的一样。
关于programmable web与human web的基本相似性,没有比这更能令人信服的例子了。第7章因为篇幅原因所以没有讲述Rails的这方面内容,不过它非常有说服力地证明了Rails适合 于“设计具有相同功能的网站和Web服务”的场合。Rails可以用同一套底层代码来暴露网站和Web服务。
下面是一个根据第6章的通用设计步骤修改后得到的版本。我在第7章设计社会性书签服务时已经非正式地采用这些设计步骤了。这里的设计步骤跟第6章的 区别在于:你不是直接把数据集划分为一个个资源,而是把数据集划分为一个个控制器,再把控制器划分为资源。这样,就不会出现“你最后得到的资源不适应 Rails控制器”的问题了。
本文节选自博文视点出版公司即将推出的经典著作《RESTful Web Services中文版》中的第12章《REST式服务框架》。
《RESTful Web Services中文版》向 读者介绍了什么是REST、什么是面向资源的架构(Resource-Oriented Architecture,ROA)、REST式设计的优点、REST式Web服务的真实案例分析、如何用各种流行的编程语言编写Web服务客户端、如何 用三种流行的框架(Ruby on Rails、Restlet和Django)实现REST式服务等。不仅讲解REST与面向资源的架构(ROA)的概念与原理,还向读者介绍如何编写符合 REST风格的Web 2.0应用。本书详实、易懂,实战性强,提供了大量RESTful Web服务开发的最佳实践和指导,适合广大的Web开发人员、Web架构师及对Web开发或Web架构感兴趣的广大技术人员与学生阅读。
与此同时,博文视点还授权InfoQ中文站独家为大家提供额外的样章进行试读:欢迎下载第3章《REST式服务有什么不同》
相关阅读: