Итак, я пытаюсь обслуживать большие файлы с помощью сценария PHP, они не находятся в веб-каталоге, поэтому это лучший способ предоставить к ним доступ.
Единственный способ, который я мог придумать с самого начала для обслуживания этого файла, - это загрузить его в память (fopen, fread и т. д.), Установить данные заголовка на правильный тип MIME, а затем просто повторить все содержимое файла.
Проблема в том, что мне нужно сразу загрузить эти файлы размером ~ 700 МБ в память и хранить все там до завершения загрузки. Было бы неплохо, если бы я мог транслировать нужные мне части по мере их загрузки.
Любые идеи?






Вам не нужно читать все целиком - просто войдите в цикл, читая его, скажем, фрагментами по 32 КБ и отправляя его в качестве вывода. А еще лучше использовать fpassthru, который делает то же самое ...
$name = 'mybigfile.zip';
$fp = fopen($name, 'rb');
// send the right headers
header("Content-Type: application/zip");
header("Content-Length: " . filesize($name));
// dump the file and stop the script
fpassthru($fp);
exit;
еще меньше строк, если вы используете файл для чтения, которому не нужен вызов fopen ...
$name = 'mybigfile.zip';
// send the right headers
header("Content-Type: application/zip");
header("Content-Length: " . filesize($name));
// dump the file and stop the script
readfile($name);
exit;
Если вы хотите стать еще симпатичнее, вы можете поддержать заголовок Content-Range, который позволяет клиентам запрашивать определенный диапазон байтов вашего файла. Это особенно полезно для передачи файлов PDF в Adobe Acrobat, который просто запрашивает фрагменты файла, необходимые для визуализации текущей страницы. Это немного сложно, но см. это для примера.
Спасибо! Я не понимал, что могу обойтись Content-Range с Adobe.
fpassthru () был сообщил, поскольку он загружает весь файл в память. Не подходит для больших файлов.
И readfile, и fpassthru для меня не работают с ошибками, связанными с памятью: Fatal error: Allowed memory size of 268435456 bytes exhausted (tried to allocate 1883504640 bytes) ...
Взгляните на fpassthru (). В более поздних версиях PHP это должно обслуживать файлы, не сохраняя их в памяти, как утверждает этот комментарий.
В то время как fpassthru() был моим первым выбором в прошлом, руководство по PHP фактически рекомендует * использовать вместо него readfile(), если вы просто выгружаете файл клиенту как есть.
* «Если вы просто хотите выгрузить содержимое файла в выходной буфер, без предварительного изменения его или поиска определенного смещения, вы можете использовать readfile (), которая избавляет вас от вызова fopen ()». —Руководство по PHP
Лучший способ отправлять большие файлы с помощью php - это заголовок X-Sendfile. Это позволяет веб-серверу намного быстрее обслуживать файлы с помощью механизмов нулевого копирования, таких как sendfile(2). Он поддерживается lighttpd и apache с плагин.
Пример:
$file = "/absolute/path/to/file"; // can be protected by .htaccess
header('X-Sendfile: '.$file);
header('Content-type: application/octet-stream');
header('Content-Disposition: attachment; filename = "'.basename($file).'"');
// other headers ...
exit;
Сервер считывает заголовок X-Sendfile и отправляет файл.
Одним из преимуществ fpassthru () является то, что эта функция может работать не только с файлами, но и с любым допустимым дескриптором. Розетка например.
И readfile () должен быть немного быстрее из-за использования механизма кеширования ОС, если это возможно (например, file_get_contents ()).
Еще один совет. fpassthru () удерживает дескриптор в открытом состоянии до тех пор, пока клиент не получит контент (что может занять довольно много времени при медленном подключении), и поэтому вы должны использовать некоторый механизм блокировки, если возможна параллельная запись в этот файл.
Все ответы Python хороши. Но есть ли причина, по которой вы не можете сделать доступным в Интернете каталог, содержащий символические ссылки на фактические файлы? Может потребоваться дополнительная настройка сервера, но она должна работать.
Если ваши файлы недоступны для веб-сервера, потому что путь не находится в вашем каталоге веб-обслуживания (htdocs), вы можете создать символическую ссылку (символическую ссылку) на эту папку в своем каталоге веб-обслуживания, чтобы избежать прохождения всего трафика через php.
Вы можете сделать что-то вроде этого
ln -s /home/files/big_files_folder /home/www/htdocs
Использование php для обслуживания статических файлов намного медленнее, если у вас большой трафик, потребление памяти будет очень большим, и он может не обрабатывать большое количество запросов.
Странно, ни fpassthru (), ни readfile () у меня этого не делали, всегда была ошибка памяти. Я прибег к использованию passthru () без буквы "f":
$name = 'mybigfile.zip';
// send the right headers
header("Content-Type: application/zip");
header("Content-Length: " . filesize($name));
// dump the file and stop the script
passthru('/bin/cat '.$filename);
exit;
эта команда выполняет команду Unix 'cat' и отправляет ее вывод в браузер.
комментарий для slim: причина, по которой вы просто не помещаете где-то символическую ссылку, - это веб-пространство - БЕЗОПАСНОСТЬ.
Если вы хотите сделать это правильно, только PHP не сможет этого сделать. Вы могли бы обслуживать файл с помощью Nginx X-Accel-Redirect(Рекомендуемые) или Apache X-Sendfile, которые созданы именно для этой цели.
Я включу в этот ответ текст, найденный на этом статья.
NGINX правильно справляется со всеми этими вещами. Итак, давайте обработаем проверки разрешений в приложении и позволим NGINX обслуживать фактический файл. Именно здесь и вступают в силу внутренние перенаправления. Идея проста: вы можете настроить запись местоположения, как обычно, при обслуживании обычных файлов.
Добавьте это в свой серверный блок nginx:
location /protected_files/ {
internal;
alias /var/www/my_folder_with_protected_files/;
}
В вашем проекте потребуется пакет HTTP Foundation:
composer require symfony/http-foundation
Подавайте файлы на PHP с помощью Nginx:
use Symfony\Component\HttpFoundation\BinaryFileResponse;
$real_path = '/var/www/my_folder_with_protected_files/foo.pdf';
$x_accel_redirect_path = '/protected_files/foo.pdf';
BinaryFileResponse::trustXSendfileTypeHeader();
$response = new BinaryFileResponse( $real_path );
$response->headers->set( 'X-Accel-Redirect', $accel_file );
$response->sendHeaders();
exit;
Это должно быть основным, что вам нужно для начала работы.
Вот более полный пример работы со встроенным PDF-файлом:
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;
$real_path = '/var/www/my_folder_with_protected_files/foo.pdf';
$x_accel_redirect_path = '/protected_files/foo.pdf';
$file = new File( $file_path );
BinaryFileResponse::trustXSendfileTypeHeader();
$response = new BinaryFileResponse( $file_path );
$response->setImmutable( true );
$response->setPublic();
$response->setAutoEtag();
$response->setAutoLastModified();
$response->headers->set( 'Content-Type', 'application/pdf' );
$response->headers->set( 'Content-Length', $file->getSize() );
$response->headers->set( 'X-Sendfile-Type', 'X-Accel-Redirect' );
$response->headers->set( 'X-Accel-Redirect', $accel_file );
$response->headers->set( 'X-Accel-Expires', 60 * 60 * 24 * 90 ); // 90 days
$response->headers->set( 'X-Accel-Limit-Rate', 10485760 ); // 10mb/s
$response->headers->set( 'X-Accel-Buffering', 'yes' );
$response->setContentDisposition( ResponseHeaderBag::DISPOSITION_INLINE, basename( $file_path ) ); // view in browser. Change to DISPOSITION_ATTACHMENT to download
$response->sendHeaders();
exit;
Они недоступны из-за аутентификации?