今日から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);}