PHPで大容量のダウンロードはサーバ側でfreadで数Kバイト単位で送出すればいいだけですが、アップロードは、そうはいかず、enable_post_data_readingをoff設定にして、自力でpostデータをパースし、データ部を数Kバイト単位で読むしかないのです。実装しました。
(1Gのファイルのダウンロード、アップロードは、オンメモリーで処理すると、
例えば同時接続10でアップロードすると10Gにもなって破綻します。)
[設定]
プログラムはアップロード専用フォルダーにして、
.htaccess
php_flag enable_post_data_reading Off
・enable_post_data_reading Offにすると、$_POST,$_FILEは使えません。$_GET,$SESSIONは有効。
・ini_set()ではenable_post_data_readingは設定できません。
test.html
<html lang="ja"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <meta http-equiv="Content-Style-Type" content="text/css" /> <meta http-equiv="Content-Script-Type" content="text/javascript" /> <link rel="stylesheet" href="/css/jquery-ui.css" type="text/css" /> <script type="text/javascript" src="/js/jquery.min.js"></script> <title></title> </head> <SCRIPT Language="Javascript1.2"> <!-- $(function() { $('input[type=file]').change(function() { file1 = document.getElementById("userfile1").files[0]; file2 = document.getElementById("userfile2").files[0]; file3 = document.getElementById("userfile3").files[0]; if(typeof file1 === "undefined"){ document.sedesupload.up_size1.value = "0\t"; }else{ document.sedesupload.up_size1.value = file1.size + "\t" + file1.name; } if(typeof file2 === "undefined"){ document.sedesupload.up_size2.value = "0\t"; }else{ document.sedesupload.up_size2.value = file2.size + "\t" + file2.name; } if(typeof file3 === "undefined"){ document.sedesupload.up_size3.value = "0\t"; }else{ document.sedesupload.up_size3.value = file3.size + "\t" + file3.name; } }); }); --> </SCRIPT> <body> <br> <h1>TEST</h1> <form enctype="multipart/form-data" name="sedesupload" action="binupload-multi.php" method="POST"> <input type="hidden" name="trkno" value="DD00008"> <input type="hidden" name="user" value="T"> <input type="hidden" name="up_size1" id="up_size1" value="0"> <input type="hidden" name="up_size2" id="up_size2" value="0"> <input type="hidden" name="up_size3" id="up_size3" value="0"> No1: <input name="userfile1" id="userfile1" type="file" /><br> No2: <input name="userfile2" id="userfile2" type="file" /><br> No3: <input name="userfile3" id="userfile3" type="file" /><br> <br> <input type="submit" value="ファイルを送信"/> </form> </html>
・最大3ファイルを選択
・POSTデータに3ファイルのファイルサイズ、ファイル名を同時にセット。ファイルサイズをサーバ側に知らせないと、受け側のプログラムが面倒な処理になるので、入れてます。
binupload-multi.php
<?php # php 5.4 or higher #.htaccess php_flag enable_post_data_reading Off # #print_r($_SESSION); // is #print_r($_REQUEST); // is #print_r($_GET); // is #print_r($_POST); // null //-- config base ----------------- ini_set("max_execution_time",180 * 3); $base_dir = "/home/temp"; $READ_BUF_SIZE = 8192; //------------------------------- $handle_in = fopen("php://input", "rb"); if (FALSE === $handle_in) { exit("Failed to open stream to URL"); } $header_sec = 0; $contents = ""; $multi_part =""; $multi_part_now = ""; while (!feof($handle_in)) { // ------------------------------------ // STEP 0: first GET multi part strings // ------------------------------------ if($header_sec == 0){ //get multipart string $multi_part = str_replace("\r\n", '', fgets($handle_in)); $header_sec = 1; //Next Content if(strlen($multi_part) == 0){ print "multi_part_len 0 error \r\n"; exit; } print "[". $multi_part . "]<br><br>"; continue; } // ------------------------------------ // STEP 2: next GET multi part strings & check // ------------------------------------ if($header_sec == 2){ $multi_part_now = str_replace("\r\n", '', fgets($handle_in)); if($multi_part_now === $multi_part){ $header_sec = 1; continue; }else{ // Last part check if($multi_part_now == $multi_part . "--"){ print "sucess\r\n"; }else{ print "sequence error step 2\r\n"; } break; } } // -------------------------------------------------- // STEP 1: GET Content-Disposition (post name & data) // -------------------------------------------------- if($header_sec == 1){ $line = str_replace("\r\n", '', fgets($handle_in)); $sbuf = explode('filename="',$line); if(count($sbuf) == 1){ //parameter $pbuf = explode('name="',$sbuf[0]); $pname = mb_substr($pbuf[1], 0, -1); fgets($handle_in); //null line; $pdata = str_replace("\r\n", '', fgets($handle_in)); print "NAME=[".$pname . "] DATA=[" . $pdata . "]<br>"; if($pname === "up_size1"){ $upbuf = explode("\t",$pdata); $upfile_size[0] = $upbuf[0]; $upfile_name[0] = $upbuf[1]; }elseif($pname === "up_size2"){ $upbuf = explode("\t",$pdata); $upfile_size[1] = $upbuf[0]; $upfile_name[1] = $upbuf[1]; }elseif($pname === "up_size3"){ $upbuf = explode("\t",$pdata); $upfile_size[2] = $upbuf[0]; $upfile_name[2] = $upbuf[1]; } $header_sec = 2; continue; }else{ //file parameter $filename = mb_substr($sbuf[1], 0, -1); fgets($handle_in); //skip Content-Type:; fgets($handle_in); //skip null line; print "FLENMAE=[".$filename . "]<br>"; if($filename == ""){ fgets($handle_in); //skip null line; $header_sec = 2; continue; }else{ for($i = 0 ; $i < 3 ; $i++){ if($filename === $upfile_name[$i]){ $filesize = $upfile_size[$i]; $handle_out = fopen($base_dir . "/" . $filename, "w+b"); $header_sec = 3; break; } } } } } // -------------------------------------------------- // STEP 3: GET file binary contests // -------------------------------------------------- if($header_sec == 3){ if($filesize < $READ_BUF_SIZE){ $contents = fread($handle_in, $filesize); fwrite($handle_out,$contents); }else{ $readsize = 0; while (!feof($handle_in)) { $contents = fread($handle_in, $READ_BUF_SIZE); $readsize += $READ_BUF_SIZE; fwrite($handle_out,$contents); if(($readsize) + $READ_BUF_SIZE >= $filesize){ $contents = fread($handle_in, $filesize - $readsize); fwrite($handle_out,$contents); break; } //usleep(1000); } } fgets($handle_in); //null line; $header_sec = 2; fclose($handle_out); } } fclose($handle_in); ?>
・max_execution_timeは、540秒(9分)
・マルチパートを区切りに、POSTデータをパース
・ファイルの場合は、ファイルサイズを基に8192バイト単位で受信しファイルに書き込み。
※テストコードなのでコードレビューはしておりません。
プログラム中に固定化(3ファイル前提)されているところもありますので利用される方はご注意を。日本語ファイル名等はそれなりの処理をして下さい。
合計9.5G アップロードできました。
この時、httpdのCPU使用率は50%でした。
※usleep(1000) 1msecのsleepを入れるとCPU使用率は13%になります。当然アップロード時間も増加しますので、max_execution_timeは要調整。
httpd CPU使用率
———————-
50% なし
34% usleep(100)
28% usleep(300)
13% usleep(1000) 1msec
———————–
まあ、昔のperlのcgi時代は全て自分でPOSTデータはパースしてたので、
全然違和感はないですが、今は全部やってくれるので知らない内に
オンメモリーで処理されていること忘れがちあるいはそもそも、そんな考えも
しないという新人の技術者も多いかと思いますのでご注意を。