サーバー側の受け取りルーチンは 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 件のコメント:
コメントを投稿