У меня есть php-скрипт на сервере для отправки файлов получателям: они получают уникальную ссылку, а затем могут загружать большие файлы. Иногда возникает проблема с передачей, и файл поврежден или никогда не заканчивается. Мне интересно, есть ли лучший способ отправить большие файлы
Код:
$f = fopen(DOWNLOAD_DIR.$database[$_REQUEST['fid']]['filePath'], 'r'); while(!feof($f)){ print fgets($f, 1024); } fclose($f);
Я видел такие функции, как
http_send_file http_send_data
Но я не уверен, что они будут работать.
Каков наилучший способ решить эту проблему?
С уважением
erwing
Если вы отправляете действительно большие файлы и беспокоитесь о том, какое влияние это будет иметь место, вы можете использовать заголовок x-sendfile.
Из SOQ using-xsendfile-with-apache-php , howto blog.adaniels.nl: how-i-php-x-sendfile /
Лучшим решением было бы полагаться на lighty или apache, но если бы в PHP я использовал HTTP_Download PEAR (нет необходимости изобретать колесо и т. Д.), Есть некоторые интересные функции, такие как:
См. Ввод / использование документов .
Файлы chunking – это самый быстрый / простой метод в PHP, если вы не можете или не хотите использовать что-то более профессиональное, например cURL , mod-xsendfile
в Apache или какой-то выделенный скрипт .
$filename = $filePath.$filename; $chunksize = 5 * (1024 * 1024); //5 MB (= 5 242 880 bytes) per one chunk of file. if(file_exists($filename)) { set_time_limit(300); $size = intval(sprintf("%u", filesize($filename))); header('Content-Type: application/octet-stream'); header('Content-Transfer-Encoding: binary'); header('Content-Length: '.$size); header('Content-Disposition: attachment;filename="'.basename($filename).'"'); if($size > $chunksize) { $handle = fopen($filename, 'rb'); while (!feof($handle)) { print(@fread($handle, $chunksize)); ob_flush(); flush(); } fclose($handle); } else readfile($path); exit; } else echo 'File "'.$filename.'" does not exist!';
Портирован с сайта richnetapps.com / NeedBee . Протестировано на 200 МБ файлах, на которых readfile()
, даже с максимальным допустимым пределом памяти, установленным в 1G
, что в пять раз больше, чем загруженный размер файла.
BTW: Я тестировал это также в файлах >2GB
, но PHP только успел написать первые 2GB
файла, а затем сломал соединение. Функции, связанные с файлом (fopen, fread, fseek), используют INT, поэтому вы в конечном итоге достигли предела в 2GB
. Вышеупомянутые решения (то есть mod-xsendfile
), по-видимому, являются единственным вариантом в этом случае.
EDIT : сделайте 100%, чтобы ваш файл был сохранен в utf-8
. Если вы опустите это, загруженные файлы будут повреждены. Это потому, что в этих решениях используется print
чтобы направить кусок файла в браузер.
Создайте символическую ссылку на фактический файл и сделайте ссылку для ссылки на символическую ссылку. Затем, когда пользователь нажимает на ссылку DL, они получат загрузку файла из реального файла, но названный из символической ссылки. Для создания символической ссылки требуется миллисекунды, и лучше, чем пытаться скопировать файл на новое имя и загрузить оттуда.
Например:
<?php // validation code here $realFile = "Hidden_Zip_File.zip"; $id = "UserID1234"; if ($_COOKIE['authvalid'] == "true") { $newFile = sprintf("myzipfile_%s.zip", $id); //creates: myzipfile_UserID1234.zip system(sprintf('ln -s %s %s', $realFile, $newFile), $retval); if ($retval != 0) { die("Error getting download file."); } $dlLink = "/downloads/hiddenfiles/".$newFile; } // rest of code ?> <a href="<?php echo $dlLink; ?>Download File</a>
Это то, что я сделал, потому что Go Daddy убивает скрипт от запуска через 2 минуты 30 секунд или около того … это предотвращает эту проблему и скрывает фактический файл.
Затем вы можете настроить CRON-задание для регулярного удаления символических ссылок ….
Затем весь этот процесс отправит файл в браузер, и не имеет значения, как долго он работает, поскольку это не скрипт.
Мы использовали это в нескольких проектах, и до сих пор он работает очень хорошо:
/** * Copy a file's content to php://output. * * @param string $filename * @return void */ protected function _output($filename) { $filesize = filesize($filename); $chunksize = 4096; if($filesize > $chunksize) { $srcStream = fopen($filename, 'rb'); $dstStream = fopen('php://output', 'wb'); $offset = 0; while(!feof($srcStream)) { $offset += stream_copy_to_stream($srcStream, $dstStream, $chunksize, $offset); } fclose($dstStream); fclose($srcStream); } else { // stream_copy_to_stream behaves() strange when filesize > chunksize. // Seems to never hit the EOF. // On the other handside file_get_contents() is not scalable. // Therefore we only use file_get_contents() on small files. echo file_get_contents($filename); } }
Для загрузки файлов самым простым способом, я могу думать, было бы разместить файл во временном месте и предоставить им уникальный URL-адрес, который можно загрузить через обычный HTTP.
В качестве части, генерирующей эти ссылки, вы также можете удалить файлы, которые были больше, чем X часов.
Когда я это делал в прошлом, я использовал это:
set_time_limit(0); //Set the execution time to infinite. header('Content-Type: application/exe'); //This was for a LARGE exe (680MB) so the content type was application/exe readfile($fileName); //readfile will stream the file.
Эти три строки кода будут выполнять всю работу загружаемого файла readfile (), который передаст весь файл, указанный клиенту, и обязательно установите неограниченный временной интервал, иначе у вас может закончиться время до того, как файл завершит потоковое вещание.
Если вы используете lighttpd в качестве веб-сервера, альтернативой безопасным загрузкам будет использование ModSecDownload . Он нуждается в конфигурации сервера, но вы позволяете веб-серверу обрабатывать загрузку, а не скрипт PHP.
Создание URL-адреса загрузки будет выглядеть так (взято из документации), и оно, конечно же, может быть создано только для авторизованных пользователей:
<?php $secret = "verysecret"; $uri_prefix = "/dl/"; # filename # please note file name starts with "/" $f = "/secret-file.txt"; # current timestamp $t = time(); $t_hex = sprintf("%08x", $t); $m = md5($secret.$f.$t_hex); # generate link printf('<a href="%s%s/%s%s">%s</a>', $uri_prefix, $m, $t_hex, $f, $f); ?>
Конечно, в зависимости от размера файлов использование readfile()
например, предложенное Unkwntech , превосходно. И использование xsendfile, предложенное garrow, является еще одной хорошей идеей, также поддерживаемой Apache.
header("Content-length:".filesize($filename)); header('Content-Type: application/zip'); // ZIP file header('Content-Type: application/octet-stream'); header('Content-Disposition: attachment; filename="downloadpackage.zip"'); header('Content-Transfer-Encoding: binary'); ob_end_clean(); readfile($filename); exit();
Я не уверен, что это хорошая идея для больших файлов. Если поток для вашего скрипта загрузки выполняется до тех пор, пока пользователь не завершит загрузку, и вы используете что-то вроде Apache, только 50 или более одновременных загрузок могут привести к краху вашего сервера, потому что Apache не предназначен для запуска большого количества длительных потоков одновременно. Конечно, я могу ошибаться, если поток apache каким-то образом заканчивается, и загрузка находится где-то в буфере, пока идет загрузка.
Я использовал следующий фрагмент, найденный в комментариях к ручному входу php для файла readfile:
function _readfileChunked($filename, $retbytes=true) { $chunksize = 1*(1024*1024); // how many bytes per chunk $buffer = ''; $cnt =0; // $handle = fopen($filename, 'rb'); $handle = fopen($filename, 'rb'); if ($handle === false) { return false; } while (!feof($handle)) { $buffer = fread($handle, $chunksize); echo $buffer; ob_flush(); flush(); if ($retbytes) { $cnt += strlen($buffer); } } $status = fclose($handle); if ($retbytes && $status) { return $cnt; // return num. bytes delivered like readfile() does. } return $status; }
У меня была такая же проблема, моя проблема решена путем добавления этого перед началом сеанса session_cache_limiter ('none');
Это проверено на файлы размером 200+ МБ на сервере с ограничением памяти 256 МБ.
header('Content-Type: application/zip'); header("Content-Disposition: attachment; filename=\"$file_name\""); set_time_limit(0); $file = @fopen($filePath, "rb"); while(!feof($file)) { print(@fread($file, 1024*8)); ob_flush(); flush(); }