Cookie の概要と使い方

Web アプリケーションを作る上では欠かせない Cookie。 本コーナーでは Cookie の仕組みを詳しく説明し、Perl スクリプトでの実装方法を簡単に解説していきます。

目次

Cookie とは?

Cookie とは、サーバとの通信において特定の情報をクライアント(ブラウザー)に保持させるものです。 主にユーザーセッション識別子を保存するために使われることが多いでしょう。 この仕組みにより、ログインという仕組みを作ったり、あなた向けにパーソナライズされたコンテンツや 広告を表示するなどn仕組みを実現できるようになります。

Cookie を使うことによって多彩な機能を実現することができますので、 CGI を作成するにあたっては非常に便利な機能です。

Cookie は Netscape Communications Corporation が PERSISTENT CLIENT STATE HTTP COOKIES という仕様を提案し、自社のウェブブラウザー Netscape に実装したのが始まりです。 その後、IEFT にて国際標準化となり現在に至ります。最新の IETF の Cookie の仕様は RFC 6265 です。 現在、IETF では RFC 6265 に置き換わる Cookie 仕様 Cookies: HTTP State Management Mechanism も策定しており、すでにその仕様の一部は使われています。

Cookie をブラウザーで確認するには

いつも知らず知らずのうちに仕込まれてしまう Cookie ですが、ブラウザーが食べてしまった Cookie を見ることが可能です。 まずは、ブラウザーにテスト用の Cookie を食べさせてみましょう。次のボタンを押してください。

Cookie を食べる

ブラウザーに保存された Cookie は、Chrome であればデベロッパーツールで確認することができます。 Chrome のデベロッパーツールは Ctrl + Shift + J で起動できます。 Microsoft Edge は F12 キーで同様のツールを表示できます。見た目はほぼ Chrome と同じです。

Chrome デベロッパーツール

Firefox は Ctrl + Shift + I でウェブ開発ツールを開きます。

Firefox ウェブ開発ツール

Cookie を CGI からセットするには

Cookie をセットするには、JavaScript でも可能ですが、ここでは Perl で作る CGI でどうすればいいかを解説します。 Cookie をブラウザーにセットするには、以下のような文字列をレスポンスヘッダーとしてブラウザーに返します。

Set-Cookie: nickname=Taro; domain=www.futomi.com; path=/lecture/cookie; expires=Mon, 04-Mar-2024 06:30:41 GMT; secure; HttpOnly

Perl スクリプトで出力する際には次のようになります。

print "Set-Cookie: nickname=Taro; domain=www.futomi.com; path=/lecture/cookie; expires=Mon, 04-Mar-2024 06:30:41 GMT; secure; HttpOnly\n"
print "Content-Type: text/html\n";
print "\n";
print "以下、HTML が続く\n";

基本的に次に示すような文法でサーバーはクライアントへレスポンスヘッダーを返します。

Set-Cookie: Cookie名=Cookie値; expires=有効期限; domain=ドメイン名 (サーバ名); path=パス; secure; HttpOnly

Cookie の名前と値は Set-Cookie ヘッダーの値の最初に = を間に入れてセットします。 値にはスペース、カンマ、セミコロンをそのまま含めるとはできません。 もし値にそれらの記号や日本語などを使いたい場合は URL エンコードをする必要があります (後述)。 Cookie をセットする際には、この名前と値のペアは必須です。

名前と値のペアの後ろにはいくつかの値が続いていますが、これらは「属性」と呼ばれ、いずれも必須ではなく、必要に応じて付け足します。 属性の詳細は次に説飯います。

Cookie 属性

Cookie の属性の意味は以下のとおりです。

属性 説明
expires

Cookie の有効期限をセットします。有効期限は GMT で指定します。フォーマットは以下のとおりです。

{Wdy}, {DD}-{Mon}-{YYYY} {HH}:{MM}:{SS} GMT

{Wdy} はアルファベット 3 文字で表す曜日、{DD} は数字 2 桁で表す日付、 {Mon} はアルファベット 3 文字で表す月、{YYYY} は数字 4 桁で表す西暦、 {HH}, {MM}, {SS} はそれぞれ時、分、秒を 2 桁数字で表したものです。

expires が省略されるとテンポラリー Cookie として扱われます。つまり、ブラウザーを閉じた時点で、その Cookie は無効となります (削除されます)。

max-age

Cookie の有効期限として現在からの相対時間を秒でセットします。 Cookie の有効期限を指定したいなら、expires 属性か max-age 属性のずれかをセットすれば良いのですが、 もしどちらも指定された場合は、max-age が優先されます。

domain

セットした Cookie が送信されるドメインを指定します。 domain が省略されると、そのときアクセスしたサーバー名がセットされます。

path

セットした Cookie が送信されるパスを指定します。 パスが省略されると、アクセスしたリソースを格納するディレクトリのパスがセットされます。

secure

この項目が指定されていると、ブラウザーは SSL/TLS の場合のみに Cookie を送信するようになります。 ドメイン、パスが一致したとしても、アクセス先が安全とみなされないとブラウザーはその Cookie を送信しません。

HttpOnly

Cookie のやり取りを HTTP に限定します。 分かりやすく言い換えると、JavaScript から Cookie にアクセスできないようにします。

Cookie 値の URL エンコード

Cookie 名と Cookie 値には、スペース、カンマ、セミコロンを含めてはいけません。 そのような文字が出現しないように URL エンコードする必要があります。 Perl スクリプトで URL エンコードするには以下のコードで実現できます。

my $value = "テスト";
$value =~ s/([^\w\=\& ])/'%' . unpack("H2", $1)/eg;
$value =~ tr/ /+/;
print $value; # %e3%83%86%e3%82%b9%e3%83%88

このコードを実行すると、%e3%83%86%e3%82%b9%e3%83%88 と出力されます。 なお、URL エンコードは、RFC 2396 で規定されています。

では、実際に日本語の Cookie をセットしてみましょう。次のボタンを押してください。

日本語の Cookie を食べる

この CGI のソースコードは次の通りです。

my $value = "たろう";
$value =~ s/([^\w\=\& ])/'%' . unpack("H2", $1)/eg;
$value =~ tr/ /+/;

print "Set-Cookie: nickname=${value}; secure; HttpOnly\n";
print "Content-Type: text/plain; charset=utf-8\n";
print "\n";
print "日本語の Cookie をセットしました。\n";
print "nickname=${value}";

Cookie を CGI で受け取る

ブラウザーから送信された Cookie を CGI で受け取るには、環境変数 $ENV{HTTP_COOKIE} を使います。 ブラウザーから送信された Cookie 情報はすべて $ENV{HTTP_COOKIE} に格納されますので、 この値を見れば Perl スクリプト内で、その値を使うことができるのです。

では、日本語の値を Cookie にセットしたうえで、その値を読み取ってみましょう。次のボタンを押してください。

日本語の Cookie を読み取る

この CGI のソースコードは次の通りです。

print "Content-Type: text/plain; charset=utf-8\n";
print "\n";
print $ENV{HTTP_COOKIE};

恐らく次のような結果が表示されたのではないでしょうか。

nickname=%e3%81%9f%e3%82%8d%e3%81%86; nickname=Taro

もしかしたら、もう少し長い文字列が表示されたかもしれません。 環境変数 $ENV{HTTP_COOKIE} には、セットされているすべての Cookie 情報が格納されます。 そして、それぞれは ; (セミコロン) で区切られています。

ご覧の通り、名前が nickname の Cookie が 2 つあります。 1 つは日本語の Cookie で domain 属性も path 属性も expire 属性も指定していないテンポラリー Cookie で、 もう一つは、このページの冒頭で食べた Cookie で、domain 属性も path 属性も expire 属性も指定した有効期限付き Cookie です。 Cookie は名前が同じであっても、domain 属性と path 属性が異なると別の Cookie として保存されます。 そのため、ここでは 2 つの Cookie が表示されるのです。

今回の実験では、Cookie 値が URL エンコードしてありますので、これをデコードしないと意味がわかりません。 次に URL エンコードされた Cookie 値を、デコードする方法を解説します。

Cookie 値の URL デコード

URL デコードは以下のコードで実現できます。

my $value = "%e3%83%86%e3%82%b9%e3%83%88";
$value =~ s/\+/ /g;
$value =~ s/%([0-9a-fA-F][0-9a-fA-F])/pack("C",hex($1))/eg;
print $value; # たろう

では、日本語の Cookie をもう一度読み取ってみましょう。次のボタンを押してください。

日本語の Cookie を読み取る

この CGI のソースコードは次の通りです。

print "Content-Type: text/plain; charset=utf-8\n";
print "\n";

# [名前=値] のペアに分離
my @pairs = split( /; /, $ENV{HTTP_COOKIE} );

for my $pair (@pairs) {

    # ペアから名前と値を分離
    my ( $name, $value ) = split( /=/, $pair );

    # 値を URL デコード
    $value =~ s/\+/ /g;
    $value =~ s/%([0-9a-fA-F][0-9a-fA-F])/pack("C",hex($1))/eg;

    # 結果を出力
    print "${name}: ${value}\n";
}

Cookie を削除する

CGI を使って、クライアント (ブラウザー) にセットされた Cookie を削除するにはどうすればいいのでしょうか。 実は HTTP レスポンスヘッダーには Cookie に関するものは Set-Cookie しかありません。 そのため、Cookie を削除するには有効期限を過去の時間にセットすることで、その Cookie を削除することができます。 過去の時間であればいつでも問題ありません。expires 属性は固定で次のような値を指定するのも良いでしょう。

expires=Thu, 01-Jan-1970 00:00:00 GMT

では、Cookie を削除してみましょう。次のボタンを押してください。

Cookie を削除する

この CGI のソースコードは次の通りです。

print "Set-Cookie: nickname=Taro; domain=www.futomi.com; path=/lecture/cookie; expires=Thu, 01-Jan-1970 00:00:00 GMT\n";
print "Content-Type: text/plain; charset=utf-8\n";
print "\n";
print "Cookie を削除しました。";

Cookie を削除する際には、Cookie 値は何でも構いません。 また、domain 属性と path 属性は、セットしたときと同じ値をセットしてください。

まとめ

Cookie の基礎を解説してきましたが、いざ Perl で Cookie を扱うコードを書くと非常に面倒なことが分かります。 できれば、楽に Cookie を扱いたいものです。 Perl にはさまざまな Cookie 関連の Perl モジュールが存在しますが、次頁では代表的な Perl モジュール CGI::Cookie の使い方について解説します。