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

zhlwish · August 09, 2012 · Last by demond replied at March 21, 2016 · 11802 hits

新手上路,有说得不对的地方请大家指正,嘿嘿,最开始发在我的博客上: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 需要进一步阅读参考中的链接。

参考

谢谢,受教

不错,学习了

学习了,感谢分享

Unknow user #5 August 13, 2012

不错

Unknow user #6 August 13, 2012

sinatra 其实就是 rack web 应用

9 Floor has deleted
10 Floor has deleted

谢谢,学习了

You need to Sign in before reply, if you don't have an account, please Sign up first.