ハッシュをクッキーに格納してみた

rubyCGIで、クッキーに以下のようなハッシュを格納するコードを作ってみた。

hash = { 'a'=>'1', 'b'=>'2', 'c'=>'3' }

素直にクッキーに格納すると、rubyのコードはこんな感じだろうか。

require "cgi"
cgi = CGI.new
hash = { 'a'=>'1', 'b'=>'2', 'c'=>'3' }
hash.each do |key,value|
  cgi.cookies[key] = CGI::Cookie.new(key, vaue)
end
cgi.out("cookie" => cgi.cookies){"...出力..."}

HTTPのヘッダはこんな出力になる。

Set-Cookie: a=1; path=; expires=Wed, 27 Aug 2008 08:11:36 GMT
Set-Cookie: b=2; path=; expires=Wed, 27 Aug 2008 08:11:36 GMT
Set-Cookie: c=3; path=; expires=Wed, 27 Aug 2008 08:11:36 GMT

キーの数が少ないうちはこれでも良いかもしれないが、クライアントが覚えておくことができるクッキーの数はドメインごとに20までなので、キーの数だけクッキーを作るのは避けたい。
そこで、ハッシュに格納されている複数のキーと値を、1つのクッキーに格納することにする。HTTPのヘッダとしては、以下のような形にしたい。

Set-Cookie: a=1&b=2&c=3; path=; expires=Wed, 27 Aug 2008 08:11:36 GMT

これを実装するRubyのコードを作ってみた。
まず、HashからCookieを作成したり、CookieからHashを復元したりするメソッドを持ったCookieHashクラスを作った。

# cookie_hash.rb
class CookieHash < Hash
  def initialize(name, hash = {})
    super()
    @name = name
    update hash
  end

  def make_cookie
    values = []
    each do |key, value|
      values << "#{key}=#{value}"
    end
    return CGI::Cookie.new({'name'=>@name, 'value'=>values})
  end

  def load_cookie(cgi)
    cgi.cookies[@name].each do |v|
      md = /(.+)=(.+)/.match v
      self[md[1]] = md[2]
    end
  end
  
  def name
    return @name
  end

end

このCookieHashを使って、ハッシュをクッキーに格納するコードは下記のようになる。

require 'cgi'
require 'cookie_hash'
cgi = CGI.new
hash = CookieHash.new('hash', { 'a'=>'1', 'b'=>'2', 'c'=>'3' })
cgi.out("cookie" => hash.make_cookie){"...出力..."}

上記のコードのHTTPヘッダは、以下のようになる。

Set-Cookie: hash=a%3D1&b%3D2&c%3D3; path=; expires=Wed, 27 Aug 2008 08:11:36 GMT

CGIライブラリによって'='が'%3D'に変換されてますが、1つのクッキーに格納することはできました。
そしてクッキーから値を取り出すコードの方は、下記のようになります。

require 'cgi'
require 'cookie_hash'
cgi = CGI.new
hash = CookieHash.new('hash')
hash.load_cookie(cgi) # hash の値が { 'a'=>'1', 'b'=>'2', 'c'=>'3' } になる

なおこのコードには、少し考えれば気づく事だとは思うが、以下の制約があるので、注意が必要。

  • キーや値には文字列しか使えない (数値とかnilとかを入れると、取り出す時に文字列になる)
  • 値に"="を含められない (取り出す時に正しく取り出せない)