深入Rails2.3 Rack

注:这次dig,是由于遇到了metal代码无法自动加载的问题。

Rails2.3引入了Rack, 这使得rails内部的Http处理机制发生了很大的变化。Rack是一个非常微型的action stack, 和java社区的webwork里使用的xwork非常类似。

Rack的进入,对rails带来的最大一个好处,就是引入了metal。

./script/generate metal MetalTest 就可以产生一个metal, 位于app/metal目录下。

metal有什么特性呢? 快,它非常快。但是web应用一次平常的请求,动辄向数据库发送数十条SQL,总时间一般都在100ms的级别。而rails内置的action_pack已经是在ms级别了, 所以对于一般的web请求, 不需要用到它。

但是,对于一些只需要发送几条SQL,然后传给浏览器一些简单的json数据,对于这样的应用场景,证实metal大展身手的地方。

rails引入metal,用DHH自己的话,也是为了这样的场景。他在自己的产品campfire中,有许多定时的ajax请求,这种请求一般用action_pack做,每秒支持200次(5ms/request),而用metal,每秒可以达到3000次(0.3ms/request)

引入rack后,action_pack的地位就陡然下降了。在新的体系里面,所有的东西都是middleware,这里的action_pack,以及metal,都是作为一个middleware,集成到rails内部的。

使用rake middlewares 你可以看到:
use Rack::Lock
use ActionController::Failsafe
use ActionController::Reloader
use ActionController::Session::CookieStore, #<Proc:0xb76182a0@(eval):8>
use Rails::Rack::Metal
use ActionController::ParamsParser
use Rack::MethodOverride
use Rack::Head
use ActiveRecord::ConnectionAdapters::ConnectionManagement
use ActiveRecord::QueryCache
run ActionController::Dispatcher.new


这里结构就比以前清晰多了。以上所有的middelware,除了Rack:Metal以外,都是用来实现已有的功能的。从名字上可以非常清晰地看清楚各自的用途。

到这里,就应该提提我之前说到的那个问题了。代码不能重新加载? 这个问题之前也经常遇到,但由于时间关系,没有能够深入地纠察原因。

使用到的工具:一个编辑器,用raise设置堆栈。

首先,查看了Reloader这个middelware,从名称上看,它就是干这个事情的。


def call(env)
Dispatcher.reload_application
status, headers, body = @app.call(env)
# We do not want to call 'cleanup_application' in an ensure block
# because the returned Rack response body may lazily generate its data. This
# is for example the case if one calls
#
# render :text => lambda { ... code here which refers to application models ... }
#
# in an ActionController.
#
# Instead, we will want to cleanup the application code after the request is
# completely finished. So we wrap the body in a BodyWrapper class so that
# when the Rack handler calls #close during the end of the request, we get to
# run our cleanup code.
[status, headers, BodyWrapper.new(body)]
end


果不其然,这里有个Dispatcher.reload_application。在每次请求过后,rails都要清除action_support中Dependencies所管理的常量,然后在下一次请求的时候,重新加载整个应用。

很显然,问题应该在清除常量这里,好像Reloader没有做这个操作啊?

呵呵,看一下注释,就清楚了,这里曾经出过一个Bug,Rails开发者用BodyWrapper解决了这个bug.clear常量的操作就在close方法里:
def close
@body.close if @body.respond_to?(:close)
ensure
Dispatcher.cleanup_application
end


难道是matal没有使用Reload这个中间件?
打个断点试试,首先找到metal这个中间件的核心代码:call
    def call(env) 
raise
@metals.keys.each do |app|
result = app.call(env)
return result unless result[0].to_i == 404
end
@app.call(env)
end


出现了这样的堆栈:


/usr/lib/ruby/gems/1.8/gems/rails-2.3.3/lib/rails/rack/metal.rb:43:in `call'

/usr/lib/ruby/gems/1.8/gems/actionpack-2.3.3/lib/action_controller/session/c
ookie_store.rb:93:in `call'
/usr/lib/ruby/gems/1.8/gems/actionpack-2.3.3/lib/action_controller/reloader.
rb:29:in `call'
/usr/lib/ruby/gems/1.8/gems/actionpack-2.3.3/lib/action_controller/failsafe.
rb:26:in `call'
.....................

明明在用,并且和action_controller的堆栈一摸一样。

好,看一下action_support中Dependencies中的代码,发现有log信息,打开日志看看。
ActiveSupport::Dependencies.log_activity=true

发现日志也非常正常!

实在没有办法了,问题只能处在metal自己身上。
陡然间,就在刚才设置raise的地方,发现了:

@metals.keys....................

原来在使用一个内部变量,先试试让每次call都重新刷新@metal变量:问题居然解决了!

哦,原来这个变量中直接将每个metal加载后产生的常量放了进来,虽然reloader有清洗,但是他肯定不会清洗这个变量里的metal类。

到这里,问题就基本解决了,下一步看看怎么切换metal这个中间件。

呵呵,对了,rack中内置了丰富的插入删除更改中间件的方法。

附赠代码:
lib/smart_metal.rb

#
# <<代码重构>>中指出用方法代替变量,是多么的闪闪发光啊。
# 你看,在这里,由于Metal用的是变量,我只能copy&plast
class SmartMetal < Rails::Rack::Metal

def get_metals
metals = ActiveSupport::OrderedHash.new
self.class.metals.each { |app| metals[app] = true }
metals
end

def call(env)
get_metals.keys.each do |app|
result = app.call(env)
return result unless result[0].to_i == 404
end
@app.call(env)
end
end


config/initializers/my_init.rb

Rails::configuration.middleware.swap 'Rails::Rack::Metal', SmartMetal

然后,在运行
$ rake middleware
(in /cygdrive/d/dev/twork)
use Rack::Lock
use ActionController::Failsafe
use ActionController::Reloader
use ActionController::Session::CookieStore, #<Proc:0x7fc2552c@(eval):8>
use SmartMetal############ 看到了吧 #################################
use ActionController::ParamsParser
use Rack::MethodOverride
use Rack::Head
use ActiveRecord::ConnectionAdapters::ConnectionManagement
use ActiveRecord::QueryCache
run ActionController::Dispatcher.new
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值