17. ファイルアップロード
今日はファイルアップロードについて、ご説明いたします。
■ファイルアップロードの概要
ユーザから、画像などのファイルをアップロードして保存したい場合などが
あるかと思います。Perlを使用すれば、こういった場合でもブラウザから
ユーザのローカルマシンに入っているファイルをサーバへアップロードして
もらうことができます。
■HTMLの記述
まず、ファイルアップロードをしてもらうためのページを作成します。
ファイルアップロードをするには、通常のフォームではできません。
<form>タグのenctypeに、multipart/form-dataを指定し、ファイル名を指定する
ボックスを用意します。
-----------------------------------------------------------------
<HTML>
<BODY>
<FORM action="upload.cgi" enctype="multipart/form-data" method="post">
ファイル: <INPUT type="file" name="upload_file" size="32"><BR>
<INPUT type="submit" value="送信">
</FORM>
</BODY>
</HTML>
-----------------------------------------------------------------
enctypeに、multipart/form-dataを指定しない場合は、テキストを送るという
ことになりますが、上記のように記述すると、テキスト以外のバイナリファイル
をアップロードできます。
<input>タグのtypeにfileを指定すると、ファイルの参照ボタンの付いたテキスト
ボックスが表示されます。これにより、ユーザが自分のローカルマシンのファイルを
選んで、アップロードすることができます。
また、ファイルをアップロードしてもらう場合は、必ずPOST送信にしてください。
■受け取り側の処理
アップロードされたファイルを受け取る処理をご説明します。
Perlでファイルを受け取るには、CGI.pmというパッケージを使用すると楽です。
CGI.pmパッケージは、Perlのインストール時にライブラリに入っているので、
特にインストールは必要ないと思います。
-----------------------------------------------------------------
use CGI;
$query = new CGI;
-----------------------------------------------------------------
上記のように、まずnewをして、パッケージを使用できる状態にします。
次に、ファイル名を取得します。
-----------------------------------------------------------------
# ファイル名の取得
$filename = $query->param('upload_file');
-----------------------------------------------------------------
upload_fileという文字列は、前のHTMLで<input>タグで指定したnameになります。
次に、ファイル読み込みと同じ要領で、バイナリ形式で読み込みます。
-----------------------------------------------------------------
# ファイルの受け取り
while($bytesread = read($filename, $buffer, 2048)) {
$file .= $buffer;
}
-----------------------------------------------------------------
2キロバイトずつ読み込んで、$file変数に格納します。$file変数は、テキスト
データではなく、バイナリ形式になっているので、ご注意ください。
-----------------------------------------------------------------
※バイナリ形式とは?
バイナリ形式を一言では説明できないのですが、簡単に言うと、テキスト形式
ではない、画像(.jpgなど)や音声(.wavなど)、プログラムの実行ファイル
(.exeなど)などを指します。
このため、テキストエディタでバイナリ形式を開くと、文字化けしたような
表示になります。
実際には、ファイルの先頭部分に、そのファイルがどんな形式(jpgやexeなど)
なのかが定義されていて、プログラムがそれを見て判断しています。(拡張子で
判断する場合もありますが、拡張子は変更できるので、正確ではありません。)
-----------------------------------------------------------------
受け取ったファイルを保存する場合は、以下のようにします。
-----------------------------------------------------------------
# ファイルの保存
open(OUT, "> ./tmp.dat") or die("ファイルの保存に失敗しました。");
binmode(OUT);
print(OUT $file);
close(OUT);
-----------------------------------------------------------------
書き込みモードで、保存したいファイル名でファイルオープンしますが、
binmode関数を使用して、バイナリ形式であることを明示する必要があります。
■アップロードファイルの情報
アップロードされたファイルが画像なのか、音声ファイルなのかの情報が
必要な場合があります。以下の方法でMIMEタイプという、ファイル形式の
分かる文字列が取得できます。
-----------------------------------------------------------------
# ファイル形式の取得
$type = $query->uploadInfo($filename)->{'Content-Type'};
-----------------------------------------------------------------
拡張子で判断しても良いですが、MIMEタイプで判定した方が確実と言えます。
★今日のまとめサンプルプログラム
index.html
-----------------------------------------------------------------
<HTML>
<BODY>
<FORM action="upload.cgi" enctype="multipart/form-data" method="post">
画像ファイル: <INPUT type="file" name="upload_file" size="32"><BR>
<INPUT type="submit" value="送信">
</FORM>
</BODY>
</HTML>
-----------------------------------------------------------------
upload.cgi
-----------------------------------------------------------------
#! /usr/local/bin/perl
print "Content-type:text/html\n\n";
print << "END_OF_HTML";
<HTML>
<BODY>
END_OF_HTML
use CGI;
$query = new CGI;
# ファイル名の取得
$filename = $query->param('upload_file');
# MIMEタイプの取得
$type = $query->uploadInfo($filename)->{'Content-Type'};
# ファイルの受け取り
while($bytesread = read($filename, $buffer, 2048)) {
$file .= $buffer;
}
# ファイルの保存
open(OUT, "> ./tmp.dat") or die("ファイルの保存に失敗しました。");
binmode(OUT);
print(OUT $file);
close(OUT);
print << "END_OF_HTML";
<img src="./tmp.dat"><br>
ファイル名 : $filename<br>
MIMEタイプ : $type<br>
</BODY>
</HTML>
END_OF_HTML
exit;
-----------------------------------------------------------------
■解説
このサンプルは、画像ファイルをアップロードし、アップロードされたファイルを
表示するプログラムです。
このプログラムを実行すると、CGIの置いてあるディレクトリにアップロードした
ファイルがコピーされます。
取得したファイル名とMIMEタイプも表示されるので、確認してください。
実際にプログラムを組むときは、ファイル名がすでに存在していて、上書きされる
場合もありますので、上書きされないようにする工夫も必要です。
★課題
1. サンプルプログラムを改良し、ファイルサイズが100キロバイトを超えたら、
エラーメッセージを表示するプログラムを作成してください。
2. サンプルプログラムを改良し、アップロードファイルのMIMEタイプが、
JPEG以外の形式だった場合に、エラーメッセージを表示するプログラムを作成して
ください。
★前回の課題の解答
1. HTML上に、メール送信者(text)、件名(text)、本文(textarea)のフォームを
作成し、自分のメールアドレスへ送信されるプログラムを作成してください。
→下のサンプルを参照してください。
index.html
-----------------------------------------------------------------
<HTML>
<BODY>
<FORM action="send_mail.cgi" method="POST">
あなたのメールアドレス <INPUT type="text" name="mail_from"><br>
件名 <INPUT type="text" name="subject"><br>
本文 <textarea name="body" rows="5" cols="30"></textarea><br>
<INPUT type="submit" value="送信">
</FORM>
</BODY>
</HTML>
-----------------------------------------------------------------
send_mail.cgi
-----------------------------------------------------------------
#! /usr/local/bin/perl
print "Content-type:text/html\n\n";
print << "END_OF_HTML";
<HTML>
<BODY>
END_OF_HTML
# メールエージェントのパス指定
$mailer = '/usr/lib/sendmail';
#// パラメタ取得
%data = &GetPara();
# 送信者のメールアドレス
$mlfr = $data{'mail_from'};
# 宛先のメールアドレス
$mlto = 'info@searchai.jp';
# 件名
$mlsb = $data{'subject'};
# 本文
$mlms = $data{'body'};
# 日本語ライブラリの読み込み
require "./jcode.pl";
# JISコードへ変換
jcode::convert(\$mlsb, "jis");
jcode::convert(\$mlms, "jis");
# メールヘッダ作成
$mlhd = "From: $mlfr" . "\n" . "To: $mlto" . "\n" . "Subject: $mlsb" . "\n\n";
# メール送信処理
$err = 0;
open(MAIL, "| $mailer -t -f'$mlfr'") or $err = 1;
if ($err == 0) {
print MAIL $mlhd;
print MAIL $mlms;
print MAIL "\n";
close(MAIL);
print 'メール送信正常に処理しました。';
}
else {
print 'メール送信エラー';
}
print << "END_OF_HTML";
</BODY>
</HTML>
END_OF_HTML
#------------------------------------------------------------------
# 関数 : GetPara
# 概要 : パラメータ取得
# 引数 : なし
# 戻値 : パラメータハッシュ配列
#------------------------------------------------------------------
sub GetPara
{
my $self = shift;
my($query_string); #// エンコードされたパラメータ全体
my(@a, $a); #// エンコードされたパラメータを分解したもの
my($name, $value); #// デコードされたパラメータ
my(%in);
#// パラメータの読み込み
if ($ENV{"REQUEST_METHOD"} eq "POST")
{
#// POSTなら標準入力から読み込む
read(STDIN, $query_string, $ENV{"CONTENT_LENGTH"});
}
else
{
#// GETなら環境変数から読み込む
$query_string = $ENV{"QUERY_STRING"};
}
#// 「変数名1=値1&変数名2=値2」の形式をアンパサンド(&)で分解
@a = split(/&/, $query_string);
# パラメータの取得
foreach $a(@a)
{
#// =(イコール)で分解
($name, $value) = split(/=/, $a);
#// + や %8A などのデコード
$value =~ tr/+/ /;
$value =~ s/%([0-9a-fA-F][0-9a-fA-F])/pack("C", hex( $1 ))/eg;
#// 後で使用するため,$in{'パラメータ名'} に代入しておく
$in{$name} = $value;
}
return %in;
}
exit;
-----------------------------------------------------------------
■解説
以前、作成したGetPara関数を使用して、パラメータを取得しています。
日本語変換ライブラリのjcode.plを使用しているので、同じディレクトリに
入れておく必要があります。
宛先の$mltoは、自分のメールアドレスを指定してください。
メールエージェントのパスが異なる場合は、$mailerのパスを変更してください。
前回のレッスンのサンプルに、パラメータを渡すだけですので、それ以外の
説明はなくても分かるかと思います。
|