Rack,貌似已经把Rails改革了。
Rack
Rack是什么?Rack提供了用ruby开发web应用的一个接口。比如Rails框架,就是由rack承担着跟web服务器之间的交互。简而言之,Rack已经成为ruby开发web应用程序的一个规范,它统一了web服务器和web框架之间的交互接口。它所支持的web服务器和web框架已经非常之多:http://rack.rubyforge.org/doc/ 。
Rack的规范非常简单,就是一个call方法:http://rack.rubyforge.org/doc/SPEC.html 。接受一个environment,然后返回status,header和body。这不就是http的全部么?
Rack Middleware
为什么不可以在Rack和web framework之间做些事情呢?于是Rack middleware发展起来了。Rack中间件其实就是一些遵循Rack规范的应用。为什么要发展出Rack middleware?因为很多事情在这里做起来就非常方便,其实就是AOP的方式(你也可以把它理解成filter)。
看一下众多的middlewares:http://wiki.github.com/rack/rack/list-of-middleware 。或者在你的最新(2.3)rails应用下敲下命令行:rake middleware。
ParamsParser:
def call(env) if params = parse_formatted_parameters(env) env["action_controller.request.request_parameters"] = params end @app.call(env) end
CookieStore:
def call(env) env[ENV_SESSION_KEY] = AbstractStore::SessionHash.new(self, env) env[ENV_SESSION_OPTIONS_KEY] = @default_options.dup status, headers, body = @app.call(env) session_data = env[ENV_SESSION_KEY] options = env[ENV_SESSION_OPTIONS_KEY] if !session_data.is_a?(AbstractStore::SessionHash) || session_data.send(:loaded?) || options[:expire_after] session_data.send(:load!) if session_data.is_a?(AbstractStore::SessionHash) && !session_data.send(:loaded?) session_data = marshal(session_data.to_hash) raise CookieOverflow if session_data.size > MAX cookie = Hash.new cookie[:value] = session_data unless options[:expire_after].nil? cookie[:expires] = Time.now + options[:expire_after] end cookie = build_cookie(@key, cookie.merge(options)) unless headers[HTTP_SET_COOKIE].blank? headers[HTTP_SET_COOKIE] << "\n#{cookie}" else headers[HTTP_SET_COOKIE] = cookie end end [status, headers, body] end
Middleware,正不就是做这些事情的好地方么?
又比如,某人开发的google_analytic middleware:
def call env status, headers, response = app.call(env) if headers["Content-Type"] =~ /text\/html|application\/xhtml\+xml/ body = "" response.each { |part| body << part } index = body.rindex("</body>") if index body.insert(index, tracking_code(options[:web_property_id])) headers["Content-Length"] = body.length.to_s response = [body] end end [status, headers, response] end
Rails on Rack
请再次用rake middleware看看清楚,没错,在Rails2.3之后,所有的东西都变成middleware了,包括Action Controller stack本身。
Rails应用的调用就是对middleware堆栈的调用。从最初始的Rack::Lock:
def call(env) old, env[FLAG] = env[FLAG], false @lock.synchronize { @app.call(env) } ensure env[FLAG] = old end
到最后的ActionController::Dispacher.new:
def call(env) if @@cache_classes @app.call(env) else Reloader.run do # When class reloading is turned on, we will want to rebuild the # middleware stack every time we process a request. If we don't # rebuild the middleware stack, then the stack may contain references # to old classes metal classes, which will b0rk class reloading. build_middleware_stack @app.call(env) end end end
而dispacher,正是Action Controller stack开始启动的地方。上面代码中的@app本身,也是一个middleware:
def build_middleware_stack @app = @@middleware.build(lambda { |env| self.dup._call(env) }) end
_call(env)到底做了什么,还是看个究竟吧:
def _call(env) @env = env dispatch end def dispatch begin run_callbacks :before_dispatch Routing::Routes.call(@env) rescue Exception => exception if controller ||= (::ApplicationController rescue Base) controller.call_with_exception(@env, exception).to_a else raise exception end ensure run_callbacks :after_dispatch, :enumerator => :reverse_each end end
Rails Metal Applications
Rails2.3集成进了Metal Application,它的作用主要是应付那些“少而多”的request。这里的少,是指访问数据库少,逻辑少(个人认为逻辑复杂的不适合放在metal里面做),多是指访问次数多(如果访问频率没有那么高,我觉得没必要)。当数据库访问非常之少时,性能的瓶颈已经在Action Controller stack,问题就来了:为什么不绕开Action Controller stack呢?Metal的作用就是这样的,它作为Rack middleware stack的一分子,在到达Action Controller stack之前对request进行拦截,如果满足,则直接返回。还是看下源代码吧:
def call(env) @metals.keys.each do |app| result = app.call(env) return result unless @pass_through_on.include?(result[0].to_i) end @app.call(env) end
http://github.com/rails/rails/blob/master/railties/lib/rails/rack/metal.rb
更多
关于更多的知识,请见:http://guides.rubyonrails.org/rails_on_rack.html 。在这里会看到script/server的实现,如何定制rackup,如何定制middleware,如何生成metal等。
EOF
ps:
技术总是翻天覆地地变化着,rails2.3又有了很多不同。做一个IT民工还真是他妈的累,不过如果可以苦中作乐,倒也认了。每周一题 越来越名不副实了,不过还是继续学习,继续写吧。