2012-04-20 136 views
3

我一直在嘗試創建一個非Flash上​​傳面板,它也顯示一個進度條。 在我們的服務器上我們有PHP 5.3(現在不能升級到5.4,所以新的上傳進度功能不能使用=>http://php.net/manual/en/session.upload-progress.php)。 我們不能使用基於閃存的解決方案,擴展或類似的。基於AJAX/PHP的大文件進度條上傳

因此,我一直在使用一個XMLHttpRequest與AJAX結合審判。 這裏的問題是我只取得了部分成功。

我設法上傳並保存在服務器上一個大約380 MB的文件,但是,當使用像4 GB這樣的大文件進行嘗試時,它不會被保存在服務器上(如果我用Firebug檢查有一點會說「POST中止」)。

另一個奇怪的事情是,用相同的文件xhr.upload.loaded與xhr.upload.total相同尺寸開始,並開始從那裏計數。

有誰知道如何解決這個問題,或者有一個替代的解決方案?

的客戶端代碼:

<script type="application/javascript" src="jquery.js"></script> 

<script type="application/javascript"> 

function uploadToServer() 
{ 
    fileField = document.getElementById("uploadedFile"); 
    var fileToUpload = fileField.files[0]; 

    var xhr = new XMLHttpRequest(); 
    var uploadStatus = xhr.upload; 

    uploadStatus.addEventListener("progress", function (ev) { 
      if (ev.lengthComputable) { 
       $("#uploadPercentage").html((ev.loaded/ev.total) * 100 + "%"); 
      } 
     }, false); 

    uploadStatus.addEventListener("error", function (ev) {$("#error").html(ev)}, false); 
    uploadStatus.addEventListener("load", function (ev) {$("#error").html("APPOSTO!")}, false); 

    xhr.open(
      "POST", 
      "serverUpload.php", 
      true 
      ); 
     xhr.setRequestHeader("Cache-Control", "no-cache"); 
     xhr.setRequestHeader("Content-Type", "multipart/form-data"); 
     xhr.setRequestHeader("X-File-Name", fileToUpload.fileName); 
     xhr.setRequestHeader("X-File-Size", fileToUpload.fileSize); 
     xhr.setRequestHeader("X-File-Type", fileToUpload.type); 
     //xhr.setRequestHeader("Content-Type", "application/octet-stream"); 
     xhr.send(fileToUpload); 
} 



$(function(){ 

    $("#uploadButton").click(uploadToServer); 

}); 


</script> 

HTML部分:

<form action="" name="uploadForm" method="post" enctype="multipart/form-data"> 

    <input id="uploadedFile" name="fileField" type="file" multiple /> 

<input id="uploadButton" type="button" value="Upload!"> 

</form> 

<div id="uploadPercentage"></div> 
<div id="error"></div> 

服務器端代碼:

<?php 

$path = "./"; 
$filename = $_SERVER['HTTP_X_FILE_NAME']; 
$filesize = $_SERVER['CONTENT_LENGTH']; 


$file = "log.txt"; 
$fo= fopen($file, "w"); 
fwrite($fo, $path . PHP_EOL); 
fwrite($fo, $filename . PHP_EOL); 
fwrite($fo, $filesize . PHP_EOL); 
fwrite($fo, $path . $filename . PHP_EOL); 

file_put_contents($path . $filename, 
file_get_contents('php://input') 
); 

?> 
+1

查找找** max_upload_file_size ** ** max_post_size **和相關的標誌上傳/文件大小上傳。 – 2012-04-20 19:39:18

+0

我已經檢查過這些,一切似乎都很好。 – DiG 2012-04-20 21:39:03

+0

這聽起來像是一個笑話,但有時候人們會做一些愚蠢的事情,所以我反過來問:「你使用FAT32作爲文件系統嗎?」 – 2013-08-29 12:01:40

回答

3

有與web服務器相關聯的限制,可以不會被PHP更改。例如,它們是IIS中默認的30MB的最大發布請求大小......還有一個您可能遇到的最大超時時間。與尺寸沒有任何關係,但是您的帖子請求需要多長時間......即提交文件需要多長時間。這兩個設置都可以受IIS或Apache的限制。

0

我寫xhr.upload.loaded的奇怪行爲與大量開始......

我有類似的問題,我無法找出原因。 唯一可能幫助的線索是,根據ISP,問題有時會消失! 例如,當我在家測試它工作正常,我沒有看到這種奇怪的行爲,但從工作互聯網,問題依然存在。

0

file_get_contents()獲取文件的內容並將其放入內存中的BUFFER並帶有內部指針。 如果你沒有足夠的內存,或者有一個32位版本的apache/php,當試圖分配太多內存時它可能會崩潰。

你可能想嘗試這樣的東西,而不是:

$upload = fopen("php://input", "r"); 
while (!feof($upload)) { 
    file_put_contents($path . $filename, fread($upload, 4096), FILE_APPEND); 
} 
fclose($upload); 

乾杯

3

其他人已經指出,有一些你會遇到被正確配置任何生產PHP服務器上的限制。內存,後期和文件最大值開始。另外httpd服務通常也限制這些。

上傳內容的答案如此之大,將剪切文件到塊,在不同的認沽或交(取決於瀏覽器。)

目前已經存在一個庫發送的每塊能塊的文件上傳,所以我會用它作爲例子。爲了支持分塊上傳,上傳處理程序使用由插件爲每個塊傳輸的Content-Range標頭。

UploadHandler類中的handle_file_upload函數是如何在服務器端使用PHP處理分塊文件上傳的一個很好的例子。 - https://github.com/blueimp/jQuery-File-Upload/blob/master/server/php/UploadHandler.php

function handle_file_upload($uploaded_file, $name, $size, $type, $error, 
     $index = null, $content_range = null) 

該函數將被傳遞到HTTP頭中的服務器,然後從$_SERVER['HTTP_CONTENT_RANGE'];

檢索到後來,我們需要知道我們是否會被追加文件上傳給個說法$content_range = null一個已經存在的文件,所以我們設置了一個變量。如果來自HTTP請求的報告文件大小大於服務器上的實際文件大小,則$content_range變量不是NULL,並且該文件存在,因此我們需要將此上載附加到現有文件。

$append_file = $content_range && is_file($file_path) && 
      $file->size > $this->get_file_size($file_path); 

太棒了!怎麼辦?

所以現在我們需要知道我們如何接收數據。老版本的Firefox不能使用multipart/formdata(POST)進行分塊文件上傳。客戶端和服務器端都需要對這些請求進行不同的處理。

 if ($uploaded_file && is_uploaded_file($uploaded_file)) { 
      // multipart/formdata uploads (POST method uploads) 
      if ($append_file) { 
      // append to the existing file 
       file_put_contents(
        $file_path, 
        fopen($uploaded_file, 'r'), 
        FILE_APPEND 
       ); 
      } else { 
      // this is a new chunked upload OR a completed single part upload, 
      // so move the file from the temp directory to the uploads directory. 
       move_uploaded_file($uploaded_file, $file_path); 
      } 
     } 

根據文檔: - https://github.com/blueimp/jQuery-File-Upload/wiki/Chunked-file-uploads

對於分塊文件上傳僅通過與XHR支持文件上傳和斑點API,其中包括谷歌Chrome和Mozilla Firefox瀏覽器4+瀏覽器支持分塊上傳在Mozilla Firefox 4-6(在Firefox 7之前支持XHR上傳功能的Firefox版本)中工作,multipart選項也必須設置爲false。以下是處理服務器端這些情況的代碼。

 else { 
      // Non-multipart uploads (PUT method support) 
      file_put_contents(
       $file_path, 
       fopen('php://input', 'r'), 
       $append_file ? FILE_APPEND : 0 
      ); 
     } 

最後我們可以驗證下載是否完成或放棄取消上傳。

 $file_size = $this->get_file_size($file_path, $append_file); 
     if ($file_size === $file->size) { 
      $file->url = $this->get_download_url($file->name); 
      if ($this->is_valid_image_file($file_path)) { 
       $this->handle_image_file($file_path, $file); 
      } 
     } else { 
      $file->size = $file_size; 
      if (!$content_range && $this->options['discard_aborted_uploads']) { 
       unlink($file_path); 
       $file->error = $this->get_error_message('abort'); 
      } 
     } 

在客戶端,您將需要跟蹤塊。在每個作品發佈之後,我們發送下一個作品,直到沒有剩下的作品。示例庫是jQuery的一個插件,它使得它非常簡單。使用像您這樣的裸XHR對象將需要更多的代碼。它可能看起來像這樣:

var chunksize = 1000000 // 1MB 
var chunks = math.ceil(chunksize/fileToUpload.fileSize); 

function uploadChunk(fileToUpload, chunk = 0) { 
    var xhr = new XMLHttpRequest(); 
    var uploadStatus = xhr.upload; 

    uploadStatus.addEventListener("progress", function (ev) { 
      if (ev.lengthComputable) { 
       $("#uploadPercentage").html((ev.loaded/ev.total) * 100 + "%"); 
      } 
     }, false); 

    uploadStatus.addEventListener("error", function (ev) {$("#error").html(ev)}, false); 
    uploadStatus.addEventListener("load", function (ev) {$("#error").html("APPOSTO!")}, false); 

    var start = chunksize*chunk; 
    var end = start+(chunksize-1) 
    if (end >= fileToUpload.fileSize) { 
      end = fileToUpload.fileSize-1; 
    } 

    xhr.open(
      "POST", 
      "serverUpload.php", 
      true 
    ); 
    xhr.setRequestHeader("Cache-Control", "no-cache"); 
    xhr.setRequestHeader("Content-Type", "multipart/form-data"); 
    xhr.setRequestHeader("X-File-Name", fileToUpload.fileName); 
    xhr.setRequestHeader("X-File-Size", fileToUpload.fileSize); 
    xhr.setRequestHeader("X-File-Type", fileToUpload.type); 
    xhr.setRequestHeader("Content-Range", start+"-"+end+"/"+fileToUpload.fileSize); 
    xhr.send(fileToUpload); 
} 

for(c = 0; c < chunks; c++) { 
    uploadChunk(fileToUpload, c); 
} 

循環遍歷塊,依次上傳每個塊範圍。請注意,Content-Range標頭值的格式爲start-end/size。範圍從0開始,因此「結束」只能是最大值小於「大小」。您可以使用範圍「start-」來指示範圍從「開始」延伸到文件末尾。

編輯:

只是認爲這將有可能實現在服務器上的進度條的地方是沒有其他可能的單個文件上傳。由於您知道每個塊的大小以及每個請求的狀態,因此每次運行循環時都可以相應地更新狀態欄。

另外值得注意的是某些瀏覽器的限制。 Chrome和Firefox應該能夠處理4GB文件,但低於9的IE版本有一個錯誤,無法處理大於2GB的文件。

0

我試圖用ajax上傳4GB的視頻文件。這是成功的。 這是我的代碼。

HTML ::

<form enctype="multipart/form-data" method="post"> 
<input type="file" id="video_file" name="video_file" accept=".mp4, .avi, .mkv"> 
<input type="submit" class="btn btn-success" id="video-upload-btn" name="video_upload_btn" value="Upload"> 
<div class="video-bar"> 
    <span class="video-bar-fill" id="video-bar-fill-id"><span class="video-bar-fill-text" id="video-bar-fill-text-id"></span></span> 
</div> 
</form> 

CSS ::

.video-bar{ 
    width: 100%; 
    background: #eee; 
    padding: 3px; 
    margin-bottom: 10px; 
    box-shadow: inset 0 1px 3px rgba(0,0,0,0.2); 
    border-radius: 3px; 
    box-sizing: border-box; 
} 

.video-bar-fill{ 
    height: 20px; 
    display: block; 
    background: cornflowerblue; 
    width: 0; 
    border-radius: 3px; 
    transition: width 0.8s ease; 
} 
.video-bar-fill-text{ 
    color: #fff; 
    padding: 3px; 
} 

阿賈克斯::

<script type="text/javascript"> 
    var app = app || {}; 

    (function(video_op){ 
     "use strict"; 

     var video_ajax, video_getFormData, video_setProgress; 

     video_ajax = function(data){ 

      var xmlhttp = new XMLHttpRequest(), uploaded; 

      xmlhttp.addEventListener('readystatechange', function(){ 
       if(this.readyState==4){ 
        if(this.status==200){ 
         uploaded = JSON.parse(this.response); 
         console.log(uploaded); 

         if(typeof video_op.options.finished==='function'){ 
          video_op.options.finished(uploaded); 
         } 
        } else { 
         if(typeof video_op.options.error === 'function'){ 
          video_op.options.error(); 
         } 
        } 
       } 
      }); 

      xmlhttp.upload.addEventListener("progress", function(event){ 
       var percent; 
       if(event.lengthComputable===true){ 
        percent = Math.round((event.loaded/event.total) * 100); 
        video_setProgress(percent); 
       } 

      }); 

      if(video_op.options.videoProgressBar!==undefined){ 
       video_op.options.videoProgressBar.style.width=0; 
      } 
      if(video_op.options.videoProgressText!==undefined){ 
       video_op.options.videoProgressText.innerText=0; 
      } 

      xmlhttp.open("post", video_op.options.videoProcessor); 
      xmlhttp.send(data); 

     }; 

     video_getFormData = function(source1){ 
      var data = new FormData(), i; 

      for(i=0;i<source1.length; i++){ 
       data.append('video_file', source1[i]); 
      } 

      data.append("ajax", true); 

      return data; 

     }; 

     video_setProgress = function(value){ 
      if(video_op.options.videoProgressBar!==undefined){ 
       video_op.options.videoProgressBar.style.width = value? value+"%":0; 
      } 
      if(video_op.options.videoProgressText!==undefined){ 
       video_op.options.videoProgressText.innerText=value?value+"%":0; 
      } 
     }; 

     video_op.videouploader = function(options){ 
      video_op.options = options; 

      if(video_op.options.videoFiles !== undefined){ 
       var videoFormDataValue = video_getFormData(video_op.options.videoFiles.files); 

       video_ajax(videoFormDataValue); 
      } 
     } 

    }(app)); 

    document.getElementById("video-upload-btn").addEventListener("click", function(e){ 
     e.preventDefault(); 

     document.getElementById("video-upload-btn").setAttribute("disabled", "true"); 

     var videof = document.getElementById('video_file'), 
      videopb = document.getElementById('video-bar-fill-id'), 
      videopt = document.getElementById('video-bar-fill-text-id'); 

     app.videouploader({ 
      videoFiles: videof, 
      videoProgressBar: videopb, 
      videoProgressText: videopt, 
      videoProcessor: "upload.php", 

      finished: function(data){ 
       console.log(data); 

      }, 

      error: function(){ 
       console.log("error"); 
      } 
     }); 
    }); 
</script> 

服務器端::

<?php 
    if(!empty($_FILES["video_file"])) 
    { 
     if(!empty($_FILES["video_file"]["error"])) 
     { 
      if(move_uploaded_file($_FILES["video_file"]["tmp_name"], __DIR__."/".$_FILES["video_file"]["name"])) 
      { 
       echo "success"; 
      } 
      else 
      { 
       echo "failed"; 
      } 
     } 
     else 
     { 
      echo "error"; 
     } 

    } 
?> 

還改變以下列出的php ini值。

  1. 的post_max_size
  2. 的upload_max_filesize

如果你是在Linux/Ubuntu的 - 遵循在** **的php.ini這個選項步驟

Open php ini file - 
sudo nano /etc/php5/apache2/php.ini 

Update these values- 
post_max_size = 6000M 
upload_max_filesize = 6000M 

restart apache 
sudo /etc/init.d/apache2 restart