论坛首页 编程语言技术论坛

Rails每周一题(二十): Rack变革

浏览 4950 次
该帖已经被评为良好帖
作者 正文
   发表时间:2009-11-30   最后修改:2009-12-01

 

    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看看清楚。

 

use Rack::Lock
use ActionController::Failsafe
use ActionController::Session::CookieStore, #<Proc:0x01a76984@(eval):8>
use Rails::Rack::Metal
use ActionController::ParamsParser
use Rack::MethodOverride
use Rack::Head
use ActionController::StringCoercion
use ActiveRecord::ConnectionAdapters::ConnectionManagement
use ActiveRecord::QueryCache
run ActionController::Dispatcher.new
 

 

    没错,在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

 

   你没发现,大部分主要对象都实现了call(env)接口么?

Rails Metal Applications

    Rails2.3集成进了Metal Application,它的作用主要是应付那些“少而多”的request。这里的少,是指访问数据库少,逻辑少(个人认为逻辑复杂的不适合放在metal里面做),多是指访问次数多(如果访问频率没有那么高,我觉得没必要)。当数据库访问非常之少时,性能的瓶颈已经在Action Controller stack,问题就来了:为什么不绕开Action Controller stack呢?Metal的作用就是这样的,它作为Rack middleware stack的一分子(在上面那个list中找),在到达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民工还真是他妈的累,不过如果可以苦中作乐,倒也认了。每周一题 越来越名不副实了,不过还是继续学习,继续写吧。

   发表时间:2009-11-30  
那我是不是可以把它看成java中的servlet呢
0 请登录后投票
   发表时间:2009-11-30  
mycybyb 写道
那我是不是可以把它看成java中的servlet呢


看成servlet filter更贴切
0 请登录后投票
   发表时间:2009-12-01   最后修改:2009-12-01
没有java语法之表,而有java抽象之实。所以这不是技术,就像oop一样,是哲学吧.
0 请登录后投票
   发表时间:2009-12-01  
这个页面上的前面两个不是javaeye内部的链接,点击了之后既然提示要登录。貌似我已经登录了。
0 请登录后投票
   发表时间:2009-12-01  
zy8643954 写道
这个页面上的前面两个不是javaeye内部的链接,点击了之后既然提示要登录。貌似我已经登录了。


不好意思,修改了。javaeye的编辑功能做得太差了。这么多bug,也不见修正。
0 请登录后投票
   发表时间:2009-12-03  
鼓励鼓励!

Rack的middleware用起来真别扭。  我敢打赌这绝对不是Rails Core team设计的。

提供的call stack和Method Chain很难理解, 也很难用。
0 请登录后投票
论坛首页 编程语言技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics