Perl/CGI研究室 'PERL-LABO'

Perl/CGI研究室 'PERL-LABO' TOPへ
戻る(History.Back)

クッキー処理汎用関数の仕様変更

研究内容

クッキーの書き込み、読み込みを処理する汎用関数、ちょっと問題がありましたので 仕様変更します。

詳細

今まで

クッキーライブラリですが、次のようにしていました。

%cookie = plab::getcookie();
(%cookieを使ってなにか処理する)
plab::setcookie(1, %cookie);

このやり方、ちょっと問題があることに気がつきました。 というのは、CGIプログラム本体の中でのみクッキーの処理を行うのならこれでいいのですが、 なにか他のファイルの関数、つまり他のライブラリの関数の中でクッキーを処理したい場合に 問題が起こります。

例えば、ライブラリ関数Aがクッキーを使うとしましょう。 ライブラリ関数Aは plab::getcookie でクッキーを取得して、クッキーの内容を変更します。 さらに、ライブラリ関数Bもクッキーを使うとしましょう。 ライブラリ関数Bも plab::getcookie でクッキーを取得して、クッキーの内容を変更します。 さて、クッキーを書き込みには plab::setcookie を呼び出しますが、 これはHTTPヘッダを出力するときに行わなければいけません。 HTTPヘッダの出力はライブラリ関数の中ではなく、CGIプログラム本体で行う必要があります。 だって、ライブラリ関数Aとライブラリ関数Bがそれぞれ HTTPヘッダを出力したら、HTTPヘッダが2つになってしまいます。 なので、ライブラリ関数がHTTPヘッダを出力するっていうのは、 複数のライブラリ関数がクッキーを使うときのことを考えると、やらない方がいいっていうことになります。 ということは、ライブラリ関数Aが変更を加えたクッキーと、 ライブラリ関数Bが変更を加えたクッキーを、 CGIプログラム本体はそれぞれ受け取って、 なんかうまく処理して2つのクッキーを合わせて、 HTTPヘッダで出力するっていうのが必要になります。

これ、うまくいきません。 これをやるためには、クッキーを使用するライブラリ関数は、 保存しておきたいクッキーをCGIプログラム本体に返す必要があります。 それをCGIプログラムで受け取る。ここまではできないことは無さそうですが、 それがライブラリ関数A、B、C…とクッキーを使用する関数の数だけ起こるわけですし、 なんだか考えるとややこしく難しい感じです。 せっかく作ったクッキー処理関数ですが、このままでは、 CGIプログラム本体のみ、あるいは呼び出されるライブラリ関数の中で1箇所のみが クッキーを使用できるなんていうとんでもない制限がかかってしまいます。 これでは困ります。 複数のライブラリ関数が、好きな時にクッキーを参照、変更することができて、 それぞれのクッキーへの変更が正しく保存される、そんな風にクッキー処理ライブラリを 変更しないといけません。

結果

変更方法

それで、仕様変更です。 どうするかというと、クッキーを連想配列にして扱うっていうのは同じですが、 ライブラリ関数の引数とか戻り値とかでその連想配列の受け渡しをするのではなく、 どこからでも参照できる、唯一のクッキー連想配列というものを用意して、 それを通してクッキーを処理します。 次のような感じです。

(関数A)
plab::readcookie();
(%plab::Cookieを使ってなにか処理する)

(関数B)
plab::readcookie();
(%plab::Cookieを使ってなにか処理する)

(CGIプログラム本体、HTTPヘッダを出力するところで)
plab::writecookie(-1);

仕様が変わったことを分かりやすくするために関数名を変えました。 (get → read、set → write。) readcookie 関数はクッキーを連想配列に入れて返すのではなく、 plabパッケージ内の連想配列 Cookie にクッキーを入れるという処理をします。 plabパッケージ内の連想配列 Cookie は、 普通の関数の中の変数と違って、plabパッケージ内で1つだけあって、 どこからでも参照できます。local でない変数です。 こういう変数は今まで使ったことがなかったのですが、 今回のような、どこで参照したくなるか分からないような変数は、 こうするのがいいようです。 そういう特殊な変数なので、今まで使ってきた local な変数と区別するために、 変数名の1文字目を大文字にしています。 plabパッケージ以外の場所で使用するときは %plab::Cookie と書きます。 関数呼び出しと同じような形ですね。 readcookie 関数はクッキーを連想配列にして Cookie に入れます。 複数回呼び出された場合は、2回目以降の呼び出しは無視します。 これは、1回目の呼び出し以降に連想配列 cookie に変更が加えられた場合に、 それを無効にしてしまわないようにです。 そして、writecookie 関数によってHTTPヘッダを出力します。 引数に連想配列を与える必要はありません。 また、ついでに writecookie 関数の引数を -1 とした場合は 有効期限を10年とするようにしましょう。 これはとても長い有効期限を設定したいときにそれが何時間にあたるのか 計算しなくてもいいように。

作成したCGIプログラム

writecookietest.cgi (クッキー書き込みテスト)
#!/usr/bin/perl

require 'cookie.pl';

cookieA();
cookieB();

plab::writecookie(0);

print "Content-type: text/html\n";
print "\n";
print "書き込みました。";


sub cookieA
{
	plab::readcookie();
	$plab::Cookie{'keyA'} = "cookieA";
}

sub cookieB
{
	plab::readcookie();
	$plab::Cookie{'keyB'} = "cookieB";
}
readcookietest.cgi (クッキー読み込みテスト)
#!/usr/bin/perl

require 'cookie.pl';

plab::readcookie();

print "Content-type: text/html\n";
print "\n";

foreach (keys(%plab::Cookie)) {
	print "$_ = $plab::Cookie{$_}<br>"; 
}
cookie.pl (クッキー処理関数ライブラリ)
# v 1.01

package plab;

# 使い方
#   最初に plab::readcookie を呼ぶ
#   連想配列 %plab::cookie でクッキーにアクセス
#   HTTPヘッダ出力時に plab::writecookie() を呼ぶ
# 注意
#   <> を含むデータは扱えません。

%Cookie;  	# plabパッケージグローバルハッシュ
$Cookie_read;	# readcookieが呼ばれたら1

# クッキーの読み込み
sub readcookie
{
	local $cookiestring;
	local(@data, @ks, $nks);
	local($n, $i);

	# 上書き読み込みを禁止する
	if ($Cookie_read == 1) {
		return;
	}
	$Cookie_read = 1;

	$cookiestring = $ENV{"HTTP_COOKIE"};	
	@data         = split('<>', $cookiestring);

	$n = @data;
	for ($i = 0; $i < $n; $i += 2) {
		$Cookie{$data[$i]} = $data[$i + 1];
	}
}

# クッキーHTTPヘッダ出力
# 引数 (有効時間)
# 有効期限は経過時間で与えます。
# 0のときは有効期限設定無しになります。
# -1のときは10年になります。
# HTTPヘッダを出力するのでHTTPヘッダ出力の最初などに呼びます。
sub writecookie
{
	local($n, $i);
	local $expire_delta_hour;
	local($expires, @t, @m, @w);
	local @key;
	local $ncookie;

	$expire_delta_hour = $_[0];
	if ($expire_delta_hour == -1) {
		$expire_delta_hour = 24*365*10;  # 10 years
	}

	# 有効期限文字列の作成
	if ($expire_delta_hour != 0) {
		@t = gmtime(time() + $expire_delta_hour*60*60);
		@m = ('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec');
		@w = ('Sun','Mon','Tue','Wed','Thu','Fri','Sat');
		$expires = sprintf(" expires=%s, %02d-%s-%04d %02d:%02d:%02d GMT;",
				$w[$t[6]], $t[3], $m[$t[4]], $t[5]+1900, $t[2], $t[1], $t[0]);
	}

	# クッキーを出力します
	# キー<>データ<>キー<>データ… という形になります
	print "Set-Cookie: ";
	@key = keys(%Cookie);
	$ncookie = @key;		
	$i = 0;
	foreach (@key) {
		print "$_<>$Cookie{$_}";
		++$i;
		if ($i != $ncookie) {
			print "<>";
		}		
	}
	print ";$expires\n";
}

1;

実行結果

クッキーを書き込みます。
クッキーを読み込みます。

考察

動きました

無事、動きました。 これで、クッキーをあちこちの関数の中で使いたい場合でもOKなクッキー処理ライブラリとなりました。 HTTPヘッダ出力時に plab::writecookie を呼び出すことを忘れなければ 便利に使えると思います。

今回の変更はかなり大きなものです。 なにしろ、今までの cookie.pl とはまったく互換がありません (^^; 本来はこれくらい大きな仕様変更があったら、 ファイル名を変えるとかしないと、新旧の cookie.pl が混ざって混乱してしまうと思うのですが、 まだ発展途上ですし、最初のうちはこういうこともあるっていうことで、 ファイル名などはそのままにしています。 その代わり、pl ファイルの1行目にバージョン番号を入れることにしました。 せめてこのバージョン番号で、ファイルの新旧を管理しようと…。 今回、v 1.01 ですが、変更の大きさから言ったら v 2.00 でもいいくらいですね (^^;

これで完成?

クッキー処理ライブラリはこれで完成かな? …と前回も思ったです (^^; まだまだ知らないこと、分からないことばかりですから、 また変更が必要になるかも知れませんね。

Perl/CGI研究室 'PERL-LABO' TOPへ
戻る(History.Back)

Copyright (c) 'PERL-LABO' All Rights Reserved.  リンクフリーです。