Подтвердить что ты не робот

Загрузка больших файлов в PHP

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

4b9b3361

Ответ 2

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

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

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

Ответ 3

Файлы 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, чтобы вытолкнуть кусок файла в браузер.

Ответ 4

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

Ответ 5

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

/**
 * 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);
    }
}

Ответ 6

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

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

Ответ 7

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

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

Ответ 8

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

Ответ 9

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();

Ответ 10

Я не уверен, что это хорошая идея для больших файлов. Если поток для загрузки script выполняется до тех пор, пока пользователь не завершит загрузку, и вы запускаете что-то вроде Apache, только 50 или более одновременных загрузок могут привести к краху вашего сервера, потому что Apache не предназначен для запуска большого количества длинных - одновременное использование потоков. Конечно, я могу ошибаться, если поток apache каким-то образом заканчивается, и загрузка находится в буфере где-то, пока идет загрузка.

Ответ 11

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

Ответ 12

У меня была такая же проблема, моя проблема решена путем добавления этого перед началом сеанса session_cache_limiter ( 'ни');

Ответ 13

Это проверено на файлах размером 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();
}