Вы можете использовать popen()
(документы) или proc_open()
(документы) для выполнения команды Unix (например, zip или gzip) и получить обратно стандартный вывод в видеPHP поток.flush()
(docs) сделает все возможное, чтобы передать содержимое буфера вывода php в браузер.
Объединение всего этого даст вам то, что вы хотите (при условии, что ничего большевстает на пути - см. предостережения на странице документации для flush()
).
( Примечание : не используйте flush()
. Подробности смотрите ниже в обновлении.)
Что-то вроде следующего может помочь:
<?php
// make sure to send all headers first
// Content-Type is the most important one (probably)
//
header('Content-Type: application/x-gzip');
// use popen to execute a unix command pipeline
// and grab the stdout as a php stream
// (you can use proc_open instead if you need to
// control the input of the pipeline too)
//
$fp = popen('tar cf - file1 file2 file3 | gzip -c', 'r');
// pick a bufsize that makes you happy (64k may be a bit too big).
$bufsize = 65535;
$buff = '';
while( !feof($fp) ) {
$buff = fread($fp, $bufsize);
echo $buff;
}
pclose($fp);
Вы спрашивали о «других технологиях»: на что я скажу «все, что поддерживаетблокировка ввода / вывода на весь жизненный цикл запроса ".Вы могли бы построить такой компонент, как автономный сервер на Java или C / C ++ (или любом из многих других доступных языков), , если , вы были готовы попасть в «грязный»блокировка доступа к файлам и еще много чего.
Если вы хотите неблокирующую реализацию, но вы бы предпочли избежать «грязного и грязного», самый простой путь (IMHO) - использовать nodeJS ,В существующей версии nodejs имеется достаточно поддержки для всех функций, которые вам нужны: используйте модуль http
(конечно) для http-сервера;и использовать модуль child_process
для запуска tar / zip / любого конвейера.
Наконец, если (и только если) вы используете многопроцессорный (или многоядерный) сервер, и вы хотитебольшинство из nodejs можно использовать Spark2 для запуска нескольких экземпляров на одном и том же порту.Не запускайте более одного экземпляра nodejs на каждое ядро процессора.
Обновление (из превосходных отзывов Бенджи в разделе комментариев к этому ответу)
1. Документы для fread()
указывают, что функция будет одновременно считывать до 8192 байтов данных из всего, что не является обычным файлом.Следовательно, 8192 может быть хорошим выбором размера буфера.
[примечание редакции] 8192 почти наверняка зависит от платформы - на большинстве платформ fread()
будет считывать данные до тех пор, пока внутренний буфер операционной системы не опустеет, и в этот момент он вернется, что позволит операционной системезаполнить буфер снова асинхронно.8192 - это размер буфера по умолчанию во многих популярных операционных системах.
Существуют и другие обстоятельства, которые могут заставить fread возвращать даже менее 8192 байт - например, «удаленный» клиент (или процесс) медленно заполняет буфер - в большинстве случаев fread()
будетвернуть содержимое входного буфера как есть, не дожидаясь его заполнения.Это может означать где-нибудь от 0..os_buffer_size возвращаемых байтов.
Мораль такова: значение, которое вы передаете fread()
как buffsize
, следует считать «максимальным» размером - никогда не думайте, что вымы получили количество байтов, которые вы запрашивали (или любое другое число по этому вопросу).
2. Согласно комментариям к fread docs, несколько предостережений: магические кавычки могут мешать и должны быть выключены .
3. Настройка mb_http_output('pass')
(документы) может быть хорошей идеей.Хотя 'pass'
уже является настройкой по умолчанию, вам может потребоваться указать ее явно, если ваш код или конфигурация ранее изменили ее на что-то другое.
4. Если вы создаетеzip (в отличие от gzip), вы можете использовать заголовок типа контента:
Content-type: application/zip
или ... вместо него можно использовать application / octet-stream.(это общий тип контента, используемый для бинарных загрузок всех видов):
Content-type: application/octet-stream
и если вы хотите, чтобы пользователю предлагалось загрузить и сохранить файл на диск (вместо того, чтобы попытаться запустить браузер)чтобы отобразить файл в виде текста), тогда вам понадобится заголовок content-disposition.(где имя файла указывает имя, которое следует предложить в диалоге сохранения):
Content-disposition: attachment; filename="file.zip"
Также следует отправить заголовок Content-length, но это сложно сделать с помощью этой техники, поскольку вы заранее не знаете точный размер почтового индекса. Есть ли заголовок, который можно установить, чтобы указать, что контент "потоковый" или имеет неизвестную длину?Кто-нибудь знает?
Наконец, вот пересмотренный пример, который использует все предложения Benji (и создает файл ZIP вместо файла TAR.GZIP):
<?php
// make sure to send all headers first
// Content-Type is the most important one (probably)
//
header('Content-Type: application/octet-stream');
header('Content-disposition: attachment; filename="file.zip"');
// use popen to execute a unix command pipeline
// and grab the stdout as a php stream
// (you can use proc_open instead if you need to
// control the input of the pipeline too)
//
$fp = popen('zip -r - file1 file2 file3', 'r');
// pick a bufsize that makes you happy (8192 has been suggested).
$bufsize = 8192;
$buff = '';
while( !feof($fp) ) {
$buff = fread($fp, $bufsize);
echo $buff;
}
pclose($fp);
Обновление : (2012-11-23) Я обнаружил, что вызов flush()
в цикле чтения / эхо может вызвать проблемы при работе с очень большимифайлы и / или очень медленные сети.По крайней мере, это верно при запуске PHP как cgi / fastcgi за Apache, и кажется вероятным, что та же проблема возникнет и при работе в других конфигурациях.Проблема возникает, когда PHP сбрасывает вывод в Apache быстрее, чем Apache может фактически отправить его через сокет.Для очень больших файлов (или медленных соединений) это в конечном итоге приводит к переполнению внутреннего выходного буфера Apache.Это заставляет Apache завершить процесс PHP, что, конечно, приводит к зависанию или преждевременному завершению загрузки, при этом происходит только частичная передача.
Решение - , а не для вызова flush()
на всех.Я обновил приведенные выше примеры кода, чтобы отразить это, и поместил примечание в текст в верхней части ответа.