Intereting Posts

Загрузка AJAX / PHP с индикатором выполнения для больших файлов

Я пытался создать панель загрузки без флэш-памяти, которая также показывает индикатор выполнения. На нашем сервере у нас есть PHP 5.3 (теперь он не может обновиться до 5.4, поэтому нельзя использовать новую функцию прогона загрузки => http://php.net/manual/en/session.upload-progress.php ). Мы не можем использовать флэш-решения, расширения или подобные.

Поэтому я попытался использовать XMLHttpRequest в сочетании с AJAX. Проблема здесь в том, что я добился лишь частичного успеха.

Мне удалось загрузить и сохранить на сервере файл размером около 380 МБ, однако при попытке с более крупным файлом, например, 4 ГБ, он не будет сохранен на сервере (если я проверю с 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') ); ?> 

Существуют ограничения, связанные с веб-сервером, которые не могут быть изменены PHP. Например, их максимальный размер почтового запроса по умолчанию составляет 30 МБ в IIS … существует также макс. Тайм-аут, который вы можете поразить. Не имеет никакого отношения к размеру, но сколько времени занимает ваш почтовый запрос … т. Е. Как долго его принимает файл. Оба параметра могут быть ограничены IIS или Apache.

Другие уже указали, что существуют ограничения, которые вы натолкнете на любой производственный PHP-сервер, который правильно настроен. Максимальные значения памяти, сообщений и файлов для запуска. Кроме того, служба httpd обычно ограничивает их.

Ответ на такой большой объем загрузки будет заключаться в том, чтобы вырезать файл на куски, отправлять каждый фрагмент в другой пост или пост (в зависимости от браузера).

Существует уже существующая библиотека, способная загружать файлы chunk, поэтому я буду использовать ее в качестве примера. Для поддержки пакетных загрузок обработчик загрузки использует заголовок Content-Range, который передается плагином для каждого фрагмента.

Функция handle_file_upload в классе UploadHandler является хорошим примером того, как обрабатывать пакетную загрузку файлов на стороне сервера с помощью 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) 

Функция принимает аргумент $content_range = null который передается серверу в HTTP-заголовке и извлекается из $_SERVER['HTTP_CONTENT_RANGE'];

Позже нам нужно выяснить, добавим ли мы загрузку файла в файл, который уже существует, поэтому мы устанавливаем переменную. Если размер сообщенных файлов из 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); } } 

Согласно документации: загрузка файлов Chunked поддерживается только браузерами с поддержкой загрузки файлов XHR и API Blob, который включает Google Chrome и Mozilla Firefox 4+ – https://github.com/blueimp/jQuery-File-Upload / вики / Chunked-закачка файлов

Для пакетных загрузок для работы в Mozilla Firefox 4-6 (для XHR-загружаемых версий Firefox до Firefox 7) параметр 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, поэтому «конец» может быть максимально на 1 меньше, чем «размер» . Вы можете использовать диапазон «start-», чтобы указать, что диапазон продолжается до конца файла с «start» .

РЕДАКТИРОВАТЬ:

Просто подумал, что это позволит реализовать индикатор выполнения на серверах, где нет возможности для загрузки отдельных файлов. Поскольку вы знаете размер каждого фрагмента и статус каждого запроса, вы можете обновить строку состояния, соответственно, каждый прогон через цикл.

Также следует отметить ограничение некоторых браузеров. Chrome и Firefox должны иметь возможность обрабатывать файл размером 4 ГБ, но версии IE ниже 9 имеют ошибку, которая предотвращает возможность обработки файлов размером более 2 ГБ.

Вы можете сравнить свой код с этим учебником. Этот учебник может загружать файлы любого размера. Он очень похож на ваш код. http://www.youtube.com/watch?v=pTfVK73CUk8

Я пишу о странном поведении xhr.upload.loaded, который начинается с большого числа …

У меня схожая проблема, и я не мог узнать причину. Единственный ключ, который может помочь, заключается в том, что в зависимости от провайдера проблема иногда исчезает! например, когда я тестирую из дома, он отлично работает, и я не вижу этого странного поведения, но из работы в Интернете проблема остается.

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); 

ура

Я попытался загрузить видеофайл размером 4 ГБ с помощью ajax. Это был успех. Вот мой код.

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; } 

Ajax ::

 <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> в <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 – выполните следующие действия.

 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