Rubyアプリケーションサーバのヘッダの扱い

最近Rubyフレームワークでよく使われるRack.前回の記事のこともありRackでのCookieの扱いが気になったので,Rackがサポートしているサーバに関してレスポンスヘッダの処理を調べてみた.バージョンとしてはRamazeが2008.06で,他のに関しては()で表示している.

WEBrick(1.3.1)

headers.each { |k, vs|
  vs.each { |v|
    res[k] = v
  }
}

WEBrickはResponseでのCookieは別途@cookiesを用意して複数返せるようにしているのだけど,Rackでは上記のようにResponseのヘッダに直接書き込んでいるので,複数あると上書きが行われて複数返すことは出来ない.

CGIFCGI,etc...

CGIとかFastCGIとかもろもろ.

headers.each { |k, vs|
  vs.each { |v|
    STDOUT.print "#{k}: #{v}\r\n"
  }
}

printだったりwriteだったりするけど,追記していくスタイルなので問題なし(のはず).

Mongrel(1.1.5)

headers.each { |k, vs|
  vs.each { |v|
    response.header[k] = v
  }
}

となっている.MongrelのResponseを調べると,response.headerというのはHeaderOutのインスタンスへのattr_readerなので,HeaderOutを調べる.

def []=(key,value)
  if not @sent.has_key?(key) or @allowed_duplicates.has_key?(key)
    @sent[key] = true
    @out.write(Const::HEADER_FORMAT % [key, value])
  end
end

@outは初期化時に設定されているStringIOのインスタンスmongrelでは@sentで出力タイプを登録してその値をどんどんと貯めていく方法らしい.ということで,問題なく複数のCookieを使うことが出来る.

Thin(0.8.2)

rack/handler/thin.rbからthin/server.rbでbackendサーバへと行って…という流れは省略.

  • thin/response.rb
def headers=(key_value_pairs)
  key_value_pairs.each do |k, vs|
    vs.each { |v| @headers[k] = v.chomp }
  end
end

となっていて,@headersはHeadersクラスのインスタンスなので,そのメソッドを見る.

  • headers.rb
def []=(key, value)
  if !@sent.has_key?(key) || ALLOWED_DUPLICATES.include?(key)
    @sent[key] = true
    @out << HEADER_FORMAT % [key, value]
  end
end

@outは単なる配列.なので,ThinではMongrelと同じく@sentで出力タイプを登録して,中身は配列で所持している(出力する時にはto_sをオーバーライドしてjoinの結果を返している).なので,複数返すことが出来る.

Ebb (0.3.2)

レスポンスが返って来なかった.Ramazeが原因らしい.

  • ramaze/current/session.rb
response.set_cookie(SESSION_KEY, hash)
p :foo
# set client side session cookie
if val = request['session.client'] and
  (!val.empty? or request.cookies["#{SESSION_KEY}-client"])
  cookie = hash.merge(:value => marshal(val))
  p :bar
  response.set_cookie("#{SESSION_KEY}-client", cookie)
end
p :baz

で:fooは出力されるのに,:barと:bazが出力されないという不思議.なので,とりあえすsession.finishの所をコメントアウトして実行すると,なかなかに酷い結果だった.これはどんなソースなのかと読んでみると

  • ebb.rb
headers.each { |field, value| @head << "#{field}: #{value}\r\n" }

となっていた.RackのResponseではset_cookieが複数回呼ばれると{"Set-Cookie" => ['foo=foo', 'bar=bar']}となるので,これはいくらなんでも考えて無さ過ぎじゃ…

Passenger(2.0.3)

  • passenger/rack/request_handler.rb
headers.each do |k, vs|
  vs.each do |v|
    output.write("#{k}: #{v}#{CRLF}")
  end
end

動かしてないからわからないけど,output.writeからどんどん追加していくのが想像できるので,複数Cookieは使えるはず.これよりも気になったのはインデントがタブだった.Rubyでは結構珍しい.

Fuzed(0.1.0)

gemを入れてみたけどErlangとかよく分からないし,色々他のgemも使っているようで面倒になった.まぁ使わないからいいだろうと^^;

結論

昨日嵌った複数Cookieの問題は,テストで使っていたWEBrickのRackハンドラのみが複数の出力に対応していなかったというだけで,実際動かす時にWEBrickを使う人はいないと思うので,誰も困っていなかったというオチ.まぁでもEbbで動かしている人がいるのかどうか分からないけど,ヘッダとか問題になっていないのか気になった.
WEBrickに関しては該当箇所を

headers.each { |k, vs|
  if k == 'Set-Cookie'
    vs.each { |cookie|
      res.cookies << cookie
    }
  else
    vs.each { |v|
      res[k] = v
    }
  end
}

とかやれば複数Cookie持てるのだけど,修正してくれないのだろうか?