Rails毎週1題(20):Rack変革



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.環境を受け入れ、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] << "
#{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
 
 
そう、Rails 2.3以降、すべてのものがmiddlewareになり、ActionController 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は、ActionController stackが起動し始めた場所です.上のコードの@app自体もmiddlewareです.
 
 
     def build_middleware_stack
        @app = @@middleware.build(lambda { |env| self.dup._call(env) })
      end

 
    _コール(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には向いていないと考えられている)、アクセス回数が多いこと(アクセス頻度がそれほど高くなければ、必要ないと思います).データベースへのアクセスが非常に少ない場合、パフォーマンスのボトルネックはすでにActionController stackにあります.問題は、なぜActionController stackを迂回しないのかということです.Metalの役割は、Rack middleware stackの一員として(上のリストで探します)、ActionController 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:
 
技術はいつも天地を覆すように変化している、rails 2.3またいろいろな違いがありました.IT民工になるのは本当に疲れているが、苦労して楽しむことができれば、むしろ認めた.毎週1題はますます名実ともになってきたが、勉強を続け、書き続けよう.