今日からCGIプログラマー
《第2回》HDDの全jpgを表示させてみる
今すぐ作れる高度なCGI


スライドショーCGI
 お・ま・た・せ。
 連載第二回である。
 誰も待ってないって?後悔しまっせ、ダンナ。

 いきなりではあるが、今回はHDDの全jpgを次々とスライドショー表示していく
という恐ろしいCGIである。
 「Cドライブ」「Dドライブ」など、表示させたいドライブを変数に登録しておくだ
けで、CGIにURLアクセスすると、サーバ・マシンのjpgファイルを次々表示し
ていく。
 何が恐ろしいって、特にCドライブの「……Temporary Internet
 Files/Content.IE5……」なんてフォルダ以下には、ブラウザで表
示させたページのJPGがわんさと入っているのが恐ろしいではないか。

 そのCGIが、リスト1のjpg.plである。
 これを、「EUCコード+LF」の改行で、「c:\perl\cgi」などのcg
i-binディレクトリに保存する。「sub reload」は、くれぐれも1行で
入力すること。
 この状態で、Webサーバを起動してから、ブラウザのURL欄に、「http:/
/localhost/jpg.pl?auto=2&md=1」などと入力(「au
to」以降は、省略可)してリターンすれば、しばら〜く考え込み、jpgをリストア
ップしたあと、一気に二秒間隔でjpgを表示し続ける。
 「auto=2」は、ページめくりの間隔(秒)である。autoに何も入れないと
、「0.5」が指定される。また、二回目からは、「md=2」としてアクセスすれば
、以前のリストファイルを流用するので、考え込まずに、いきなりJPGの表示が始ま
る。
 起動すると、うーん。やはり、テンポラリには、恐ろしいjpgファイルが残ってい
るものだ。毎朝、こそこそと工学社のHPをチェックしている私のライフ・スタイルが
白日の元に晒されてしまった!


<私が第四章を書いたムックの情報をチェックしていたことが、バレバレ!>

 もちろん、ビューワのスライドショーほどには快適ではないが、たったこれだけのス
クリプトで、(モデムのポートを開けば)ネットにつながったPCなら、どこからでも
表示させることができるのだ。
 CGIは、自分のためのミニプログラムであると同時に、容易にスタンドアロンの壁
を、飛び越える。

 ……と、いきなり、高度なCGIを扱うようだが、これは、本連載の目標を明確にし
たいためだ。このスクリプトをよく見れば明らかであるが、いくつかのテクニックは使
っているものの、プログラム作りにはテクニックよりも、機転が大切だということであ
る。


cgiの流れ
 このスクリプトには、内部的に、モード1〜3までの動作モードがある。

■全体の流れ
@もし、モード2でリスト・ファイルが存在しない場合は、モード1にする
Aモード1なら、jpgファイルをリストアップ
Bモード2なら、「<IMG src」タグで、モード3のjpg画像を張り付
けて表示し、一定時間でリロードして次のページに移る。
Cモード3なら、目的のjpgファイルを、バイナリ・データとして、ブラウザに渡す
。

●モード1
 リスト作成。jpgファイルのリストを「c:\tmp\jpglist.txt」
に作成。一行目に、全jpgファイルの総数、二行目以降は、jpgファイルのパスと
ファイル名がリスト化される。
 リスト・ファイルを作るモードなので、本来、ブラウザに何かを表示させる必要はな
いのだが、「何らかの表示」をすることがcgiの基本動作であるため、空のページを
表示させている。
 また、これだけでは意味がないので、そのまま、モード2に自動的に移行するタグ「
META HTTP-EQUIV="refresh"……」を吐き出している。

●モード2
 HTML表示。「jpgno=xx」で、ブラウザに渡された行番号のjpgを表示
する。また、全○ファイル中の○番目であるのかや、ファイル名なども表示される。
 次のページに自動的に移行するように、「META HTTP-EQUIV="re
fresh"……」を表示させている。
 また、このページから、モード3のjpg画像を呼び出している(「<IMG
 src=jpg.pl?jpg=$jpg\&md=3 width=640>
;」)。画像の幅は640で固定。$jpgという変数には、jpgファイルのパスと
ファイル名が、URLエンコードして入っている。

●モード3
 jpgバイナリ出力。「jpg=xxx」で渡された変数をURLデコードし、たと
えば、「c:/WINNT/Web/Wallpaper/フライ フィッシング.j
pg」などという名称に戻して、そのファイルをバイナリ出力している。

・●一つのCGIにまとめる
 このように、本来まったく違う、3つのcgiを、1つにまとめている。
 「リスト作成」「HTML表示」「jpgバイナリ出力」という動作が異なるcgi
を、md=1〜3という変数をブラウザから渡すことで、使い分けているだけなのだ。

 原理的なことだけいうと、1つのサイトをまるごと1つのCGIに全部埋め込むこと
は可能なのである。もちろん、そうすることに意味はないし、快適な動作という面で考
えても、小さいプログラム、小さいデータに分けるほうが有利である。

 しかし、いくつものプログラムや機能を、一つにまとめるのは、cgiを作るツボで
あることを覚えておこう。


初心者Tips
 初心者向けに、CGI特有の基本的な手法を紹介する。今回のソースと照らし合わせ
てほしい。

●sub lprint
 Windows+perlcgiでHTML出力するのは、ソースをEUCで書いて
、HTMLをs-jis出力するのが、最もお勧めである。s-jisが有利なのは、
IEなどのブラウザから、ソースを調べて、CGIの動作を把握しやすいからだ。
 「sub lprint」は、$lineに、溜めたeucを、sjis変換して表
示する。

●ヘッダ・フィールド
 HTML出力するとき、これからcgiが出力するデータが、HTML/Textや
、あるいは、jpgやgifだと、データの種類をブラウザに報せるために、最初に、
ヘッダ・フィールドを表示(出力)する必要がある。

HTML/Text…Content-type: text/html\n\n
jpg…………Content-type: image/jpg\n\n
gif…………Content-type: image/gif\n\n

 CGI作成に慣れてきたら、上記をIMEに登録するツワモノが後を絶たないと聞く
。気持ちは分かる。てか、私もしている(笑)。

●CGIへの変数の渡し方
 $ENV{'QUERY_STRING'}という変数には、ブラウザ越しにユーザ
から渡される変数が、ひとかたまりで入っている。これを変数に分解するのが「sub
 get」である。
 たとえば、「http://xxx.co.jp/cgi/xxx.cgi?a=4
&b=3&c=5」と呼ばれたcgiからこのルーチンを実行すると、「$q{a}」
という変数に「4」、「$q{b}」に「3」が入る。
これで、普通に変数として扱えるようになる。
 
●<IN>の使い方
 「$allcount = (<IN>);」という行がある。これは
あまりやられないようだが、要するに、データ・ファイルの1行目の情報(だけ)を、
「$allcount」に入れている。

<IN>を使ったcgiの例
open (IN, "< c:/file.txt");
$stock = (<IN>);
while (<IN>){print $_}

 こんなスクリプトがあったとすると、「c:/file.txt」の最初の1行目だ
けは$stockに入り、2行目以降は表示される。ちょっとしたデータを渡すのに便
利なテクニックだ。

●再帰的にファイルをリスト化
 「sub saiki_filelist」は、再帰的に指定したフォルダより下の
ファイルを全部見ていく方法である。コツは、自分自身「&saiki_fileli
st」を呼び出す部分。

●urlエンコード・デコード
 ブラウザのURL欄には、原則として全角文字を入力できない。「sub urle
ncode」と「sub urldecode」は、たとえば、「ひまわり」を「%a
4%d2%a4%de%a4%ef%a4%ea」のように、ブラウザやサーバが受け
付けるデータにエンコードしたり、元にデコードしてくれる。

*
 本スクリプトを使うと、実に意外なjpgが表示される。こんなトリッキーなプログ
ラムも1ページあまりのコードを書けば実現できる。CGI自作道、ヤル気が出てきた
だろうか?

HP「今日からCGIプログラマー(http://www.geocities.c
o.jp/SiliconValley-Cupertino/6103/)」
(辻豊史)
 
■リスト1 スライドショーCGI(jpg.pl)
#! C:\Perl\bin\perl.exe
require "./jcode.pl";#jcode.plを読み込む
#---代入する変数
@root = ("c:/","d:/");#jpg表示したいディレクトリリスト
$list = "c:/tmp/jpglist.txt";#全jpgリスト(テキストファイル)
$deftime = 0.5;#連続してリロードするデフォルトタイム
#---変数解釈 $q{md} $q{jpgno} $q{auto} $q{jpg} $allcount
&get;($q{jpgno} == 0) && ($q{jpgno} = 1);($q{md} == 0) && ($q{md} = 1);
($q{auto} eq "") && ($q{auto} = $deftime);
($q{auto} < 0) && ($q{auto} = 0);
if ($q{md} == 2) { open (IN , "< $list");$allcount = (<IN>);
  chomp($allcount);}
#---モード1($q{md}=1)・リスト作成
if (($q{md} ==2) && (!(-e $list))) {$q{md} = 1; }
if ($q{md} == 1 ) {&head;foreach (@root) {&saiki_filelist($_);}
  $allcount = $#dflist;$allcount++;
  open (OUT, "> $list");print OUT "$allcount\n";
  foreach (@dflist){print OUT "$_\n";}
  close (OUT);$q{md} = 2;&reload;print "</BODY></HTML>";exit;}
#---モード2($q{md}=2)・画像表示
if ($q{md} == 2 ) {&head;$n = 0;$q{jpgno}++;
  ($q{jpgno} > $allcount) && ($q{jpgno} = 1);
  ($q{jpgno} < 1) && ($q{jpgno} = 1);
  while (<IN>) { chomp;$n++;if ($q{jpgno} > $n) {next;}
    $jpg = &urlencode($_);
    print "$q{jpgno} / $allcount<BR>$_<BR>
    <IMG src=$ENV{'SCRIPT_NAME'}\?jpg=$jpg\&md=3 width=640>";&reload;
    print "</BODY></HTML>";exit;} }
#---モード3・リクエストのあったjpgをバイナリ出力する
$jpg = &urldecode($q{jpg});print "Content-type: image/jpg\n\n";
open (IN,"< $jpg");binmode (IN);binmode (STDOUT);
while (read (IN,$i,1)) {print $i; } close (IN);exit;
#---サブルーチン
sub head { $line ="Content-type: text/html\n\n<!DOCTYPE HTML 
  PUBLIC \"-//W3C//DTD HTML   4.0  Transitional//EN\"><HTML><HEAD>
    <META http-equiv=Content-Type content=\"text/html; charset=x-sjis\">
    <TITLE>jpg表示</TITLE></HEAD>"; &lprint; }
#---sjis表示
sub lprint { &jcode'convert(*line,sjis);print $line;}
#---再帰的ファイルリストjpgファイルのみ
sub saiki_filelist {local($dir) = @_;
  local *DIR; opendir(DIR,$dir) or die $!;local ($file,$dirfile) ;
  while($_ = readdir(DIR)){next if $_ =~ m/^\.\.$/;next if $_ =~ m/^\.$/;
    $file = $_;$dirfile = "$dir/$file";$dirfile =~ s/\/\//\//g;
    if( -d $dirfile){ &saiki_filelist($dirfile); }
    else{ 
      if ($dirfile =~ /\.jpg$/) {push (@dflist,$dirfile);}
    } } closedir(DIR);}
#---QUERY_STRINGを変数に分解する
sub get {@get = split(/&/, $ENV{'QUERY_STRING'});
  foreach (@get) { ($x ,$y) = split(/=/, $_);$q{"$x"} = $y;} }
#---このCGI自身を$q{auto}秒でリロードする
sub reload {
  print "<META HTTP-EQUIV=\"refresh\" CONTENT=\"$q{auto};URL=$ENV{'SCRIPT_NAME'}\?md=$q{md}\&auto=$q{auto}\&jpgno=$q{jpgno}\">";} #←改行しないこと
#---urlエンコード
sub urlencode { local($value) = @_;
  $value =~ s/([^\w ])/sprintf("%%%02X", ord($1))/eg;
  $value =~ tr/ /+/;return($value);}
#---urlデコード
sub urldecode {local ($url) = @_; $url =~tr/+/ /;
  $url =~s/%(..)/pack("C", hex($1))/ge;return ($url);}