サーバー側の受け取りルーチンは fine-uploader では Endpoint と呼ばれる。 fine-uploader のserver-example には,PHP,node.js,Java,Python のサンプルもあるが,ここではPerlによる Endpoint のサンプルをベースにして,多少アレンジしたものを用いた。
以下に Perl による Endpoint を書こう。
#!/usr/bin/perl
#
use CGI::Carp qw(fatalsToBrowser);
use Digest::MD5;
use CGI;
my $IN = new CGI;
# ================================================================
$ext_check = 1; # Do extension check
$original_filename = 1; # use original filename (Not a random name)
$unique_file_max_number = 10; # maximum number for same file name
# ================================================================
print STDERR "\n"."<<<No chunk process>>>"."\n";
my $uploaddir = '../uploadFiles/';
my $uploadtmpdir = '../uploadFiles/'; # chunking 用。簡単のため upload directory と同じにした
my $maxFileSize = 20971520; # 20 MB
# 送られてきたファイル情報の取得
my $file;
if ($IN->param('POSTDATA')) { $file = $IN->param('POSTDATA'); }
else { $file = $IN->upload('qqfile'); }
my $qquuid = $IN->param('qquuid');
my $qqtotalfilesize = $IN->param('qqtotalfilesize');
my $qqtotalparts = $IN->param('qqtotalparts');
my $qqchunksize = $IN->param('qqchunksize');
my $qqpartbyteoffset = $IN->param('qqpartbyteoffset');
my $qqpartindex = $IN->param('qqpartindex');
my $filename4store = $IN->param('qqfilename');
if ($filename4store eq '') { $filename4store = $file; }
# make a random filename, and we guess the file type later on...
my $name = Digest::MD5::md5_base64(rand);
$name =~ s/\+/_/g;
$name =~ s/\//_/g;
# file head の取り出しと,拡張子のチェック
my $filehead, $ext, $extUC, $filename;
my $error = 0;
if ($filename4store =~ /(.*)\.(.*)/) {
$filehead = $1; $ext = $2;
if ($ext_check == 1) {
$extLC = lc($ext); # 小文字でチェックする
if ($extLC !~ /jpg|jpeg|gif|png|zip|7z/) { $error =1; } # Invalid extension type error
}
}
# オリジナルファイル名を使う際には,同じ名前があるかを調べてから書き込む。
# 上書きしない場合で同一名があれば,ファイル名に数字を付加する。
if ($original_filename == 1) {
my $ii = 0;
my $filename_work = $uploaddir.$filehead.'.'.$ext;
if (-f $filename_work) {
# 同一名の時には後ろに番号を付加する
while ((-f $filename_work) and ($ii<$unique_file_max_number)) {
$ii++;
$filename_work = $uploaddir.$filehead.'-'.$ii.'.'.$ext;
}
$filename = $filehead.'-'.$ii.'.'.$ext;
} else { $filename = $filehead.'.'.$ext; }
} else {
$filename = $name.'.'.$ext; # random file name
}
# ---------------------------------------------
# $qqpartindex がカラでない時は,chunking されたファイルの一部分と解釈する
if ($qqpartindex ne '') {
$filename = $filename.'.tmp'.$qqpartindex.'.'.$filename4store; # like 'P114025-2.JPG.tmp0.P114025.JPG'
$uploaddir = $uploadtmpdir; # directory の切替え
}
# ---------------------------------------------
# ファイルの書き出し(chunking の最終処理は別スクリプトで行う)
my $check_size;
if ($error == 0) {
# ---------------------------------------------
# 実際のファイルの書き込み処理
binmode(WRITEIT);
open(WRITEIT, ">$uploaddir$filename") or die "Cant write to $uploaddir$filename. Reason: $!";
if ($IN->param('POSTDATA')) {
print WRITEIT $file;
} else {
while (<$file>) { print WRITEIT; }
}
close(WRITEIT);
# ---------------------------------------------
chmod 0664,"$uploaddir$filename"; # 後で扱いやすいように permission を変更
$check_size = -s "$uploaddir$filename"; # upload されたファイルのサイズの取得
if ($check_size < 1) { $error = 2; } # file empty error
elsif ($check_size > $maxFileSize) { $error = 3; } # Too big error
print STDERR qq|Main filesize: $check_size \n|; # /var/log/httpd-error.log に記載
} # if ($error == 0)
my $currentTimeStr = &GetDateTimeStr();
print $IN->header();
if ($error == 0) {
# 以下の行が Endpoint から送り返される情報
print qq|{"success":true,"filename":"$filename","size":"$check_size","endtime":"$currentTimeStr" }|;
# 以下の行は /var/log/httpd-error.log へのログ
print STDERR "File has been successfully uploaded.\n";
} elsif ($error == 2) {
print qq|{"success":false,"error":"File is empty" }|;
print STDERR "File is EMPTY !!\n";
print STDERR "file has been NOT been uploaded... \n";
} elsif ($error == 3) {
print qq|{"success":false,"error":"File is too large" }|;
print STDERR "File size is TOO LARGE !!\n";
print STDERR "file has been NOT been uploaded... \n";
} else {
print qq|{"success":false,"error":"Invalid file type"}|;
print STDERR "Invalid file extension ERROR !!\n";
print STDERR "file has been NOT been uploaded... \n";
} # if $error == 0
print STDERR ' Date Time ="'.$currentTimeStr.'"'."\n";
print STDERR ' upload_dir ="'.$uploaddir.'"'."\n";
print STDERR ' filename(orig) ="'.$file.'"'."\n";
print STDERR 'filename(edited) ="'.$filename4store.'"'."\n";
print STDERR 'filename(stored) ="'.$filename.'"'."\n";
print STDERR ' qquuid ="'.$qquuid.'"'."\n";
print STDERR ' qqtotalfilesize ="'.$qqtotalfilesize.'", qqchunksize ="'.$qqchunksize.'", qqpartbyteoffset ="'.$qqpartbyteoffset.'", qqtotalparts ="'.$qqtotalparts.'", qqpartindex ="'.$qqpartindex.'"'."\n";
print STDERR "\n";
exit;
# ======================================================================
sub GetDateTimeStr {
my(@GTS_date_array) = @_;
if ($GTS_date_array[1] eq '') {
@GTS_date_array = localtime(time);
}
my $dum=($GTS_date_array[5]+1900)."/".&ZeroPadding($GTS_date_array[4]+1)."/".&ZeroPadding($GTS_date_array[3])
.' '.&ZeroPadding($GTS_date_array[2]).":".&ZeroPadding($GTS_date_array[1]).":".&ZeroPadding($GTS_date_array[0]);
return $dum;
}
sub ZeroPadding {
my($dum)=sprintf("%d",$_[0]);
$dum="00".$dum;
$dum=substr($dum,(length($dum)-2));
return $dum;
}
具体的にはスクリプトを読んで理解してほしいが,少し解説しておこう。まず,CGI パッケージを使っている。先頭の辺りで2度「use CGI」が出て来るが,なんでいるんやろ?もともとのサンプルで2個出てるからそのまま使っておいた。 Digest::MD5 はランダムなファイル名をつけるために使っている。
$ext_check や $original_filename 等は,サーバー側で拡張子のチェックを行うかや,オリジナルのファイル名で保存するか,などのフラグとして使っている。
「print STDERR xxxxx」みたいな行がいくつも出てくるが,これは /var/log/httpd-error.log への出力であり,エラーがあった時には有効である。
次に「$IN->param('XXXX')」という行が複数並んでいる。 これはポータルサイトから送られた情報を POST で受け取っているものである。 Endpoint ではこれらを使って実際にアップロードされたファイルをサーバーに書き込む作業を行う。 特に分割送信(chunking)の場合には,分割された何個目のファイルかを表したりするので,重要な情報である。
POSTDATA または qqfile がファイル名を示す。分割送信時には「blob」などが入るため,後の qqfilename を参照しないといけない。
qquuid は,fine-uploader によって与えられた,ファイルのアップロード固有の id である。
qqtotalfilesize は,送信中のファイルの全サイズ(分割していない場合は単にファイルサイズ)を表す。
qqtotalparts は,分割送信の場合に,いくつに分割されたかを示す。
qqchunksize は,分割送信の際の分割された当該ファイルのサイズである。
qqpartbyteoffset は,分割送信の際,当該ファイルが先頭から何バイト目からのものか,を表している。
qqpartindex は,当該ファイルが分割ファイルとして何番目のファイルか(0番から始まる)を表している。
qqfilename は,分割送信時にファイル名が入る。一括の場合はカラである。
途中に「if ($qqpartindex ne '')」で始まる行があるが,これは分割送信のための処理である。 分割送信では後処理でファイルを結合させないといけないが,その時の便宜のために,ファイル名に何番目の分割ファイルか,と,最終的なファイル名をつける処理である。 また,一時保存ディレクトリを別個に用意することもできる(ここでは面倒くさいのでアップロードディレクトリと同じにしている)。
「実際のファイルの書き込み処理」と書かれた部分が実際にサーバーにファイルを保存している部分である。 実は Endpoint の重要な部分はこの部分に集約されている。後はおまけみたいなものである。
後は,ファイルサイズが大きすぎればエラーと判断し,最後にポータルサイトに情報を送り返している。 送り返す情報は,
{"success":true}
{"success":false, "error":"File is empty."}
のように「{}」の中に「"xxx":yyy」の形式で情報を並べる。 情報としては,最低限「"success":」が必要である。 "success": が true なら送信成功であり,そうでなければエラーが発生した判断される。 それ以外は自由に情報を返すことができる。 ここでは送信がうまくできた場合には,サーバーに保存された際のファイル名,サイズ,保存時刻を返している。
最後に,デバッグのために /var/log/httpd-error.log にも情報を書き込んで終了となっている。
以下に,サーバーの /var/log/httpd-error.log に残った情報を書いておこう。
<<<No chunk process>>>
Main filesize: 1041983
File has been successfully uploaded.
Date Time = "2017/06/xx 22:00:40"
upload_dir = "../uploadFiles/"
filename(orig) = "DSC_0482.jpg"
filename(edited) = "DSC_0482.jpg"
filename(stored) = "DSC_0482-2.jpg"
qquuid = "19a43a62-6061-41e9-a238-42d4142baec8"
qqtotalfilesize = "1041983", qqchunksize ="", qqpartbyteoffset ="", qqtotalparts ="", qqpartindex =""
これは単独のファイルアップロードの際のログである。
そのため qqchunksize や qqpartbyteoffset 等はカラとなっている。
また,テストで何度も同じファイルをアップロードした際のものなので,保存されたファイル名に「-2」が加えられているのがわかる。今回は fine-uploader ライブラリ使用時の Endpoint ルーチンについて書いてみた。 最後に chunking の際のファイル結合ルーチンのことを次の投稿で書こう。
FreeBSD で fine-uploader を使ってみた(その3)
0 件のコメント:
コメントを投稿