Статьи

Отслеживание прогресса загрузки с помощью PHP и JavaScript

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

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

JavaScript может получить доступ к имени файла, типу и даже ширине и высоте локального изображения, но только в HTML5 он смог получить доступ к размеру файла . К сожалению, HTML5 все еще не является законченным стандартом и не поддерживается одинаково во всех браузерах. Альтернативным решением является использование плагина Flash, Java или ActiveX; нет, спасибо, я сдам. Еще одно решение — установить расширение Alternative PHP Cache , но оно может быть недоступно в зависимости от среды вашего хостинга, и это кажется излишним для такой небольшой задачи, как эта.

Казалось бы, все варианты чреваты неприятностями, и задача быстро стала головной болью. Но, по словам Йоды, «нет… есть другое».

Одна из многих причин, по которым я люблю PHP, заключается в том, что он облегчает, казалось бы, сложные задачи. Начиная с PHP 5.4, они сделали это снова с новым набором директив конфигурации, session.upload_progress .

В этой статье я покажу вам, как эту функцию можно использовать для создания простого индикатора выполнения загрузки без каких-либо внешних библиотек или зависимостей браузера. Сначала я расскажу, как это работает, а затем проведу вас через создание четырех файлов, необходимых для выполнения задачи (форма загрузки, немного JavaScript, немного CSS и файл для возврата статуса загрузки).

Процесс загрузки сеанса

Помимо обычных требований разрешить загрузку файлов, есть еще два, чтобы отслеживать прогресс. Директива session.upload_progress.enabled должна быть включена, и в вашей веб-форме должно быть скрытое поле с именем, указанным в директиве session.upload_progress.name . Если для session.upload_progress.enabled true (как по умолчанию в PHP 5.4 и, вероятно, за его пределами) и во время загрузки отправляется $_POST[ session.upload_progress.name ] , информация о передаче файла становится доступной в $_SESSION выражении $_SESSION массив.

Вывод print_r() массива $_SESSION будет выглядеть примерно так во время передачи файла:

  массив
 (
     [upload_progress_myForm] => Массив
         (
             [start_time] => 1323733740
             [content_length] => 721127769
             [bytes_processed] => 263178326
             [сделано] => 
             [files] => Array
                 (
                     [0] => Массив
                         (
                             [field_name] => userfile
                             [name] => ubuntu-10.04.3-desktop-i386.iso
                             [tmp_name] => 
                             [ошибка] => 0
                             [сделано] => 
                             [start_time] => 1323733740
                             [bytes_processed] => 263178026
                         )
                 )
         )
 ) 

Когда вы разрабатываете локально или в быстрой сети и загружаете небольшие файлы, вы не сможете визуально наблюдать за прогрессом, потому что передача происходит так быстро. В этом случае вы можете попробовать перенести большой файл. Убедитесь, что настройки в вашем файле php.ini разрешают большие загрузки, в частности директивы post_max_size и upload_max_filesize , а затем убедитесь, что они являются нормальными значениями, когда вы приступаете к работе.

Создать форму

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

Вот код для form.php :

 <?php if ($_SERVER["REQUEST_METHOD"] == "POST" && !empty($_FILES["userfile"])) { // move_uploaded_file() } ?> <html> <head> <title>File Upload Progress Bar</title> <link rel="stylesheet" type="text/css" href="style.css"> </head> <body> <div id="bar_blank"> <div id="bar_color"></div> </div> <div id="status"></div> <form action="<?php echo $_SERVER["PHP_SELF"]; ?>" method="POST" id="myForm" enctype="multipart/form-data" target="hidden_iframe"> <input type="hidden" value="myForm" name="<?php echo ini_get("session.upload_progress.name"); ?>"> <input type="file" name="userfile"><br> <input type="submit" value="Start Upload"> </form> <iframe id="hidden_iframe" name="hidden_iframe" src="about:blank"></iframe> <script type="text/javascript" src="script.js"></script> </body> </html> 

В этом примере код для фактической обработки файла был опущен для упрощения работы. Если вас интересует, как должен выглядеть такой код, ознакомьтесь со статьей « Загрузка файлов с помощью PHP » Тимоти Борончика.

После раздела заголовка, в котором содержится заголовок страницы и таблица стилей, вы увидите небольшую коллекцию элементов div. Div с идентификатором «bar_blank» является контейнером для индикатора выполнения. Div с идентификатором «bar_color» будет динамически обновляться по мере загрузки файла. Div «status» будет отображать числовое значение загруженного процента.

Форма настроена на отправку по тому же URL, а ее целевой атрибут указывает на скрытый элемент iframe. Отправка формы в скрытый фрейм позволяет удерживать посетителя на той же странице, пока работа выполняется в фоновом режиме. На самом деле, это обычная практика при «загрузке Ajax-файла», так как невозможно отправить содержимое файла напрямую с помощью объекта JavaScript XmlHttpRequest .

Внутри формы появляется специальное скрытое поле, необходимое для заполнения массива $_SESSION , за которым следует кнопка ввода и отправки файла для загрузки. Отправка формы вызовет функцию JavaScript с именем startUpload() которая будет определяться включенным файлом JavaScript.

Внизу страницы находится скрытый фрейм, в котором будет размещена форма, и импорт файла script.js .

Добавить стиль

Следующий файл style.css довольно прост. Я определил размер контейнера индикатора выполнения и присвоил ему черную рамку размером 1 пиксель, цвет индикатора выполнения при загрузке, а также iframe и индикатор выполнения скрыты.

 #bar_blank { border: solid 1px #000; height: 20px; width: 300px; } #bar_color { background-color: #006666; height: 20px; width: 0px; } #bar_blank, #hidden_iframe { display: none; } 

Функциональность на стороне клиента

Файл script.js является самым большим из группы файлов. Он содержит шесть функций, которые я расскажу ниже. Многим нравится использовать jQuery для предоставления некоторых функций здесь, и вы, безусловно, можете сделать это, если хотите, но я лично предпочитаю подход старой школы. Подобно тому, как японцы придают большее значение товарам ручной работы, я просто чувствую большую страсть к коду, если он мой.

 function toggleBarVisibility() { var e = document.getElementById("bar_blank"); e.style.display = (e.style.display == "block") ? "none" : "block"; } function createRequestObject() { var http; if (navigator.appName == "Microsoft Internet Explorer") { http = new ActiveXObject("Microsoft.XMLHTTP"); } else { http = new XMLHttpRequest(); } return http; } function sendRequest() { var http = createRequestObject(); http.open("GET", "progress.php"); http.onreadystatechange = function () { handleResponse(http); }; http.send(null); } function handleResponse(http) { var response; if (http.readyState == 4) { response = http.responseText; document.getElementById("bar_color").style.width = response + "%"; document.getElementById("status").innerHTML = response + "%"; if (response < 100) { setTimeout("sendRequest()", 1000); } else { toggleBarVisibility(); document.getElementById("status").innerHTML = "Done."; } } } function startUpload() { toggleBarVisibility(); setTimeout("sendRequest()", 1000); } (function () { document.getElementById("myForm").onsubmit = startUpload; })(); 

Функция toggleBarVisibility() устанавливает соответствующий стиль в div «bar_blank» для отображения или скрытия индикатора выполнения по мере необходимости. Первоначально он начинается скрытно, но будет отображаться после начала загрузки и снова скрываться после завершения загрузки.

Функция createRequestObject() создает объект XMLHttpRequest или ActiveXObject на основе браузера пользователя. Вероятно, это функция, которую большинство людей хотели бы использовать в jQuery или какой-либо другой среде JavaScript.

Функция sendRequest() запрашивает файл progress.php с помощью запроса GET, а затем вызывает handleResponse() для обработки возвращаемых данных.

Функция handleResponse() обрабатывает ответ от файла progress.php который будет иметь значение от 1 до 100 в зависимости от процесса загрузки файла. Я также обновляю div «status» соответствующим значением. Если текущий процент ниже 100, то я вызываю встроенную в JavaScript функцию setTimeout() чтобы через 1 секунду отправить другой запрос на обновление (вы можете изменить это значение соответствующим образом), в противном случае я снова скрываю индикатор выполнения и устанавливаю статус в «Выполнено.»

Функция startUpload() делает панель загрузки видимой и отправляет запрос на обновление после задержки в 1 секунду. Эта небольшая задержка необходима для того, чтобы начать загрузку.

Последняя функция — это startUpload() анонимная функция, которая регистрирует startUpload() с событием отправки формы.

Прогресс в реальном времени

Последний файл, который объединяет все, — это файл progress.php :

 <?php session_start(); $key = ini_get("session.upload_progress.prefix") . "myForm"; if (!empty($_SESSION[$key])) { $current = $_SESSION[$key]["bytes_processed"]; $total = $_SESSION[$key]["content_length"]; echo $current < $total ? ceil($current / $total * 100) : 100; } else { echo 100; } 

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

Информация о передаче основывается на конкатенации значения директивы session.upload_progress.prefix и скрытого значения поля session.upload_progress.name . Поскольку моя форма прошла «myForm», ключ сеанса определяется с помощью ini_get("session.upload_progress.prefix") . "myForm" ini_get("session.upload_progress.prefix") . "myForm" .

Вот скриншот индикатора выполнения в действии:

загрузить индикатор выполнения

Тонкая настройка поведения

PHP предоставляет некоторые дополнительные директивы, чтобы помочь точно настроить поведение загрузки сеанса, о которой вы должны знать. Например, session.upload_progress.cleanup , для которого по умолчанию установлено значение 1, очищает дополнительные данные сеанса сразу после завершения загрузки. Вы должны быть осторожны, чтобы избежать потенциального состояния гонки.

Посмотрите еще раз на код в progress.php и вы заметите, что я проверяю, является ли $_SESSION[$key] пустым или нет, прежде чем продолжить. Мои функции JavaScript отключаются каждую секунду до тех пор, пока результат, полученный из progress.php будет меньше 100. Если включен session.upload_progress.cleanup и мой скрипт извлекает 99% загрузки, а через 1/2 секунды загрузка завершается $_SESSION[$key] не будет существовать для следующей проверки. Если я не учту это, тогда моя функция JavaScript может продолжать работать даже после завершения загрузки.

Еще две директивы: session.upload_progress.freq и session.upload_progress.min_freq которые определяют, как часто следует обновлять сеанс. Значение freq может быть задано либо в байтах (т.е. 100), либо в процентах от общего количества байтов (т.е. 2%). Значение min_freq дается в секундах и указывает минимальное количество секунд между обновлениями. Очевидно, что если min_freq был настроен на обновление каждую 1 секунду, для вашего JavaScript было бы бессмысленно проверять каждые 100 миллисекунд.

Резюме

Теперь у вас должно быть четкое представление о том, как создать индикатор выполнения для загрузки файлов с помощью функции выполнения загрузки сеанса. В дальнейшем я рекомендую вам поэкспериментировать с загрузкой нескольких файлов, давая возможность отменить $_SESSION[$key]["cancel_upload"] загрузку, используя $_SESSION[$key]["cancel_upload"] или любые другие идеи, которые ваш разум может придумать. Пожалуйста, поделитесь в комментариях своим опытом и улучшениями, которые вы сделали для других, чтобы извлечь из этого пользу!

Изображение через файл 404 / Shutterstock