Можно ли определить завершение скачивания файла клиентом с сервера?
Всем привет. Задался вопросом, можно ли как-то определить начало загрузки и окончание загрузки файла с сервера клиентом сайта? Почитал разные материалы, и с началом загрузки более менее понятно, а вот вроде как ни php ни js не может отследить завершение процесса загрузки, т.к. это все происходит с сервера напрямую. Также пишут, что сервер все действия логирует. Тогда у меня возникает такой вопрос, можно ли каким то образом на стороне сервера отследить начало процесса загрузки файла и завершения этого процесса, а по завершению, например, отправить cron запрос на какой нибудь URL и обработать его?
Ответы (1 шт):
Как и предполагалось, исходный вопрос представляет из себя классический пример проблемы XY. Если вы хотите отдать пользователю файл, используя уникальное имя, скорее всего вы хотите сделать так, чтобы кроме этого пользователя, ссылкой не смог воспользоваться кто-нибудь ещё. То есть я предполагаю, что с помощью какого-то механизма аутентификации собственно пользователя вы уже авторизовали, и удостоверились в том, что права на скачивание этого файла у пользователя есть.
Отдача файла клиенту средствами PHP
Начнём с самых простых вариантов. Отдать содержимое файла вы можете исключительно средствами самого PHP, например, с помощью функции readfile():
# предплагается, что архив '/full/path/to/file.zip' уже создан
$file = '/full/path/to/file.zip';
$filename = basename($file);
header('Content-Type: application/zip');
header("Content-Disposition: attachment; filename=\"{$filename}\"");
readfile($file);
# если после отправки клиенту файл надо удалить
unlink($file);
С помощью HTTP-заголовка Content-Disposition вы можете указать любое имя, хотя насколько я понимаю вопрос, уникальное имя файла вы собирались использовать только для того, чтобы предотвратить скачивание этого же файла по такой же ссылке другими пользователями.
Естественно, если этот блок кода будет выполняться только для авторизованных пользователей, "уникальные" имена файлов вам становятся не нужны. Более того, вы можете заранее приготовить архивы со всеми нужными файлами, и не архивировать каждый раз заново исходный файл. Если же свободное место на сервере вам важнее, чем нагрузка на процессор, вы можете создавать архив с файлом "на лету":
$file = '/full/path/to/file.ext';
$filename = pathinfo($file, PATHINFO_FILENAME);
header('Content-Type: application/zip');
header("Content-Disposition: attachment; filename=\"{$filename}.zip\"");
// -q: "тихий" режим работы (без вывода сообщений в STDOUT/STDERR)
// -j: не сохранять в архиве пути к файлам
// -: вывод в STDOUT
$zipcmd = "zip -q -j - " . escapeshellarg($file);
$zip = popen($zipcmd, 'r');
fpassthru($zip);
pclose($zip);
Если вы не хотите привязываться к консольным утилитам типа zip, в качестве альтернативного варианта вы можете использовать сторонние библиотеки, например, ZipStream-PHP (GitHub, документация).
Отдача файла клиенту посредством веб-сервера
Более производительным решением будет использование специальных возможностей веб-серверов для подобных задач. Вместо содержимого файла, ваш PHP-скрипт должен вернуть вышестоящему веб-серверу специальный заголовок, содержащий путь к файлу относительно корня файловой системы сервера. Естественно, заголовок этот вы должны использовать только в ответе для авторизованного пользователя.
Веб-сервер lighttpd поддерживает для этого специальный заголовок X-Sendfile, в Apache этот же механизм можно использовать с помощью стороннего модуля mod_xsendfile (GitHub, домашняя страница). В nginx похожий механизм реализован с помощью заголовка X-Accel-Redirect (документация доступна в архивной копии бывшего Nginx Wiki на GitHub: XSendfile, X-Accel). Стоит также заметить, что возможности nginx в данном случае шире - переданный в заголовке путь рассматривается как URI, которому обычно сопоставляется отдельный блок конфигурации. Помимо раздачи статического содержимого, он может проксировать запрос на другой сервер (например, на какой-нибудь CDN, в этом случае nginx сработает в привычной для себя роли reverse proxy сервера), или же вообще обрабатывать его совершенно произвольным образом.
Можно ли определить завершение скачивания файла клиентом с сервера?
Не думаю, что этот вопрос после всего вышеизложенного остаётся актуальным, но теоретически вы можете узнать, полностью ли отработал вызов функции readfile(), проверив после её вызова результат функции connection_aborted(). Если окажется, что соединение уже разорвано, скорее всего readfile() не успел отработать до конца. Для того, чтобы выполнение PHP-скрипта не было прервано после обрыва связи с клиентом, предварительно нужно вызвать функцию ignore_user_abort(true).
Данная информация носит исключительно теоретический характер и на практике мной не проверялась!