Vous pouvez utiliser popen()
(docs) ou proc_open()
(docs) pour exécuter une commande unix (par exemple. zip ou gzip), et de revenir stdout comme php stream. flush()
(docs) fera de son mieux pour pousser le contenu de php est sortie de la mémoire tampon du navigateur.
Combinant tout cela va vous donner ce que vous voulez (à condition que rien d'autre n'est dans la manière -- voir les esp. les mises en garde sur les docs page pour flush()
).
(Remarque: ne pas utiliser flush()
. Voir la mise à jour ci-dessous pour plus de détails.)
Quelque chose comme ce qui suit peut faire l'affaire:
<?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);
Vous m'avez demandé "d'autres technologies": à qui je vais le dire, "tout ce qui appuie le non-blocage i/o pour l'ensemble du cycle de vie de la demande". Vous pourriez construire un tel composant en tant que serveur autonome en Java ou en C/C++ (ou une des nombreuses autres langues disponibles), si vous étiez prêt à entrer dans le "down and dirty" de non-blocage de l'accès au fichier et autres joyeusetés.
Si vous voulez un non-blocage de la mise en œuvre, mais vous préférez éviter le "down and dirty", le plus simple (à mon humble avis) serait d'utiliser nodeJS. Il y a beaucoup de soutien pour toutes les fonctionnalités dont vous avez besoin dans la version existante de nodejs: utilisation de l' http
module (bien sûr) pour le serveur http et à l'utilisation ( child_process
module pour frayer le tar/zip/autre pipeline.
Enfin, si (et seulement si) vous êtes en cours d'exécution d'un multi-processeur (ou multi-core) du serveur, et vous voulez le plus de nodejs, vous pouvez utiliser Spark2 d'exécuter plusieurs instances sur le même port. Ne pas exécuter plus d'un nodejs instance par processeur-core.
Mise à jour (à partir de Benji excellents commentaires dans la section des commentaires sur cette réponse)
1. Les docs pour fread()
indiquent que la fonction est en lecture seule jusqu'à 8192 octets de données à un moment de tout ce qui n'est pas un fichier régulier. Par conséquent, 8192 peut être un bon choix de la taille de la mémoire tampon.
[note de la rédaction] 8192 est presque certainement une plate-forme dépendante de la valeur -- sur la plupart des plates-formes, fread()
va lire des données jusqu'à ce que le système d'exploitation interne de la mémoire tampon est vide, à quel point il sera de retour, permettant à l'os de remplissage de la mémoire tampon de nouveau de manière asynchrone. 8192 est la taille de la mémoire tampon par défaut sur de nombreux systèmes d'exploitation courants.
Il y a d'autres circonstances qui peuvent causer fread pour revenir encore moins de 8192 octets -- par exemple, la "distance" du client (ou de processus) est lent à remplir la mémoire tampon dans la plupart des cas, fread()
renverra le contenu de la mémoire tampon d'entrée comme telle, sans attendre pour elle d'obtenir la pleine. Cela pourrait signifier n'importe où à partir de 0..os_buffer_size octets sont retournées.
La morale est: la valeur que vous avez passer à l' fread()
comme buffsize
doit être considéré comme un "maximum" de taille, ne supposez jamais que vous avez reçu le nombre d'octets que vous avez demandé (ou tout autre nombre d'ailleurs).
2. Selon les commentaires sur fread docs, quelques mises en garde: magic quotes peuvent interférer et doit être éteint.
3. Paramètre mb_http_output('pass')
(docs) peut être une bonne idée. Si 'pass'
est déjà la valeur par défaut, vous devrez peut-être spécifier explicitement si votre code ou de configuration a été modifié de quelque chose d'autre.
4. Si vous êtes à la création d'un zip (par opposition à gzip), vous souhaitez utiliser le type de contenu d'en-tête:
Content-type: application/zip
ou... "application/octet-stream" peut être utilisé à la place. (c'est un générique de type de contenu utilisé pour les téléchargements de toutes sortes):
Content-type: application/octet-stream
et si vous voulez que l'utilisateur soit invité à télécharger et enregistrer le fichier sur le disque (plutôt que risque d'avoir le navigateur essayez d'afficher le fichier en tant que texte), alors vous aurez besoin de l'-tête content-disposition. (d'où le nom de fichier indique le nom qui devrait être proposé dans la boîte de dialogue enregistrer):
Content-disposition: attachment; filename="file.zip"
On doit aussi envoyer le Contenu de l'en-tête de longueur, mais c'est dur avec cette technique que vous ne connaissez pas le zip de la taille exacte à l'avance. Est-il un en-tête qui peut être réglé pour indiquer que le contenu est "streaming" ou est de longueur inconnue? Quelqu'un sait?
Enfin, voici un exemple révisé qui utilise tous @Benji suggestions (et qui crée un fichier ZIP à la place d'un TAR.Fichier 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);
Mise à jour: (2012-11-23) j'ai découvert que l'appelant flush()
dans la lecture/l'écho de la boucle peut causer des problèmes lorsque vous travaillez avec des fichiers très volumineux et/ou très lente. Au moins, cela est vrai lorsque vous utilisez PHP comme cgi/fastcgi derrière Apache, et il semble probable que le même problème peut se produire lors de l'exécution dans d'autres configurations de trop. Le problème semble résulter lorsque PHP bouffées de chaleur de sortie de Apache plus rapide qu'Apache peut effectivement envoyer sur le support. Pour les très gros fichiers (ou des connexions lentes), ce qui va entrainer un dépassement de Apache interne du tampon de sortie. Cela provoque Apache pour tuer le processus PHP, qui, bien sûr, les causes de la télécharger à accrocher, ou se terminer prématurément, avec seulement un transfert partiel ayant eu lieu.
La solution est de ne pas appeler flush()
à tous. J'ai mis à jour les exemples de code ci-dessus pour en tenir compte, et j'ai mis une note dans le texte en haut de la réponse.