綾小路龍之介の素人思考

Perl > Perl実験室でWeb雑考

このページでは、Webを漁りながら思ったことを書き留めていきます。

目次

imgタグのsrc属性に画像ファイルへのLocationヘッダを出力するcgiを指定する

よくあるアクセス解析は画像ファイルを読み込んで、Content-Type宣言とともに標準出力。ならばLocationヘッダで画像ファイルに飛ばせないかと考えた。まぁ世の中には賢い人がいるもので、この技術はすでにPerlで書かれた各種ダウンロードカウンタで上手くいくことが実証済みだった。要はアクセス解析なんてのは訪問者の知らないところで画像をダウンロードさせることでカウントしているわけ。ダウンロードカウンタの場合は訪問者のダウンロード要求を待ってダウンロードさせてるわけ。これを使えば別にわざわざcgiが画像ファイルを読み込んで云々する必要なんてどこにもないじゃないか。

もっと言ってしまえば、cgiの使えるサーバでなおかつLocationヘッダも出力できるなら、aタグのhref属性、cssの外部ファイル、JavaScriptの外部ファイル、とかもLocationヘッダを出力するcgiへのリンクにして、とにかく訪問者が自分のWebページ内で何をやったか全てを記録できてしまうんじゃないかと考えた。まぁここは実験室なんでそんなこともいいかなと。動作確認は取れていないけれどこんなところだろう。

open OUT,">>access.log";
print OUT join'',map{"$_ $ENV{$_}\n"}sort keys %ENV;
close OUT;
my %u = (
  'Gif'        => 'test_gif.gif',
  'Index'      => 'test_html.html',
  'JavaScript' => 'test_js.js',
  'CSS'        => 'test_css.css'
);
if (defined $ARGV[0] && defined $u{$ARGV[0]}){
  print "Location: $u{$ARGV[0]}";
} else {
  print "Location: $u{Index}";
}

結論から言えば、上のスクリプトは自分の環境(isweb,Windows Me,Internet Exprorer 6.0)のもとでは期待通りの挙動を示す。実際にこのサイトにあるいくつかのファイルは上のようなアクセスログ取得cgiを噛ませてブラウザに取得させている(上と全く同じではない)。言い換えれば、あるHTMLファイルに中に上の4つのファイルへのリンクがあった場合、これらをcgiファイルと対応した引数で書き換えることで、元のHTMLファイルをブラウザで開いた場合と全く同じように見えるということだ。これが効果的なものかどうかを検証せねばならない。アクセスログが取れるという点を除けば全く効果的ではない。100個のHTMLファイルに同じスタイルシートを適応した場合、このスタイルシートに向けてリンクが張られる。スタイルを変更する場合は現在適応されているスタイルシートを直接編集すればよいのであって、わざわざ上のようにして変更するメリットはないだろう。これは、JavaScriptや画像ファイルについても同様である。

上のようなスクリプトを介して実際のコンテンツに訪問者を誘導することは、HTMLファイルに含まれるimgタグ、scriptタグ、metaタグ、のような訪問者の許可無しにリンク先の内容をダウンロードさせるタグの回数だけcgiが呼び出されることになる。サーバーの仕事は2倍になるわけで、特にHTTP/1.0のConection: closeでコンテンツをダウンロードさせるようなブラウザを使っている場合、サーバに高負荷をかけることになるかもしれない

それでもなおメリットを探すとすれば、多少の取りこぼしはあるものの、サーバに記録されるのと同じようなアクセスログが取れるという点だろう。まともな解析を行えば、画像、JavaScript、スタイルシート、これらを訪問者がどのように処理しているかもおおよそのところわかる。例えば、あるHTMLファイル中に含まれるリンク(aタグのhref属性にはcgiを指定)を辿って別のHTMLファイルに移動し、移動先のHTMLファイルに画像(imgタグ)、JavaScript(scriptタグ)、スタイルシート(metaタグ)、をcgiを指定して仕込んでおいた場合、非常に短い時間間隔で同一のIPアドレスから4回cgiにアクセスがあれば、訪問者は画像、JavaScript、スタイルシート、を全て許可していることになる。

iswebが提供しているアクセス解析のログ保存期間は確か3ヶ月程度だと思ったが、このcgiスクリプトを噛ませると、ディスクスペースの許す限りアクセスログの保存ができる。上の例では環境変数をログファイルに保存するだけだが、例えばここでApacheのアクセスログと同じフォーマットを適応させれば、世の中に数多く存在するApacheのアクセス解析ソフトにアクセスログを読み込ませることができる。

cgiからtextコンテンツを出力するにはContent-Typeヘッダを操作

適当なContent-Typeヘッダを吐くcgiを作ることでtextコンテンツをcgiから出力できる。例えば、cgiからcssを出力する場合、Content-Typeを指定しなければならない。例えば、サーバーが適切にcssを処理できる場合、telnetを使ってしかるべき方法でcssファイルを取得すると、cssに対応したMIMEタイプがわかる(Content-Typeから始まる1行がhttpヘッダに付けられている。)。

telnet za.toypark.in 80
GET /cgi-bin/link2/test_css.css HTTP/1.0
Host: za.toypark.in
HTTP/1.1 200 OK
Date: Thu, 23 Feb 2006 16:50:19 GMT
Server: Apache
Last-Modified: Thu, 23 Feb 2006 08:36:45 GMT
ETag: "c69b1b-d8-43fd741d"
Accept-Ranges: bytes
Content-Length: 216
Connection: close
Content-Type: text/css
*{margin:0px;padding:0px;border:1px solid red;white-space: normal;}
body{background-color:Black;}
br{z-index:10;white-space:nowrap;letter-spacing: 0;clear: left; display:inline;position:absolute; top:0px; left:0px;}

ちなみに上のように入力しないと目的のコンテンツは得られない。というのも、共有サーバ上のデータなのでhostが必須となのである(あぁバーチャルホストの悲しさ)。試しにhost無しでGETを実行するとlocationヘッダとともに301が返される。加えて接続するホストをinfoseek.co.jpやwww.infoseek.co.jpに変えると、Host込みでgetを実行しても目的のコンテンツは得られない。また、infoseek.co.jpやwww.infoseek.co.jpはHTTP/1.0を返す。

URLエンコードと文字コードのお話GiRLFRieND

Googleトップページをtelnetで取る方法

どうやらGoogleの認めたUser-Agentを与えないと正しく取れないようだ。結構昔の話。今はどうなってるのかわからん

telnet google.co.jp 80
GET / HTTP/1.1
Host: www.google.co.jp
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows 98; Win 9x 4.90)
telnet google.co.jp 80
GET http://www.google.co.jp/ HTTP/1.0
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows 98; Win 9x 4.90)
telnet za.toypark.in 80
GET / HTTP/1.1
Host: za.toypark.in

Jcode.pmで出力文字コードを引数で渡せないというのは本当か。

多分、半分本当で半分嘘だと思う。無料版iswebはJcode.pmを標準で実装していないので(Jcodeは設置済みでした(Jcode.pm v2.06)。use Jcode;で使えます。)Jcode.pm0.88をおいて実験してみたところ、次のようなことがわかった。1、出力文字コードは文字列または文字列を内容として持つスカラ変数で渡さねばならない。2、配列要素として渡すとエラーが生じる。つまり、下のように書いた場合は思ったとおりの結果が得られる。

my $str = '文字コード変換テスト';
my $OutCode = 'utf8';
print Jcode->new($str)->h2z->$OutCode;

ただし、次のように書いた場合は思い通りの結果が得られない($strをutf8に変換してくれない。)。エラーが生じる。

my $str = '文字コード変換テスト';
my $OutCode[0] = 'utf8';
print Jcode->new($str)->h2z->$OutCode[0];

例えば、引数を2つ要求する文字コード変換プログラムで、第1引数に変改対象のファイル名、第2引数に出力文字コード、がセットされているとする。このとき、下のようなコードを書いてみると上手くいかない。最初のセクションは引数の存在チェックと存在しない場合の初期値の設定である。第1引数がセットされていればこれを変換対象のファイルとし、そうでない場合は自分自身。第2引数がセットされていればこれを出力文字コードとし、そうでない場合はutf8。2番目のセクションで変換対象ファイルからデータをもらい$strに格納。3番目のセクションで文字コード変換を行って出力。

$ARGV[0] = defined $ARGV[0] ? $ARGV[0] : $0;
$ARGV[1] = defined $ARGV[1] ? $ARGV[1] : 'utf8';
open IN,$ARGV[0];
my $str = join'',<IN>;
close IN;
print "Content-Type: text/html\n\n";
print Jcode->new($str)->h2z->$ARGV[1];

この場合最後の行でエラーが生じる。これを回避するには前述のようにutf8などの出力文字コードを含む変数をスカラ変数として、配列要素とすることが肝心である。例えば以下のように書き換えれば思ったとおりに動く。これの欠点をあげるとするならば、メモリ上に余分な変数を作ることだろう。たった1回しか使わない$codeを全く同じ内容をもつ$ARGV[1]のコピーとしてわざわざ再定義するは、無駄なメモリを消費することにはならないのだろうか。

my $name = defined $ARGV[0] ? $ARGV[0] : $0;
my $code = defined $ARGV[1] ? $ARGV[1] : 'utf8';
open IN,$name;
my $str = join'',<IN>;
close IN;
print "Content-Type: text/html\n\n";
print Jcode->new($str)->h2z->$code;

Jcode.pmが内部でどのように動いているかはよく知らないが、少なくとも上で述べた問題を解決する策を一つ知っている。それはjcode.plで使われていたインターフェイスを使うことだ。例えば下のように。すると自分で定義した変数は$strだけとなり、メモリを余分に消費することもないと思う(もちろん引数に何が含まれているかを覚えておかねばならないが。)。

$ARGV[0] = defined $ARGV[0] ? $ARGV[0] : $0;
$ARGV[1] = defined $ARGV[1] ? $ARGV[1] : 'utf8';
open IN,$ARGV[0];
my $str = join'',<IN>;
close IN;
print "Content-Type: text/html\n\n";
print Jcode::convert(\$str, $ARGV[1]);

エラートラップ

ダイヤモンド演算子の中身は文字列でなければならないというのは本当かな

ソーシャルブックマーク

  1. はてなブックマーク
  2. Google Bookmarks
  3. del.icio.us

ChangeLog

  1. Posted: 2003-11-14T20:23:24+09:00
  2. Modified: 2003-11-14T13:56:29+09:00
  3. Generated: 2017-07-04T23:09:28+09:00