Надежная загрузка больших файлов в PHP - PullRequest
24 голосов
/ 28 февраля 2009

У меня есть 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

Ответы [ 13 ]

11 голосов
/ 28 февраля 2009

Если вы отправляете действительно большие файлы и беспокоитесь о том, как это повлияет, вы можете использовать заголовок x-sendfile.

Из SOQ с использованием -xsendfile-with-apache-php , инструкции blog.adaniels.nl: how-i-php-x-sendfile /

8 голосов
/ 21 февраля 2014

Чанкинг файлов - это самый быстрый / простой метод в PHP, если вы не можете или не хотите использовать что-то более профессиональное, например, cURL , mod-xsendfile on 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, что в пять раз больше размера загружаемого файла.

Кстати: я проверял это также на файлах >2GB, но PHP удалось записать только сначала 2GB файла, а затем разорвал соединение. Связанные с файлами функции (fopen, fread, fseek) используют INT, поэтому вы в конечном итоге достигнете предела 2GB. Вышеупомянутые решения (т.е. mod-xsendfile), по-видимому, являются единственным вариантом в этом случае.

РЕДАКТИРОВАТЬ : Убедитесь, 100% , что ваш файл сохранен в utf-8. Если вы пропустите это, загруженные файлы будут повреждены. Это потому, что в этом решении используется print для передачи фрагмента файла в браузер.

8 голосов
/ 06 марта 2009

Лучшим решением было бы полагаться на lighty или apache, но если в PHP я бы использовал PEAR HTTP_Download (не нужно заново изобретать колесо и т. Д.), Имеет некоторые приятные функции, такие как:

  • Основной механизм дросселирования
  • Диапазоны (частичная загрузка и возобновление)

См. введение / использование документов .

3 голосов
/ 04 апреля 2012

Мы использовали это в нескольких проектах, и до сих пор оно работало довольно хорошо:

/**
 * 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);
    }
}
3 голосов
/ 08 ноября 2010

Создайте символическую ссылку на фактический файл и сделайте ссылку для загрузки точкой на символическую ссылку. Затем, когда пользователь нажимает на ссылку 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 для регулярного удаления символических ссылок ....

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

3 голосов
/ 28 февраля 2009

Для загрузки файлов самым простым способом, который я могу придумать, было бы поместить файл во временную папку и дать им уникальный URL-адрес, который они могут загрузить через обычный HTTP.

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

1 голос
/ 16 августа 2012
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();
1 голос
/ 28 февраля 2009

Если вы используете 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.

1 голос
/ 28 февраля 2009

Когда я делал это в прошлом, я использовал это:

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.

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

0 голосов
/ 29 ноября 2017

Это проверено на файлах размером более 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();
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...