2012-04-20 9 views
3

진행률 표시 줄을 보여주는 플래시 이외의 업로드 패널을 만들려고했습니다. 우리 서버에는 PHP 5.3이 있습니다 (현재 5.4로 업그레이드 할 수 없으므로 새 업로드 진행 기능 =>http://php.net/manual/en/session.upload-progress.php). 플래시 기반 솔루션, 확장 프로그램 등은 사용할 수 없습니다.AJAX/PHP 기반 업로드 (대용량 파일의 진행률 표시 줄 포함)

따라서 AJAX와 결합 된 XMLHttpRequest를 사용해 보았습니다. 여기에서 문제는 부분적으로 성공한 것입니다.

나는 약 380MB의 파일을 업로드하고 서버에 저장할 수 있었지만, 4GB와 같은 더 큰 파일을 사용하려고 시도하면 서버에 저장되지 않습니다 (Firebug로 한 점은 "POST aborted"라고 말합니다).

또 다른 이상한 점은 동일한 파일에서 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_post_size ** 및 게시/파일 크기 업로드 관련 플래그. –

+0

나는 그것들을 이미 점검했고 모든 것이 잘된 것처럼 보인다. – DiG

+0

그것은 농담처럼 들리지만 어쨌든 사람들은 어리석은 일을하기 때문에 어쨌든 물어 봅니다. "FAT32를 파일 시스템으로 사용하고 있습니까?" –

답변

3

웹 서버와 관련된 한계가 있다고 할 수 PHP에 의해 변경 될 수 없습니다. 예를 들어, 그것들은 IIS에서 30MB의 기본 최대 게시물 요청 크기입니다 ... 당신이 치는 최대 타임 아웃이 있습니다. 크기와는 아무런 관련이 없지만 게시물 요청이 얼마나 오래 걸리는지 ... 즉 파일 제출 소요 기간. 두 설정 모두 IIS 또는 Apache에 의해 제한 될 수 있습니다.

0

나는 많은 수의 시작 xhr.upload.loaded의 이상한 행동에 대해 쓰고 있어요 ...

나는 비슷한 문제를 가지고 나는 그 이유를 찾을 수 없습니다. ISP에 따라 문제가 해결되지 않을 수도 있습니다. 예를 들어 내가 집에서 테스트 할 때 제대로 작동하고이 이상한 동작이 보이지 않지만 인터넷에서 문제가 남아 있습니다.

0

file_get_contents()는 파일 내용을 가져 와서 내부 포인터로 RAM의 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의 이전 버전은 청크 파일 업로드에 다중 파트/양식 데이터 (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 파일 업로드에 대한 지원 및 구글 크롬과 모질라 파이어 폭스 4+가 포함 된 물방울의 API와 브라우저에서 지원된다 청크 업로드가 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 헤더 값의 형식은 시작/끝입니다. 범위는 0에서 시작하므로 "end""크기"보다 1 미만일 수 있습니다. 범위 "start-"을 사용하여 범위가 "start"의 파일 끝까지 확장됨을 나타낼 수 있습니다.

는 편집 :

그냥이 가능는 단일 파일 업로드, 그렇지 않으면 가능하지 않다 서버에서 진행률 표시 줄을 구현할 수 것이라고 생각했다. 각 청크의 크기와 각 요청의 상태를 알고 있기 때문에 루프마다 실행될 때마다 상태 표시 줄을 업데이트 할 수 있습니다.

또한 특정 브라우저의 제한 사항에 유의하십시오. Chrome 및 Firefox는 4GB 파일을 처리 할 수 ​​있어야하지만 9보다 낮은 IE 버전은 2GB가 넘는 파일을 처리 할 수없는 버그가있었습니다.

0

아약스를 사용하여 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

당신이 리눅스/우분투에있는 경우 -이 ** max_upload_file_size 찾고 ** 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 
관련 문제