週末には少しPerlを。

Perlスクリプトの学習日記です。

CGI::Sessionを使うとセッション情報はどのようにファイルに格納されるかを観察する

CGI::Sessionを使った簡単なCGIプログラムを書いてみる

入力フォーム hogesess.cgi に入力した内容を fugasess.cgi で表示することにして、 同時にその内容をセッション情報に格納します。 次回、直接に fugasess.cgi にアクセスしても同じ内容を表示したいとします。

hogesess.cgi は抜粋すると次のようなもの。

#!/usr/bin/perl

use strict;
use warnings;
use CGI;
use CGI::Session;

my $q = CGI->new;
my $session = CGI::Session->new($q)
    or die CGI::Session->errstr;

my $hogename = $session->param('hogename');

$session->expire('1m');

print $session->header;

print $q->start_html('hoge');
print $q->start_form(-method=>'POST', -action=>'fugasess.cgi');

print "hogename";
print "<br />";
print $q->textfield(
      -name => 'hogename',
      -value => $hogename,
      -size => 10,
      -maxlength => 30,
);
print "<br />";
print $q->submit(
    -name => 'hogesubmit',
    -value => 'hogesubmitvalue',
    );

print $q->end_form;
print $q->end_html;

テキストボックス1つとsubmitボタンだけのフォームです。 データを受け取る fugasess.cgi は以下の通り。

#!/usr/bin/perl

use strict;
use warnings;
use CGI;
use CGI::Session;
my $q = CGI->new;
my $session = CGI::Session->new($q)
    or die CGI::Session->errstr;

# $session->save_param(['hogename']);
my $hogename = $q->param('hogename');
if ($hogename) {
    $session->param('hogename', $hogename);
}
$session->expire('1m');

print $session->header;

print $q->start_html('hoge');

$hogename = $session->param('hogename');
print "hogename is $hogename";

print "<br />";
print $q->end_html;

CGI::Session::Turorialの記述によるとPOSTされたデータをセッション情報に格納する方法として save_paramメソッドを使うように書かれているのですが、どうもそれだけだと私のケースでは 直接にfugasess.cgiにアクセスしたときにInternal Server Errorとなるのが格好悪いように思えて避けました。

/tmpに保存されたセッション情報の中身を覗く

環境にも依るのかもしれませんが、デフォルトで/tmpの下に 「cgisess_(セッションID)」のファイル名でセッション情報を格納するファイルが作成されます。 中身は次のようなものでした。

$D = {
'_SESSION_ETIME' => 60,
'_SESSION_ID' => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
'_SESSION_ATIME' => 1370160147,
'hogename' => 'perltest',
'_SESSION_REMOTE_ADDR' => 'aaa.bbb.ccc.ddd',
'_SESSION_CTIME' => 1370160131
};;$D

Expireされたあと

expireされたあとはどうなるかと思って1分という短い時間を指定してみました。 期待通り、1分以上経過したあとに fugasess.cgiにアクセスしたところ 以前の結果を表示せず新規のセッション扱いとなりましたが、 旧いセッション情報のファイルは /tmpに残ったままとなりました。 なにかしら掃除する仕組みが必要なようです。

CGIプログラムでcookieに大きなサイズの情報を詰め込む実験をしてみる

HTTP cookieとは

Wikipedia によると

RFC 6265などで定義されたHTTPにおけるウェブサーバとウェブブラウザ間で状態を管理する
プロトコル、またそこで用いられるウェブブラウザに保存された情報のこと

おそらく日常で無意識的に使っているモノのだと思うのですが私は恥ずかしながら技術の詳細を理解せず今日まで来ています。

さて、クッキーに保存できる情報の大きさは当然ながら有限でありまして RFC HTTP 6265 State Management Mechanism の中の 6.1. Limits によれば、

   Practical user agent implementations have limits on the number and
   size of cookies that they can store.  General-use user agents SHOULD
   provide each of the following minimum capabilities:

   o  At least 4096 bytes per cookie (as measured by the sum of the
      length of the cookie's name, value, and attributes).

   o  At least 50 cookies per domain.

   o  At least 3000 cookies total.

しかしながら実際のところ、どこまで大きな情報を格納できるものかをひとつ自分のプログラムで試してみようと思った次第です。

Firefoxでのcookieの確認方法

格納可能な情報サイズはおそらくブラウザによっても異なるはずですが、今回はFirefox Ver.21で試します。 クッキーを確認する方法は、

  1. メニューバーから「ツール」-「ページの情報」をクリック。
  2. ダイアログウィンドウの「セキュリティ」-「プライバシーと履歴」に「Cookieを表示」ボタンがあります。

CGI.pmのcookie()

CGI.pmには cookieを扱うためのメソッド が準備されておりまして、これを利用してこんなCGIを設置してみました

#!/usr/bin/perl

use strict;
use warnings;
use CGI;

my $q = CGI->new;
my $ck = $q->cookie(
    -name => 'z',
    -value => 'c' x 4095,
     );

print $q->header(-cookie => $ck);

print $q->start_html('cookie test');
print "This is cookie test.";
print $q->end_html;

このCGIにFirefoxでアクセスして前記の方法で確認すると、たしかに「z」という名前で「c」の行列を見ることができます。

複数のcookieを格納する

「At least 50 cookies per domain」とのRFC文書の記載から、次のように変更してみました。

my @cks;
for (1..50) {
    push @cks, $q->cookie(-name => sprintf("c%03d", $_), -value => 'c' x 4092);
}
print $q->header(-cookie => \@cks);

名前が3バイト増加した分、valueの大きさを減らして4092にしています。 このCGIにFirefoxでアクセスして確認したところ、たしかに50個のcookieが保存されていました。

ではさらに多くのcookieをとループの部分を0~999までにしたところ、 Firefoxではc850~c999までの150個のみが確認できました。

サーバー側のtcpdumpで850番より小さな番号のcookieもヘッダに入っていることを確認できましたので どうやらFirefox側で廃棄しているようです。

まとめ

RFC文書に曰く「Servers SHOULD use as few and as small cookies as possible」 とのことであります。

mod_rewriteで出来るApache2のURLリダイレクトを敢えてmod_perl2で行う

元ネタ

YAPC::Asia Tokyo 2012 の発表資料 「モダンmod_perl入門」 を最近読みまして、その中で紹介されていた内容を実際に試した備忘録をここに記します。

mod_rewirte

mod_rewirteはリダイレクトを実現するApacheの拡張モジュールで、以下の公式ドキュメントで解説されています。

例えばhttpd.confに以下のように書きます。

LoadModule rewrite_module modules/mod_rewrite.so
RewriteEngine on
RewriteRule ^/foo\.html$ /bar.html [PT]
RewriteLog /var/log/httpd/rewrite.log
RewriteLogLevel 3

これでfoo.htmlへのアクセスがbar.htmlにリダイレクトされます。 その様子はrewirte.logファイルにログとして書き出されます。

mod_perl2では

PerlTransHandlerというフェーズに変換処理をはさみます。httpd.confに次のように書きます。

LoadModule perl_module modules/mod_perl.so
PerlRequire /var/www/perl/startup.pl
PerlTransHandler MyApache2::Trans

ここでstartup.plは単にパスの設定のみのコードです。

use lib qw(/var/www/perl);
1;

これで/var/www/perl/MyApache2/Trans.pmを実行できるようになります。

#!/usr/bin/perl

package MyApache2::Trans;

use strict;
use warnings;

use Apache2::RequestRec (); # for $r->uri()
use Apache2::Const -compile => qw(DECLINED);

sub handler {
    my $r = shift;
    my $uri = $r->uri(); # e.g. "/path/to/foo.html
    ### modify uri
    $uri =~ s/foo\.html$/bar.html/;

    $r->uri($uri); # set uri

    return Apache2::Const::DECLINED;
}

1;

参考文献

Perlモジュールのバージョンを確認するイロイロな方法

元ネタ

Google先生に尋ねたところ2010年のadvent calendarで モジュールのバージョンを確認するときどうやってますか? にイロイロ紹介されていましたので、いくつか自分で試してみました。

VERSION変数をprintする

$ perl -MDBI -le 'print $DBI::VERSION'
1.625

もっともシンプルな方法でしょうか。

pmversを使う

# pmvers DBI
1.625

cpanm pmversでインストールするとpmtoolsというコマンド集のようなものが入ります。 pmversコマンド以外にも例えばpmallと打つとすべてのインストール済みモジュールがバージョンと共に表示されます。

巨大なバージョン番号でuseして意図的にエラーを表示させて確認する

# perl -e 'use DBI 9999'
DBI version 9999 required--this is only version 1.625 at -e line 1.
BEGIN failed--compilation aborted at -e line 1.

エラーメッセージの中に現在のバージョンが含まれていることを利用した方法。

Vモジュールを使う

# perl -MV=DBI
DBI
        /usr/local/lib64/perl5/DBI.pm: 1.625

「V.pm」はCPANからインストールしておきます。

perldoc -m

# perldoc -m DBI

じかにソースを出して$VERSIONを確認します。

結局どうするか

1つ2つのモジュールの確認だけならどうやってもよさそうに思いました。 頻繁に機会があるならpmtoosのコマンド群はインストールしておくと便利そうです。

ワンライナーで足し算したり時刻表示したりする

端折っていますが Famous Perl One-Liners の読書メモの3回めです。

足し算する

perl -MList::Util=sum -alne 'print sum @F'

List::Utilはコアモジュールなのでインストールの心配無用。 「-Mmodule=arg」は次と等価。

use module qw(arg)

-aスイッチの「auto-splitting」の結果、各入力行はフィールドに分割され配列@Fに格納されます。

シャッフルする

perl -MList::Util=shuffle -alne 'print "@{[shuffle @F]}"'

@{[ ]} は無名配列を作って直後にデリファレンスする。 ニックネームは「Baby cart」。

絶対値にする

perl -alne 'print "@{[map { abs } @F]}"'

時間に関するあれこれ

UNIX time を表示する

perl -le 'print time'

現在の日時を表示する。

perl -le 'print scalar localtime'

昨日の日時を表示する

perl -MPOSIX -le '@now = localtime; $now[3] -= 1; print scalar localtime mktime @now'

POSIXモジュールのmktimeは負の値もうまく捌いてくれるとのこと。

エスキモーの挨拶を知り、初めてPerlの秘密の演算子に触れる

ワンライナーでよく登場する奇妙なコードの書き方は「secret operator」と呼ばれニックネームがつけられて いるようです。

参考: CPANのperlsecret

実際には演算子でもなんでもない、そんな奇妙な書き方に困惑しつつ Famous Perl One-Liners の読書メモの2回めです。

行番号をつけてファイル出力

perl -pe '$_ = "$. $_"'

変数 $. は $INPUT_LINE_NUMBER, すなわち行番号。 表示に書式指定もできます。

perl -ne 'printf "%-5d %s", $., $_'

空行以外を数え上げる

perl -pe '$_ = ++$a." $_" if /./'

変数 $a は use strict していないので自動的に作成され ゼロで初期化されます。 「/./」は空行以外にマッチする正規表現。「.」は改行以外の任意の文字にマッチするので。

正規表現にマッチした行のみを数え表示する

perl -ne 'print ++$a." $_" if /regex/'

行数を表示する

perl -lne 'END { print $.}'

wc -l と打てばいいところを敢えてPerlで。 スイッチ -l はoutput record separatorを改行文字にします。

別解としてファイルを一度リストコンテキストで読み込んでから スカラーコンテキストで評価して表示する方法:

perl -le 'print $n=()=<>'

「=()=」の部分は goatse operator と呼ばれるそうで、 その名前の由来を画像検索してはならないとのこと。 perlsecretの記述をそのまま引用すると、

If you don't understand the name of this operator, consider yourself lucky. 
You are advised not to search the Internet for a visual explanation.

さらに別解。

perl -lne '}{ print $.'

「}{」は eskimo operator と呼ばれるそうです。 恥ずかしながら本当にそういう演算子があるのかと思ったことを ここに告白いたします。

実際にはこれは単に次と等価。

while (<>) {
}{
    print $.
}

その名前の由来について perlsecret から引用:

Ethnographic note: 
in modern Western culture, 
an eskimo kiss is the act of pressing the tip of one's nose 
against another's. 
It is loosely based on a traditional Inuit greeting called a kunik, 
that early explorers of the Arctic dubbed "Eskimo kissing" 
when they first witnessed it. 
The kunik itself is not a kiss, not erotic, 
and simply of form of affectionate greeting.

空行以外の行数を表示する

perl -le 'print scalar(grep {/./}<>)'

もう少し短くすると

perl -le 'print ~~ grep /./,<>'

ビット反転の「~」を2つ重ねた「~~」は 「Inchworm」と呼ばれる。

正規表現にマッチした行数を表示

perl -lne '$a++ if /regex/; END{print $a+0}'

数値として評価するための「+0」は金星に似ている姿から「Venus operator」と呼ばれるとのこと。

一行野郎(ワンライナー)でテキストファイルの行間に空行を入れたり削ったりする

元ネタ

ワンライナーでいろいろな事ができるようになるとカッコイイだろうなと 思いつつ手頃な教科書を探していたのですが、 "Perl One-Liners Explained" という記事が英文ですが読みやすかったので、 ここに第一章の覚書メモを残すものです。

行間に空行を入れるには

各行に改行を付加して出力する方法

perl -pe '$_ .= "\n"'

とすればよろしい。3行の間隔を開けたいなら"\n"x3とする。

-p スイッチは次のコードと等価になるとのこと。

while (<>) {
    # your program goes here
} continue {
    print or die "-p failed: $!\n";
}

別解として、$OUTPUT_RECORD_SEPARATORを変更する方法や、

perl -pe 'BEGIN {$\="\n"}'

行末を置換する方法もあります。

perl -pe 's/$/\n/'

空行は処理しないようにするには

perl -pe '$_ .= "\n" unless /^$/'

とすればよい。正規表現「^$」が空行にマッチする。 さらに空白文字のみの行も空行とみなすなら perl -pe '$_ .= "\n" if /\S/' とすればOK

各行の前に空行をいれたいときは

perl -pe 's//\n/'

とする。 置換パターンが空なのであらゆる位置にマッチし、 この場合最初の文字の前の位置にマッチして 結果として行の前に改行が挿入される。

行間の空行を削除したいときは

すべての空行を削除したいとき

perl -ne 'print unless /^$/'

-n スイッチは以下と等価。

LINE:
while (<>) {
    ...
}

2つ以上の連続した空行を1つだけにしたいときは

perl -00 -pe ''

スイッチ -00 によりパラグラフモードでファイルが読み込まれます。