Ответ 1
Если вы отправляете действительно большие файлы и беспокоитесь об этом, вы можете использовать заголовок x-sendfile.
Из SOQ using-xsendfile-with-apache-php, howto blog.adaniels.nl: how- я-PHP-х-SendFile/
У меня есть php script на сервере для отправки файлов получателям: они получают уникальную ссылку, а затем могут загружать большие файлы. Иногда возникает проблема с передачей, и файл поврежден или никогда не заканчивается. Мне интересно, есть ли лучший способ отправить большие файлы
код:
$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- я-PHP-х-SendFile/
Лучшим решением было бы полагаться на lighty или apache, но если бы в PHP я использовал PEAR HTTP_Download (нет необходимости изобретать велосипед и т.д.), имеет несколько приятных функций, например:
Файлы chunking - это самый быстрый/простой метод в PHP, если вы не можете или не хотите использовать что-то более профессиональное, например cURL, mod-xsendfile
на Apache или посвященный script.
$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
), по-видимому, являются единственным вариантом в этом случае.
РЕДАКТИРОВАТЬ. Сделайте 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 убил script от работы через 2 минуты 30 секунд или около того... это предотвращает эту проблему и скрывает фактический файл.
Затем вы можете настроить CRON-задание для регулярного удаления символических ссылок....
Весь этот процесс затем отправит файл в браузер, и не имеет значения, как долго он работает, поскольку он не является script.
Мы использовали это в нескольких проектах, и до сих пор он работает довольно хорошо:
/**
* Copy a file 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();
Я не уверен, что это хорошая идея для больших файлов. Если поток для загрузки script выполняется до тех пор, пока пользователь не завершит загрузку, и вы запускаете что-то вроде 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 ( 'ни');
Это проверено на файлах размером 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();
}