Cookie について

基本中の基本ではあるが、案外よく理解していない Cookie について、調べてみた。

基本概念

Wikipedia "HTTP Cookie" によれば、

「HTTP cookie(エイチティーティーピークッキー、単にCookieとも表記される)は、RFC 2965などで定義されたHTTPにおけるWebサーバとウェブブラウザ間で状態を管理するプロトコル、またそこで用いられるWebブラウザに保存された情報のことを指す。ユーザ識別やセッション管理を実現する目的などに利用される。」

とある。基本的な動作については、同じく Wikipedia "HTTP Cookie" から。

そこで、1994年にNetscape Communications CorporationによってCookieが提案・実装された。Cookieでは次のようにサーバとクライアント間の状態を管理する。

   1. WebサーバがWebブラウザにその状態を区別する識別子をHTTPヘッダに含める形で渡す。
   2. ブラウザは次にそのサーバと通信する際に、与えられた識別子をHTTPヘッダに含めて送信する。
   3. サーバはその識別子を元にコンテンツの内容をユーザに合わせてカスタマイズし、ブラウザに渡す。必要があれば新たな識別子もHTTPヘッダに含める。

    以降2、3の繰り返し。

この仕組みによって、ステートレスなプロトコルであるHTTP上でステートフルなサービスを実現する。

言ってしまえばこれだけのことだ。しかし、実際にどう動いているのか、いままできちんと確認したことがなかったので調べてみる。

情報ソース

Cookie仕様 日本語訳
必読

とほほのCookie入門
わかりやすい。古い資料だが Cookie は枯れた技術だからあまり問題はないと思う。それにしても、とほほさんはすごすぎる・・・何者??

Cookie について
一年以上前に自分が書いたエントリ。ググッたら自分のエントリを発見してびびった。

サーバ側の Set-Cookie レスポンスヘッダ

Cookie は、まずサーバがブラウザに対して、「うちの URL を今度リクエストするときから、この Cookie 文字列を送ってよ」とレスポンスヘッダのなかでブラウザにお願いをするところから始まる。これはあくまでもお願いにすぎず、ブラウザとしては、ユーザーによって「Cookie なんて記憶するなよ!」と設定されていれば、このお願いを断らざるをえない。断ってしまえば、話はそこで終わりなので、以後、ブラウザは Cookie を受け入れる設定になっているものと考える。

さて、具体的にはレスポンスヘッダにどう設定すればいいのか? ここらを参考に、Rails 1.2.5 を使ってレスポンスヘッダをつけてみた・・・。(Rails でやるなよ、という話もあるが)
Firefox 2.0 で見てみる。

class SiteController < ApplicationController  
  def test1
    response.headers["Set-Cookie"] = "key1=value1; path=/; expires=Wednesday, 09-Nov-09 23:12:40 GMT"
    render :text => "<script type='text/javascript'> document.write(document.cookie); </script>"
  end
end

たしかにこうやると、(key1, value1) という (キー, 値) ペアの Cookie が生成される。たとえば、HTTP のセッションを覗き見できる Live HTTP headers のログは次のような感じだ。

http://example.com:3000/site/test1

GET /site/test1 HTTP/1.1
Host: example.com:3000
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; ja; rv:1.8.1.11) Gecko/20071127 Firefox/2.0.0.11
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Language: ja,en-us;q=0.7,en;q=0.3
Accept-Encoding: gzip,deflate
Accept-Charset: Shift_JIS,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Cache-Control: max-age=0

HTTP/1.x 200 OK
Connection: close
Date: Fri, 08 Feb 2008 05:07:54 GMT
Set-Cookie: _cookie_test_session_id=67da9aa65c636512742ef723b3c8efa0; path=/
Set-Cookie: key1=value1; path=/; expires=Wednesday, 09-Nov-09 23:12:40 GMT
Status: 200 OK
Cache-Control: no-cache
Server: Mongrel 1.0.1
Content-Type: text/html; charset=utf-8
Content-Length: 74

GET からはじまる最初のブロックがブラウザからサーバへの HTTP リクエスト、HTTP/1.x から始まる2つ目のブロックがサーバからブラウザへの HTTP レスポンスである。レスポンスの中で、Set-Cookie というヘッダがあるのがわかる。ちなみに _cookie_test_session_id はこの Rails アプリが作るセッション ID である。ちなみに作られた CookieFirefox だとメニューの [ツール] - [オプション] - [プライバシー] - [Cookie を表示] で確認できる。ちなみにこの機能を使って気がついたのだが、Cookie とは1組のキーと値のペア(+有効期限などの付加属性)なんだね。ずっと Cookie は複数のキーをもつ連想配列みたいなものだと勘違いしていた。粒度がちがっていたんだね。おのおのの Cookie(一組のキーと値) が独立であることは、それぞれが別の有効期限や適用パス(同じドメインのなかのどのパス以降の場合に Cookie を送信するか)が違うことでわかる。

クライアント側の Cookie リクエストヘッダ

サーバは、Set-Cookie というレスポンスヘッダを使ってブラウザに Cookie の記憶を依頼する。さてブラウザがそのお願いを聞き入れたとして、つぎにサーバにどんなリクエストを送るか、ふたたび Live HTTP headers を使って見てみる。

GET /site/test1 HTTP/1.1
Host: charlie.softculture.com:3000
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; ja; rv:1.8.1.11) Gecko/20071127 Firefox/2.0.0.11
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Language: ja,en-us;q=0.7,en;q=0.3
Accept-Encoding: gzip,deflate
Accept-Charset: Shift_JIS,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Cookie: _cookie_test_session_id=67da9ad65c635512742ef823b3c8efa0; key1=value1
Cache-Control: max-age=0

今度はリクエストの Cookie ヘッダが使われる。サーバが設定依頼をしたときは、Set-Cookie は2行にわたっていたのに、ここでは ;(セミコロン)で区切られて、1行にまとめられていることに注意しよう。しかも有効期限等のほかの属性は省略されている。この Cookie ヘッダを利用することが多かったから、Cookie連想配列だとおもってしまったらしい。だったら、Cookie ではなく Cookies ヘッダにしてくれればよかったのに。Cookie がはじめて実装されたころ、Cookie の粒度について人々の間で見解の相違があったのかもしれない。いちど Cookie をブラウザが受けいれてしまえば、同じキーの Cookie に関しては、値を変えるつもりがなければ、サーバ側はなにもしなくても、ブラウザは律儀に Cookie をサーバに送り続ける。

Javascript による Cookie の操作

ブラウザ上の Javascript のコードを用いて、Cookie を閲覧・変更することができる。ここで操作対象になる Cookie とは、もちろんブラウザが現在表示しているサイトのものである。(でなければ重大なセキュリティホールになってしまう) javascript:alert(document.cookie) とブラウザのアドレス部に入力してみよう。つぎような文字列が観察できるはずだ。

_cookie_test_session_id=67da9ad65c635512742ef823b3c8efa0; key1=value1

これは、ブラウザのリクエストヘッダと内容としては同じだ。";" でキー・値ペアが区切られている。

設定のほうはどうだろうか?

document.cookie = "key2=value2";

これで OK だ。こうするとなんだかいままで設定した他の Cookie が消えてしまう気がするが、そんなことはなく、他の Cookie はそのままで、それらに追加されるだけだ。
Cookie を消すには、expires に過去の日付を指定する。

document.cookie = "key3=value3; expires=Thu, 1-Jan-2000 00:00:00 GMT";