Rails 一点点心得: Rack 在 Rails 中的使用

zhlwish · 发布于 2012年8月09日 · 最后由 demond 回复于 2016年3月21日 · 7496 次阅读
96

新手上路,有说得不对的地方请大家指正,嘿嘿,最开始发在我的博客上:Rack在Rails中的使用

今天工作中遇到Rails的一个问题,最后发现是使用的一个叫Rack包版本不兼容Rails2.3引起的,虽然问题很容易就解决了,但是Rack这个包是干什么的却引发了我的兴趣,经过查资料阅读代码,写了这篇博客。

Rack是一个中间件,介于Web应用程序和Web服务器之间,为所有的Web服务器都提供了统一的接口,使用Rack构建的Web应用程序能简单换到其他的Web服务器上,因为Rails在底层用到了Rack,所以我们可以在开发的时候使用Webrick,然后通过fastcgi或者ruby_mod发布到nginx或者Apache。

#Rack简介

使用Rack构建的应用程序比Rails要简单多了,当然,功能也简单多了,只有request, response, session, logger等一些基本的组件,不过对于一些简单的应用,足够了。基于Rack的Web应用太简单了,以至于大家都用一句话来描述:

A Rack application is any Ruby object that responds to the call method, takes a single hash parameter and returns an array containing the response status code, HTTP response headers and the response body as an array of strings.

意思就是,一个包含call(env)方法的对象就能做为一个Rack Web应用,参数env是一个hash,方法的返回值是一个列表,包含三个元素:HTTP状态码(200, 500等),HTTP响应头(Hash),HTTP响应内容(字符串数组)。

先安装rack和mongrel

sudo gem install rack sudo gem install mongrel --pre

下面代码演示了如何创建一个用Rack来创建一个Web应用,在控制台中执行下面的代码,然后用浏览器访问http://localhost:3000Hello,即可看到显示“ Rack!”的页面。

#!/usr/bin/env ruby require 'rubygems' require 'rack'

class HelloRack def call(env) [ 200, {"Content-Type" => "text/html"}, ["Hello Rack!"] ] end end

Rack::Handler::Mongrel.run(HelloRack.new, :Port => 3000)

我们可以将后端的Web服务器Ruby标准库中的WEBrick,只需要将上面代码的最后一行改为:

Rack::Handler::WEBrick.run(HelloRack.new, :Port => 3000)

当然要是Rack只能支持对于根路径的响应就没有啥意义了,Rack还提供了一个称为Rack::Builder的API,提供了简单的DSL,可以定义简单的URL mapping,使对不同路径的请求由不同的程序来处理,不过,这个和Rails暂时无关,有兴趣请阅读Ruby on Rack #1 - Hello Rack!Ruby on Rack #2 - The Builder,或者Understanding Rack Builder

Rack还提供一个有意思的东西,叫rakeup,使得可以将主要的对请求的响应都放在一个名为config.ru文件中,和Rails关系不大,这篇文章可能讲解得更加清楚一些:Using Rack

我目前发现的,真正和Rails相关的,是Rack::Server API。下面是一个简单的示例。

Rack::Server.start( :app => proc {|env| 200, {"Content-Type" => "text/html"}, ["Hello Rack!"]]}, :server => 'webrick', :Port => 3030 )

Server#start({})的参数是一个Hash,其参数包括:server, :Host, :Port等,其中:config可以是一个.ru文件的路径,用于覆盖:app的配置,比如下面代码,我们从config.ru中读取处理请求的函数,比如下面的代码。

Rack::Server.start( :config => 'config.ru' :server => 'webrick', :Port => 3030 )

config.ru的代码如下所示,仅仅一行。

run proc {|env| [200, {"Content-Type" => 'text/html'}, ["Hello Rack!"]]}

Rails中的Rack

对Rails的分析采用倒推的方式,我们启动Rails应用的时候,使用rails server命令。这个命令会调用railties/lib/rails/commands/server.rb代码,下面是代码的节选。

module Rails class Server < ::Rack::Server def start # … super end

def default_options super.merge({ :Port => 3000, :DoNotReverseLookup => true, :environment => (ENV['RAILS_ENV'] || "development").dup, :daemonize => false, :debugger => false, :pid => File.expand_path("tmp/pids/server.pid"), :config => File.expand_path("config.ru") }) end end end

可以看到,Rails::Server继承了Rack::Server,在前一节已经了解到,Rack::Server#start()方法会启动服务,Rails:Server覆盖了start()方法,并且在做了一些处理之后使用super调用了父类的同名方法,因此调用这个方法同样能启动服务。而且,Rails:Server也覆盖了父类的default_options(),这里的super也表示调用父类的同名方法,其返回值为Hash,使用Hash#merge()覆盖了父类的一些配置信息,比如将Rack默认的9292端口改为3000,等等。最后是最为关键的配置信息::config => File.expand_path("config.ru"),意味着Rails::Server会读取从Rails App根目录的config.ru,然后交给Rack执行。

在Rails App的根目录下面找到config.ru,里面一如既往的简单,只有两条语句:

require ::File.expand_path('../config/environment', FILE) run AppName::Application

第一句是加载config/environment.rb,第二句和前一节的最后一段的代码非常相似,我们现在可以勇敢地猜测,AppName::Application中肯定定义了call(env)方法。

Rails3的config/environment.rb文件也很简单,第一句加载相同目录下的application.rb,第二句调用了AppName::Applicationinitialize!()方法。(注意,Rails2的启动流程不一样)。

require File.expand_path('../application', FILE) AppName::Application.initialize!

config/application.rb还是的定义依然很简单,继承了Rails::Application,仅仅做了一些配置工作,比如禁止在log文件中记录:password,启用Rails3新引入的SASS和Coffie Script。

module AppName class Application < Rails::Application config.encoding = "utf-8" config.filter_parameters += [:password] config.active_record.whitelist_attributes = true config.assets.enable = true config.assets.version = '1.0' end end

因此,进一步找到railties/lib/rails/application.rb,这个文件定义了Rails::Application,而且终于看到了我们预测中的call(env)。当然,除此意外Rails做的更多,比如定义了Rails::Rack::Logger用来替代Rack自身的日志系统。

module Rails class Application < Engine def call(env) env["ORIGINAL_FULLPATH"] = build_original_fullpath(env) super(env) end end end

另外,Rails::Application的父类Rails::Engine是Rails的启动配置的核心所在,一次加载了config/routes.rb、app/views等信息。

#总结

总的来说,Rails首先加载了config/application.rb中定义了AppName::Application,然后调用其initialize!()方法执行一些初始化工作,最后使用Rack的run AppName::Application运行整个应用程序。Rails也通过Rack可以很方便的部署于Apache、nginx、lighttpd等各种服务器,包括Ruby自带的Webrick,以及mongrel等。要更深入的了解Rack需要进一步阅读参考中的链接。

参考

共收到 9 条回复
475

谢谢,受教

122

不错, 学习了

304

学习了,感谢分享

96

不错

96

sinatra其实就是rack web应用

9楼 已删除
10楼 已删除
96

谢谢,学习了

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册