Rackを読む #4

Builder.new

rack/builder.rbにあるexampleを見てみる。

app = Rack::Builder.new {
  use Rack::CommonLogger #lamda create
  use Rack::ShowExceptions
  map "/lobster" do
    use Rack::Lint
    run Rack::Lobster.new
  end
}

まずはDSLである"use","map","run"が定義されている箇所がBuilder内にあるはずなので、
それを見つけてみる。というか見えてる。

    def use(middleware, *args)
      @ins << lambda { |app| middleware.new(app, *args) }
    end

    def run(app)
      @ins << app #lambda { |nothing| app }
    end

    def map(path, &block)
      if @ins.last.kind_of? Hash
        @ins.last[path] = Rack::Builder.new(&block).to_app
      else
        @ins << {}
        map(path, &block)
      end
    end

 

Builder.newのデータ構造

DSL内のコードを読むとインスタンスのデータ構造は以下のようになりそうだ。

#<Rack::Builder 
 @ins=[#<Proc {|app| Rack::CommonLogger.new(app, *args)}>,
  #<Proc {|app| Rack::ShowExceptions.new(app, *args)}>,
  {"/logster"->#<.to_appの動きが分からないので保留>} ]>
 >

@insにDSLで定義したものを積んでいるようだ。
 

Builder.to_app

このメソッドがRackの一番の肝となる。

    def to_app
    
      #最後の値がHashであればRack::URLMap.newしたもので上書きする
      @ins[-1] = Rack::URLMap.new(@ins.last)  if Hash === @ins.last
      
      # run もしくは map の部分を@insから取り出す
      inner_app = @ins.pop
      
      #下で解説
      @ins.reverse.inject(inner_app) { |a, e| e.call(a) }
    end

最後の行でappを一気に組み上げる。
DSLをappに組み上げる動きが多少トリッキーなので、
上述のケースの場合のデータの動きを一行づつ書いてみる。

      # 1.@ins=[#<Proc CommonLogger.new(app, *args)>, #<Proc Rack::ShowExceptions.new(app, *args)>]
      #   @ins=[#<Proc Rack::ShowExceptions.new(app, *args)>, #<Proc CommonLogger.new(app, *args)>]
      
      # 2.{|a=#<URLMap>, e=#<Proc CommonLogger.new(app, *args)>| CommonLogger.new(inner_app)};
      
      # 3.{|a=#<Proc ShowExceptions.new(app, *args)>, e=logger_instanse| ShowExceptions.new(logger_instanse)};
      @ins.reverse.inject(inner_app) { |a, e| e.call(a) }

この用に、最後に定義した run もしくは map を
useで定義したクラスのinitialize引数に入れ、newさせる。
またそれで生成されたインスタンスをuseで定義したクラスのinitialize引数に入れるというのを
繰り返し、appを組み上げているようだ。
 

次回は

各クラスのinitializeの部分とWEBrickなどでどうやって動かすかを見ていくつもり。