【 Perl 編 】

1. 特記
2. 共通処理
3. bbs.cgi と global.cgi
4. 設定ファイルの読み込みを global.cgi に実装
5. ログ表示の実装
6. ページジャンプ用のリンク作成
7. ログ書き込みの実装
8. 過去ログ処理の実装
9. ロック処理の実装

  • Perl は他の言語と比べると特殊な表現が多く、Perl のみに依存する記述方法が多く見られますので
    他言語への移行や学習を考慮する為、そのような記述はできるだけ避けるようにします

  • グローバル変数は、全て大文字にします
  • ユーザ関数名は、単語の先頭を大文字にします
  • ローカル変数は全て小文字にします


  • Perl 特有の処理
  • ここでは、開発の実現の為に Perl のみに依存する処理を行なう事になり
    ますが、本来プログラマが意識せずに利用する外部ファイルとして実装し
    ます ( global.cgi )

  • そういった目的をふまえて、共通関数もここで記述します。共通関数は、
    標準化の1部であり、開発を効率よくかつ品質を向上する為に用いられる
    ものです


  •   概要
    1
    http ヘッダの出力
    2
    入力データの取得
    3
    デバッグ情報出力関数
    4
    文字列出力関数


  • 言語によって、デフォルトの文字列出力は関数であったり、ステートメントであったりメソッドであったり
    しますが、統一した関数名で標準化します


  • bbs.cgi
  • require './global.cgi';
    require './model.cgi';
    $LOG_VIEW = "";
    $PAGE_LINK = "";
     
    if ($ENV{'REQUEST_METHOD'} eq "POST") {
    	WriteLog();
    }
     
    require './head.cgi';
    require './input.cgi';
     
    LoadLog();
    Out( $LOG_VIEW );
     
    require './foot.cgi';
    
  • head.cgi と input.cgi と foot.cgi は、基本的に HTML タグのみを記述します。その為に、
    ヒアドキュメントを使用しています
  • global.cgi
  • print "Content-Type: text/html; Charset=Shift_JIS\n";
    print "Expires: Wed, 31 May 2000 14:59:58 GMT\n";
    print "\n";
     
    $GET_BUFFER = $ENV{'QUERY_STRING'};
    %GET = ();
    foreach( split(/&/, $GET_BUFFER) ) {
    	($KEY, $VALUE) = split(/=/);
    	$VALUE =~ tr/+/ /;
    	$VALUE =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
    	$GET{$KEY} = $VALUE
    }
     
    if ($ENV{'REQUEST_METHOD'} eq "POST") {
    	read( STDIN, $POST_BUFFER, $ENV{'CONTENT_LENGTH'} );
    	%POST = ();
    	foreach( split(/&/, $POST_BUFFER) ) {
    		($KEY, $VALUE) = split(/=/);
    		$VALUE =~ tr/+/ /;
    		$VALUE =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
    		$POST{$KEY} = $VALUE;
    	}
    }
     
    # **********************************************************
    # デバッグ情報出力
    # **********************************************************
    sub DispDebug
    {
    	print "<TABLE border=0><TR><TD valign=top>";
    	print "<TABLE border=0 cellspacing=1 bgcolor=black>";
    	print "<TR><TH bgcolor=silver colspan=2>GET</TH></TR>";
    	print "<TR><TH bgcolor=silver>Key</TH><TH bgcolor=silver>Value</TH></TR>";
    	while ( ($KEY,$VALUE) = each %GET ) {
    		print "<TR>";
    		print "<TD bgcolor=white>$KEY</TD><TD bgcolor=white>$VALUE</TD>";
    		print "</TR>";
    	}
    	print "</TABLE>";
     
    	print "</TD><TD valign=top>";
     
    	print "<TABLE border=0 cellspacing=1 bgcolor=black>";
    	print "<TR><TH bgcolor=silver colspan=2>POST</TH></TR>";
    	print "<TR><TH bgcolor=silver>Key</TH><TH bgcolor=silver>Value</TH></TR>";
    	while ( ($KEY,$VALUE) = each %POST ) {
    		print "<TR>";
    		print "<TD bgcolor=white>$KEY</TD><TD bgcolor=white>$VALUE</TD>";
    		print "</TR>";
    	}
    	print "</TABLE>";
    	print "</TD></TR></TABLE>";
    }
     
    # **********************************************************
    # 文字列出力
    # **********************************************************
    sub Out
    {
    	local($value) = @_;
    	print $value;
    }
     
    # **********************************************************
    # 改行付き文字列出力
    # **********************************************************
    sub OutCr
    {
    	local($value) = @_;
    	print $value . "\n";
    }
     
    # **********************************************************
    # 配列にファイルの行を全て読み込む
    # **********************************************************
    sub GetFile
    {
    	local($path) = @_;
     
    	open( IN_FILE, $path );
    	local(@file) = <IN_FILE>;
    	close( IN_FILE );
     
    	return @file;
    }
     
    # **********************************************************
    # ファイルの存在チェック
    # **********************************************************
    sub FileExist
    {
    	local($path) = @_;
     
    	if ( !(-e $path) ) {
    		return 0;
    	}
     
    	return 1;
    }
     
    # **********************************************************
    # 配列の範囲指定の書き込み
    # **********************************************************
    sub WriteArray
    {
    	local($handle,$start,$end,@data) = @_;
    	local($i);
     
    	for( $i = $start; $i <= $end; $i++ ) {
    		print $handle $data[$i];
    	}
     
    }
     
    1;
    

  • 設定ファイルの処理は、広域な標準化ではありませんが、掲示板というシステムとして考えれば
    共通の領域になります

  • 設定ファイルから読み込んだ情報を、%GET へ格納します
  • GetIni();
     
    # **********************************************************
    # 設定ファイルの読み込み
    # **********************************************************
    sub GetIni
    {
    	local($i,$target,@ini);
     
    	local(@file) = GetFile( "./bbs.ini" );
     
    	for( $i = 0; $i <= $#file; $i++ ) {
     
    		# 改行削除
    		$target = $file[$i];
    		chomp( $target );
     
    		# 空行は無視
    		if ( $target eq "" ) {
    			next;
    		}
     
    		# コメント文字
    		if ( substr( $target, 0, 1 ) eq ";" ) {
    			next;
    		}
     
    		# 行を区切り文字で分解
    		@ini = split(/=/, $target );
     
    		$GET{$ini[0]} = $ini[1];
    	 
    	}
    }
    

  • ログ表示は、掲示板としてもっとも重要な部分であり、アプリケーションとしては「問い合せ」の分類に入
    ります。これだけでもアプリケーションとして有用性が存在します

  • 通常、アプリケーションの開発では、入力処理と表示処理は別々である事があります。そのような場合
    データは、手動で作成されます。この場合も、テキストエディタで6件のデータを作成します
  • 日付,ユーザ,タイトル,本文1<br>改行
    日付,ユーザ,タイトル,本文2<br>改行
    日付,ユーザ,タイトル,本文3<br>改行
    日付,ユーザ,タイトル,本文4<br>改行
    日付,ユーザ,タイトル,本文5<br>改行
    日付,ユーザ,タイトル,本文6<br>改行
    
  • model.cgi
  • # **********************************************************
    # ログ表示
    # **********************************************************
    sub LoadLog
    {
    	# ログファイルが存在しない場合はなにもしない
    	if ( !FileExist( "./$GET{'INI_LOGFILE'}") ) {
    		return;
    	}
     
    	# 初期表示対応
    	if ( $GET{'page'} eq "" ) {
    		$GET{'page'} = 1;
    	}
     
    	# ログデータを配列として読み込み
    	local(@file) = GetFile( "./$GET{'INI_LOGFILE'}" );
     
    	# ページあたりの行数
    	local($row_par_page) = $GET{'INI_ROWPERPAGE'};
     
    	local($i,$page,@row);
    	for( $i = 0; $i <= $#file; $i++ ) {
     
    		# ログの表示データ作成 ( 自ページ分のみ )
    		if ( int($i / $row_par_page) + 1 == $GET{'page'} ) {
    			# 行を区切り文字で分解
    			@row = split( ",", $file[$i] );
     
    			# 表示データの作成
    			$LOG_VIEW .= "$row[0] $row[1] $row[2]<BR>";
    			$LOG_VIEW .= "$row[3]";
    			$LOG_VIEW .= "<HR size=1 color=silver>";
    		}
     
    	}
     
    }
    

  • ジャンプ先は、ページの数 - 1 件必要になります。ログ表示で全行を処理するループがあるのでその中
    に記述します
  • # ページリンクの作成
    if ( ( $i % $row_par_page ) == 0 ) {
    	$page = int($i / $row_par_page) + 1;
    	# 自ページの場合
    	if ( $page == $GET{'page'} ) {
    		$PAGE_LINK .= "$page&nbsp;&nbsp;";
    	}
    	# 他ページの場合
    	else {
    		$PAGE_LINK .= "<A href" . "='$ENV{'SCRIPT_NAME'}";
    		$PAGE_LINK .= "?page=$page'>$page</A>&nbsp;&nbsp;";
    	}
    }
    
  • $PAGE_LINK は、foot.cgi のヒアドキュメントに埋め込まれています

  • print <<FOOT;
    $PAGE_LINK
    </BODY>
    </HTML>
     
    FOOT
     
    DispDebug();
    1;
    

  • ログの書き込みは、単純に書き込むだけにします。過去ログへの最終データの移動は、過去ログ処理
    に任せる事にして、必ず OPEN した時には 最大行数 - 1 件以下のデータになるようにします

  • 読み込まれた改行コードはそのまま利用しています
  • # **********************************************************
    # ログ書き込み
    # **********************************************************
    sub WriteLog
    {
     
    	# ログデータを配列として読み込み
    	local(@file) = GetFile( "./$GET{'INI_LOGFILE'}" );
     
    	# ログファイルを書き込みモードでオープン
    	open( LOG_FILE,">./$GET{'INI_LOGFILE'}");
     
    	# 日付フォーマット
    	local(($sec,$min,$hour,$mday,$mon,$year)) = localtime(time);
    	local($str_date) = sprintf("%04d/%02d/%02d-%02d:%02d:%02d",
    			$year+1900,$mon+1,$mday,$hour,$min,$sec);
     
    	# カンマのコンバート
    	local($user_name) = $POST{'UserName'};
    	$user_name	=~  s/,/&#44;/g;
    	local($title) = $POST{'Title'};
    	$title	 	=~  s/,/&#44;/g;
    	local($message) = $POST{'Message'};
    	$message 	=~  s/,/&#44;/g;
     
    	# その他のコンバート
    	$message	=~ s/</&lt;/g;
    	$message	=~ s/>/&gt;/g;
    	$user_name	=~ s/</&lt;/g;
    	$user_name	=~ s/>/&gt;/g;
    	$title		=~ s/</&lt;/g;
    	$title		=~ s/>/&gt;/g;
     
    	# 改行のコンバート
    	$message	=~ s/\r//g;
    	$message	=~ s/\n/<BR>/g;
     
    	# POST されたデータの書き込み
    	print LOG_FILE "$str_date,$user_name,$title,$message\n";
     
    	WriteArray( LOG_FILE, 0, $#file, @file );
     
    	close(LOG_FILE);
     
    }
    

  • 過去ログは、カレントログで MAX を超えた行を履歴として残し、通常別のインターフェイスで表示する
    ためのデータです

  • 過去ログファイルは、1ファイルあたりの MAX 行が決められ、その件数を超えれば新しい過去ログフ
    ァイルが作成されて使用されます。古い過去ログのデータは変更される事無く、直接メンテナンスしな
    い限り行の順序は変わる事はありません

  • 過去ログ処理のトリガは、カレントログのデータが、MAX 行の状態での投稿です。まず、カレントログを
    書き込む前に、そのチェックを行なって MAX 行に達していたら、最終行を変数に保存してカレントロ
    グを MAX - 1 件の状態にしておきます

  • WritePastLog は、WriteLog の先頭でコールして下さい

  • # **********************************************************
    # 過去ログ処理
    # **********************************************************
    sub WritePastLog
    {
     
    	local($move_row);
     
    	# ログデータを配列として読み込み
    	local(@file) = GetFile( "./$GET{'INI_LOGFILE'}" );
     
    	# MAX 行数に達していたら、最終行を取得
    	if ( $#file + 1 >= $GET{'INI_MAXROW'} ) {
    		$move_row = $file[$#file];
    	}
    	else {
    		return;
    	}
     
    	# 最終行以外を書き込む
    	open( LOG_FILE,">./$GET{'INI_LOGFILE'}" );
    	WriteArray( LOG_FILE, 0, $#file-1, @file );
    	close(LOG_FILE);
     
    }
    
  • この後に、仕様編のフローをコードに直して実装します
  • 	# 過去ログディレクトリが存在しない場合は作成
    	if ( !FileExist( "./$GET{'INI_PASTDIR'}") ) {
    		mkdir( "./$GET{'INI_PASTDIR'}" );
    		# 過去ログファイルを作成
    		local(($sec,$min,$hour,$mday,$mon,$year)) = localtime(time);
    		local($past_name) = sprintf("%04d%02d%02d%02d%02d%02d",
    				$year+1900,$mon+1,$mday,$hour,$min,$sec);
    		open( LOG_FILE,">./$GET{'INI_PASTDIR'}/$past_name.csv" );
    		close(LOG_FILE);
    	}
     
    	# 過去ログディレクトリの最新ファイル名を取得
    	opendir( DIR, "./$GET{'INI_PASTDIR'}" );
     
    	local(@arr_file) = ();
    	local($count) = 0;
    	local($filename);
    	while( $filename = readdir(DIR) ) {
    		if ( $filename ne '.' && $filename ne '..' ) {
    			$arr_file[$count] = $filename;
    			$count++;
    		}
    	}
    	closedir(DIR);
     
    	@arr_file2 = reverse( @arr_file );
    	local($cur_past) = "./$GET{'INI_PASTDIR'}/$arr_file2[0]";
     
    	# 過去ログデータを配列として読み込み
    	local(@file) = GetFile( $cur_past );
     
    	# MAX 行になっていたら、新しいファイルを作成
    	if ( $#file + 1 >= $GET{'INI_PASTMAXROW'} ) {
    		local(($sec,$min,$hour,$mday,$mon,$year)) = localtime(time);
    		local($past_name) = sprintf("%04d%02d%02d%02d%02d%02d",
    				$year+1900,$mon+1,$mday,$hour,$min,$sec);
    		$cur_past = "./$GET{'INI_PASTDIR'}/$past_name.csv";
    		open( LOG_FILE, $cur_past );
    		close(LOG_FILE);
    	}
     
    	# 過去ログデータを配列として読み込み
    	local(@file) = GetFile( $cur_past );
     
    	# ログファイルを書き込みモードでオープン
    	open( LOG_FILE,">$cur_past" );
     
    	# カレントログのはみ出したデータの書き込み
    	print LOG_FILE $move_row;
     
    	# 元々のデータを書き込み
    	WriteArray( LOG_FILE, 0, $#file, @file );
     
    	close(LOG_FILE);
    

  • ロック処理はファイルの書き込みの前に行い、解除は書き込みの後に行います

  • もしロックされていた場合は、現在の投稿データを再表示してエラーメッセージを出力します
  • # ***********************************************************
    # ロック関数
    # ***********************************************************
    sub LockByDir
    {
     
    	local($lock) = "./$GET{'INI_LOCKDIR'}";
    	local($lockcnt) = $GET{'INI_LOCKCNT'};
     
    	local($ret) = 1;
    	if ( FileExist( $lock ) ) {
    		local(@astamp) = stat($lock);
    		local($laststamp) = $astamp[9];
    		if ( $laststamp < time - 60 ) {
    			rmdir( $lock );		# 古いディレクトリの削除
    		}
    	}
     
    	local($cnt) = 0;
     
    	while( 1 ) {
    		if ( mkdir( $lock ) ) {
    			last;	# 成功
    		}
    		$cnt++;
    		if ( $cnt > $lockcnt ) {
    			$ret = 0;	# 失敗
    			last;
    		}
    		sleep(1);
    	}
     
    	return $ret;
     
    }
    
  • bbs.cgi の変更
  • require './global.cgi';
    require './model.cgi';
    $LOG_VIEW = "";
    $PAGE_LINK = "";
     
    if ($ENV{'REQUEST_METHOD'} eq "POST") {
    	if ( LockByDir() ) {
    		WriteLog();
    		rmdir("./$GET{'INI_LOCKDIR'}");
    	}
    	else {
    		# input.cgi に埋め込まれた変数にセット
    		$USER_NAME = $POST{'UserName'};
    		$TITLE = $POST{'Title'};
    		$MESSAGE = $POST{'Message'};
    		Out( "ロックされています<br>" );
    	}
    }
     
    require './head.cgi';
    require './input.cgi';
     
    LoadLog();
    Out( $LOG_VIEW );
     
    require './foot.cgi';