Web アプリケーションを作る上では欠かせない Cookie。 本コーナーでは Cookie の仕組みを詳しく説明し、Perl スクリプトでの実装方法を簡単に解説していきます。
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 は、Chrome であればデベロッパーツールで確認することができます。 Chrome のデベロッパーツールは Ctrl + Shift + J で起動できます。 Microsoft Edge は F12 キーで同様のツールを表示できます。見た目はほぼ Chrome と同じです。
Firefox は Ctrl + Shift + I でウェブ開発ツールを開きます。
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 の属性の意味は以下のとおりです。
属性 | 説明 |
---|---|
expires |
Cookie の有効期限をセットします。有効期限は GMT で指定します。フォーマットは以下のとおりです。
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 名と 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 をセットしてみましょう。次のボタンを押してください。
この 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 で受け取るには、環境変数 $ENV{HTTP_COOKIE}
を使います。
ブラウザーから送信された Cookie 情報はすべて $ENV{HTTP_COOKIE}
に格納されますので、
この値を見れば Perl スクリプト内で、その値を使うことができるのです。
では、日本語の値を 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 値を、デコードする方法を解説します。
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 をもう一度読み取ってみましょう。次のボタンを押してください。
この 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";
}
CGI を使って、クライアント (ブラウザー) にセットされた Cookie を削除するにはどうすればいいのでしょうか。
実は HTTP レスポンスヘッダーには Cookie に関するものは Set-Cookie
しかありません。
そのため、Cookie を削除するには有効期限を過去の時間にセットすることで、その Cookie を削除することができます。
過去の時間であればいつでも問題ありません。expires 属性は固定で次のような値を指定するのも良いでしょう。
expires=Thu, 01-Jan-1970 00:00:00 GMT
では、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 の使い方について解説します。