***HPクリエイターのためのCGI講座 第45回***
ここでは、クッキーについて学びます。
クッキーとは、ユーザーに固有な情報を、ユーザー側のコンピュータに記憶させる仕組みです。
ここの受講者は、HP作成の経験者ですから、どのように使われているかは、大体ご存知ですよね。
<クッキーへの書き出し>
ブラウザへ次のように出力します。
print "Set-Cookie: 〜 ";
〜のところには、次のフォーマットで記述します。
名前=値; expires=クッキーの有効期限(国際標準時刻で設定);
名前 | 自由に記述します。 |
値 | 自由に記述します。 |
expires | これはキーワードです。このまま記述。 |
クッキーの有効期限 | ユーザー側のコンピュータに記録されるCookieの有効期限を指定します。次のようなフォーマットで指定します。
Sunday, 28-May-2000 10:10:10 GMT |
クッキーの書き出しは、最初に行ってください。
(例)
print "Set-Cookie: C_BBS=aaabbbccc;
expires=Sunday, 28-May-2000 10:05:02 GMT
";
print "Content-type: text/html
";
print "<html> ";
:
:
上記例の場合 |
print "Set-Cookie: C_BBS=aaabbbccc; expires=Sunday, 28-May-2000 10:05:02 GMT "; |
クッキーは2000年5月28日の10時5分2秒まで、保存される 記憶されるクッキーは C_BBS=aaabbbccc; |
<クッキーからの読み出し>
環境変数HTTP_COOKIEにクッキーの内容が設定されています。
例のようにクッキーを書き込まれていた場合、$ENV{'HTTP_COOKIE'}には、次のテキストが格納されています。
C_BBS=aaabbbccc;
例示プログラムとして、投稿者の名前と、e-mailアドレスをクッキーに記憶する掲示板を作成しました。
この掲示板では、まだまだ力不足ですが、掲示板としての最低限の仕事はするでしょう。
<ログファイルのフォーマット>
この掲示板のログファイルに格納されているメッセージは、<>を区切り記号にして、名前、e-mail、タイトル、メッセージの順に書かれています。
(1メッセージの例)
alk<>alk@manabitai.com<>タイトル<>投稿内容
<ヒアドキュメント>
今回のCGIプログラムでは、HTML文章の作成にprint文の特殊な形、ヒアドキュメントを使っています。
ヒアドキュメントの説明はここを参照してください
<プログラムの説明>
require '../../../cgi/jcode.pl';
日本語コード変換プログラムの場所を指定
$lock_file = "../../../cgi/c_bbs.lock";
多重アクセス禁止用ロックファイル名
***********各種設定************
$top_title = 'BBS'; # BBSのタイトル
$msg_log = 'msg_log.txt'; # メッセージログファイル名
$log_num = 10; # メッセージの格納数(1ページの表示数も兼ねる)
$msg_max = 500; # 1回の最大メッセージ数(半角文字数で指定)
#処理のスタート
&in_para; # 入力パラメータの取得
入力パラメータ取得用サブルーチンをコール。
入力パラメータを受け取り、デコードします。
&get_cook;
クッキーの読み込み
if ($in_btn ne ""){ # ボタンが押されて、このCGIが起動された
&log_write; # ログファイルへメッセージの書込み
}
前回の掲示板プログラムと同様に、このCGIが起動される要因として、2種類あります。
1.オープニング時に呼び出される
「サンプルはここ」と記述された「ここ」をクリックした場合
2.掲示板の書込みボタンを押した場合
書込みボタンが押された場合は、 $out に "書込み" の文字列が格納されています。
書込みボタンが押されていない場合は、 $out には何も格納されていません。
このCGIプログラムの起動要因を、 $out に文字列が設定されているか否かで判定しています。
********ここからは、HTMLテキストをブラウザへ出力*********
print "Content-type: text/html\n\n"; # HTML文をブラウザへ出力
print <<"HTML";
ヒアドキュメントのはじまり
<html><head>
<title>BBS</title></head>
<center><font color="00ff00" size="6">
<b>$top_title</b></font></center><hr size=2>
<FORM method="POST" action="c_bbs.cgi">
名前<INPUT size="40" type="text" name="name" value="$c_name"><BR>
eメール<INPUT size="40" type="text" name="mail" value="$c_mail"><BR>
タイトル<INPUT size="40" type="text" name="title"><BR>
<BR>
<TEXTAREA rows="10" cols="60" name="msg"></TEXTAREA><INPUT type="submit" name="out" value="書 込 み"></FORM>
FORMタグ
methodはPOSTで、CGI(easy_bbs.cgi)を呼び出す
テキスト入力領域の大きさは、40文字*15行
CGIプログラムの受け取る、テキスト入力パラメータの名前はmsg
書込みボタンの名前はout
<hr size=2>
HTML
ヒアドキュメントの終わり
open(LOG,"<$msg_log") || &error(0); # ロググファイルの読込みモードでのオープン
ログファイル(発言内容が記述されている)を読み込みモードでオープン
@log_buff = <LOG>; # ログファイルから全て、読み出し
ログファイルの内容を一度に配列@log_buffへ読み込む。
このように、記述すると、配列の各要素に一行ずつ文字列が読み込まれます。
log_buff[0]・・・・1行目の文字列
log_buff[1]・・・・2行目の文字列
log_buff[2]・・・・3行目の文字列
:
:
close(LOG);
ログファイルのクローズ(読み込みが終了したので)
foreach (@log_buff){ # $_へ1行ずつ取り出す
#ファイルには<>を区切り記号として、名前、e-mail、タイトル、メッセージの順で格納されている
($out_name,$out_mail,$out_title,$out_msg) = split(/<>/);
$out_name・・・名前を格納
$out_mail・・・メールアドレスを格納
$out_title・・・タイトルを格納
$out_msg・・・投稿メッセージを格納
# 取り出した1行を編集して表示
print "題名:$out_title名前:<a href=\"mailto:$out_mail\">$out_name</a><br><br>";
print "$out_msg<br>";
print "<hr size=2>\n"; # 区切り用ラインの表示
}
print "</html>\n";
exit;
ここからは、入力パラメータ取得サブルーチン
sub in_para {
if ($ENV{'CONTENT_LENGTH'} > $msg_max){
METHODがPOSTなので、環境変数CONTENT_LENGTHに文字列が格納されています。
その値をチェックし、500文字以上はエラーとします。
&error(0); # 入力メッセージの最大長を500バイト(半角500文字)とする
}
read(STDIN, $in_buffer, $ENV{'CONTENT_LENGTH'}); # POSTなのでパラメータを標準入力から読み出す
標準入力STDINから、パラメータを取得します
@pairs = split(/&/,$in_buffer); # パラメータのペアを切り出す
パラメータは &
で区切って送られてきます。
配列@pairsの各要素に &
で区切った文字列を格納します。
foreach $pair (@pairs) {
($name, $value) = split(/=/, $pair); # パラメータを名前と値に分解
各パラメータは 名前=値 の形をしています。
それぞれを$nameと$valueへ格納します。
$value =~ tr/+/ /; # コードのデコード
変換規則に従って+はブランクに置換
$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
16進文字列を、数値に置換
&jcode'convert(*value,'sjis'); # S-JIS変換
得られた文字列を、S-JISに変換
$value =~ s/\"/"/g; # タグ文字の変換
特殊文字の変換
"(ダブルクォテーション)は"に置換
$value =~ s/</</g;
< は< に置換
$value =~ s/>/>/g;
> は> に置換
if($name eq "name"){
現在のパラメータの処理がname(名前)の場合
$in_name = $value; # 名前
}
elsif($name eq "title"){
現在のパラメータの処理がtitle(タイトル)の場合
$in_title = $value; # タイトル
}
elsif($name eq "mail"){
現在のパラメータの処理がmail(メールアドレス)の場合
$in_mail = $value; # e-mailアドレス
}
elsif($name eq "msg"){ # 入力メッセージ
現在のパラメータの処理がmsg(投稿記事)の場合
$in_msg = $value;
記事を$msgに格納します。
現在のパラメータの処理がmsg(投稿記事)の場合は改行コードを<br>に置換します。
(改行コードには、次の3種類がある)
$in_msg =~ s/\r\n/<br>/g; # リターンコードを<br>に置換
$in_msg =~ s/\r/<br>/g; # "
$in_msg =~ s/\n/<br>/g; # "
}
elsif($name eq "out"){
現在のパラメータの処理が書込みボタンの場合
$outに文字列(書込み)を格納します。
$in_btn = $value; # ボタンに表示している文字が格納される 今回の場合は「書込み」
}
}
}
ここからは、投稿記事のログファイルへの書込み処理
sub log_write {
&lock; # ロック処理
多重アクセス排他処理
open(LOG,"<$msg_log") || &error(1); # ログファイルを読込みモードでオープン
ログファイルを読み込みモードでオープンします。
@log_buff = <LOG>; # 全メッセージの読み出し
ログファイルの内容を@log_buffへ読み出します
close(LOG);
ログファイルのクローズ
if ($log_num <= @log_buff){
ログファイルへの最大格納メッセージ数は$log_num(10件)です。
現在のログファイルに格納されているメッセージ数が10件なので
pop(@log_buff); # メッセージが$log_numを超えれば、一番古いメッセージを削除する
一番古いメッセージを配列@log_buffから pop 命令を使って、削除します。
}
unshift (@log_buff,"$in_name<>$in_mail<>$in_title<>$in_msg\n");
今、取り込んだメッセージを unshift 命令を使って配列@log_buffの先頭に格納します。
open(LOG,">msg_log.txt") || &error(1); # ログファイルを書込みモードでオープン
ログファイルを書込みモードでオープン
print LOG @log_buff; # メッセージのログファイルへ書込み
配列@log_buffをログファイルへ書き出します。
close(LOG);
ログファイルをクローズします。
if (-e $lock_file){
unlink($lock_file); # ロックの解除
}
&put_cook; # クッキーへの書き出し
}
ここからはクッキーへの書込み処理
sub put_cook {
# クッキーの有効期限のために
($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = gmtime(time + 10*24*60*60);
標準時刻を読み込みます。
年月日時分秒の設定
日時分秒が一桁の場合は、前に 0 を付けて2文字にします。
$year += 1900; #年は1900年からの年数なので、1900を足す
if ($sec < 10){$sec = "0$sec";} # 前に0を付ける
if ($min < 10){$min = "0$min";}
if ($hour < 10){$hour = "0$hour";
if ($mday < 10){$mday = "0$mday";
$mon = ('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec') [$mon];
月を、英字で取得
$youbi = ('Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday') [$wday];
曜日を、英字で取得
$cook_gmt = "$youbi, $mday\-$mon\-$year $hour:$min:$sec GMT";
クッキー設定時間を格納
<例>
2000/8/9(水) 15:47:5の場合
Wednesday,09-Aug-2000
15:47:05 GMT
$cook_txt="name\:$in_name\,mail\:$in_mail";
$cook_txtに名前とメールアドレスを格納します。
<例>
$cook_txtには次のように格納される
name:ALK.mail:alk@manabitai.com
print "Set-Cookie: C_BBS=$cook_txt; expires=$cook_gmt\n";
クッキーへの書込み
<例>
Set-Cookie: C_BBS=name:ALK.mail:alk@manabitai.com;
expires=Wednesday,09-Aug-2000 15:47:05 GMT
}
ここからはクッキーからの読み出し処理
sub get_cook {
@cook_paras = split(/\;/,$ENV{'HTTP_COOKIE'});
クッキーは環境変数 $ENV{'HTTP_COOKIE'} に格納されます。
また、クッキーは;(セミコロン)で区切られたテキストで入ってきます。
今回は、クッキーへの書込み処理を見てもらえれば分かるように1回だけです。
$cook_paras[0]には次のような文字列が格納されてきます。
C_BBS=name:ALK.mail:alk@manabitai.com
foreach $cook_para (@cook_paras) {
$cook_paras[0] を $cook_para に取り出します。
local($name, $value) = split(/\=/, $cook_para);
C_BBS=name:ALK.mail:alk@manabitai.comを次のように分解します。
$name・・・C_BBS
$value・・・name:ALK.mail:alk@manabitai.com
$name =~ s/ //g;
$nameのブランクを取り除きます。
これは、処理系によってはブランクが入る可能性があるための処理
if($name eq 'C_BBS'){ # C_BBSはこのBBS用クッキーである
$nameが’C_BBS'であれば、今回の演習のBBSが書き込んだクッキーです。
@cook_paras = split(/\,/,$value);
パラメータは,(カンマ)で区切られているので、カンマ分解して、配列@cook_parsに設定します。
<例>
$cook_pars[0]・・・name:ALK
$cook_pars[1]・・・mail:alk@manabitai.com
foreach $cook_para (@cook_paras) {
パラメータをひとつずつ$cook_parに取り出します
local($name, $value) = split(/\:/, $cook_para); #クッキーのデコード
ひとつのパラメータは:(コロン)で区切られた形をしているので、コロンで分解します。
<例>
$cook_parsに文字列 name:ALK が設定されていた場合
$name・・・name
$value・・・ALK
if($name eq 'name'){
$nameが name の場合
$c_name = $value; # 名前
$c_nameに$value設定されている名前を格納
}
elsif($name eq 'mail'){
$nameが mail の場合
$c_mail = $value; # e-mailアドレス
$c_mailに$value設定されているマールアドレスを格納
}
}
last;
C_BBSの処理が終われば終了です。
}
}
}
sub lock{ # ロック処理
$retry = 5;
while (!symlink(".", $lock_file)){
if (--$retry <= 0){
&error(0);
}
sleep(1);
}
}
sub error{ # エラー処理
if((-e $lock_file) && ($_[0] != 0)){
unlink($lock_file);
}
print "Content-type: text/html\n\n";
print "<html>\n<body bgcolor=# FF0000>\n";
print "<center><h3>ERROR</h3>\n";
print "</html>\n";
exit;
}
********************************************************************************
講師:ALK alk@arkland.co.jp
運営:アークランド(株) http://www.arkland.co.jp