LAMPE: Comment les créer .Zip de fichiers volumineux pour l'utilisateur à la volée, sans disque dur/PROCESSEUR raclée
Souvent un service web doit zip plusieurs fichiers de grande taille pour le téléchargement par le client. Le moyen le plus évident pour ce faire est de créer un temporaire de fichier zip, puis echo
à l'utilisateur ou l'enregistrer sur le disque et de rediriger (supprimer un certain temps dans l'avenir).
Cependant, en faisant les choses de cette façon a des inconvénients:
- une phase initiale intensive du PROCESSEUR et du disque raclée, résultant en...
- un considérable retard initial de l'utilisateur pendant que l'archive est préparé
- très forte empreinte mémoire par demande
- utilisation importante de l'espace disque temporaire
- si l'utilisateur annule le téléchargement de la moitié du chemin, toutes les ressources utilisées dans la phase initiale (CPU, mémoire, disque), aura été gaspillé
Solutions comme ZipStream-PHP améliorer sur ce à la pelle les données dans Apache fichier par fichier. Toutefois, le résultat est encore élevé, l'utilisation de la mémoire (les fichiers sont entièrement chargé en mémoire), et les grands, thrash des pics dans le disque et utilisation CPU.
En revanche, considérer les points suivants bash extrait de:
ls -1 | zip -@ - | cat > file.zip
# Note -@ is not supported on MacOS
Ici, zip
fonctionne en mode continu, résultant en une faible empreinte mémoire. Une pipe a une partie intégrante de la mémoire tampon lorsque la mémoire est pleine, le système d'exploitation suspend l'écriture du programme (programme sur la gauche de la pipe). Ici s'assure que zip
ne fonctionne vite que son signal de sortie peut être écrit par cat
.
La meilleure façon, alors, serait de faire la même chose: remplacer cat
avec un serveur web de traiter, streaming le fichier zip à l'utilisateur qu'il a créé sur la volée. Cela permettrait de créer peu de surcharge par rapport à la juste streaming les fichiers, et aurait sans problème, non hérissés profil des ressources.
Comment pouvez-vous obtenir sur une pile LAMP?
- Note: je suis en partie écrit cela à cause de la divers similar questions – apparaît comme un problème relativement fréquent, et n'a pas été très bien mis/encore répondu. ie Ont essayé d'écrire en streaming/PHP problème bien grave réponses seulement s'il vous plaît! (Suggestions pour améliorer le q beaucoup apprécié aussi.)
- Vous pourriez probablement utiliser Node.js. Je sais que ça a été utilisé pour analyser les en-têtes de fichier téléchargé (alors qu'ils téléchargées). Depuis votre plus grand contrôle sur les tampons d'e/S de PHP, je suppose que ça ne devrait pas être difficile d'écrire un fichier zip en temps réel.
Vous devez vous connecter pour publier un commentaire.
Vous pouvez utiliser
popen()
(docs) ouproc_open()
(docs) pour exécuter une commande unix (par exemple. zip ou gzip), et de revenir stdout comme php stream.flush()
(docs) va faire 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()
).(Note: 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:
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: utiliser le
http
module (bien sûr) pour le serveur http; et l'utilisationchild_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()
indiquer 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 à
fread()
commebuffsize
devrait ê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 désactivé.
3. Réglage
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:
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):
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):
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):
Mise à jour: (2012-11-23), j'ai découvert que l'appel de
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 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.fread
est en lecture seule jusqu'à 8192 octets de données à un moment de tout ce qui n'est pas un fichier régulier. 8192 peut donc être un bon choix de la taille de la mémoire tampon. (2.) Selon les commentaires surfread
docs, quelques mises en garde: magic quotes peuvent interférer et doit être désactivée; le réglage de mb_http_encoding('pass') " peut être une bonne idée. (3.)Peut-être que cette question est précisément à propos de zip, (qui est la seule option pour être au service des utilisateurs de la croix-plate-forme), de modifier les parties du code?"Content-type: application/zip"
(ouapplication/octet-stream
), etContent-disposition: attachment; filename="file.zip"
. On doit aussi de Contenu d'ensemble de la longueur, mais c'est dur avec cette technique que vous ne connaissez pas le zip de la taille exacte à l'avance.flush()
semble être inutile. (Testé avec apache en cours d'exécution mod_fastcgi.) Je soupçonne que la normale PHP et Apache de mise en mémoire tampon des comportements devenir hors de propos pour les gros téléchargements. Il semble que ça fonctionne comme suit: PHP remplit la mémoire tampon, et est suspendu jusqu'à ce que Apache envoie. Les aspects concrets de ce script sont 1. PHP ne tient jamais plus de 8192 octets en mémoire, 2.zip
fonctionne en mode continu et utilise peu de mémoire, 3. l'exécution est suspendue pendant que Apache efface (envoie) ses tampons.flush()
inutile -- je soupçonne que, dans certaines configurations (par exemple. mod_php) il peut être nécessaire si vous voulez minimiser le degré de choses sont mises en mémoire tampon par le serveur. Cependant, dans la plupart des cas, le serveur intégré de mise en mémoire tampon va être adapté à l'environnement de fonctionnement, et donc il peut être préférable d'omettre la chasse dans ces scénarios de trop.flush()
. Compte tenu de la complexité des différents tampons sur le chemin de PHP pour le navigateur de l'utilisateur, il est probablement quelque chose que les gens devraient tester sur la base de leur cas individuel/config.zip
sur votre serveur. sur mon poste de travail (mac OSX) le-j
option causes zip à jeter le chemin d'info, donczip -j foo/file1.jpg bar/file2.jpg
vous donnent une archive zip qui contenaitfile1.jpg
etfile2.jpg
"nu" (sans chemin d'info). Bien sûr, si vous avez rassemblé tous les fichiers source de réunir dans un même répertoire, puis il vous suffit de modifier ce répertoire avant d'appeler zip. Dans ce cas, vous auriez quelque chose comme ceci:cd /some/directory ; zip - file1.jpg file2.jpg file3.jpg
. (Ce qui ne devrait tous aller dans l'appel àpopen
, comme dans l'exemple ci-dessus).Content-Disposition
en-tête, en changeantfilename="file.zip"
. Ou l'êtes-vous demander comment faire pour renommer des fichiers individuels à l'intérieur du fichier zip? La meilleure façon de le faire serait de les renommer sur votre serveur de système de fichiers avant de créer le fichier zip. Autant que je sache, il n'y a pas moyen d'ajouter un fichier dans une archive zip en utilisant un nom différent du nom du fichier sur le disque.flush()
". Si vous utilisezflush()
de la mise en œuvre, veuillez consulter l'info, j'ai ajouté ci-dessus.$fp = popen("cd /some/long/path && zip -r - ./", "r");
find /path/ -iname "*.txt" -print | zip -@ -
.Une autre solution est de mon mod_zip module pour Nginx, écrit spécialement pour ce but:
https://github.com/evanmiller/mod_zip
Il est extrêmement léger et ne pas se prévaloir d'un "zip" ou de communiquer à travers des tuyaux. Il vous suffit de pointer vers un script qui répertorie les emplacements des fichiers à inclure, et mod_zip fait le reste.
Essaie de mettre en place une dynamique générée à télécharger avec beaucoup de fichiers avec différentes tailles, je suis tombé sur cette solution mais je croise les diverses erreurs de mémoire comme "Allowed memory size of 134217728 bytes exhausted à ...".
Après l'ajout de
ob_flush();
juste avant laflush();
la mémoire des erreurs disparaissent.Ensemble avec l'envoi des en-têtes, ma solution finale ressemble à ceci (Juste à stocker les fichiers à l'intérieur du zip sans structure de répertoire):
J'ai écrit cette s3 cuisson à la vapeur fichier zipper microservice le week-end dernier pourrait être utile: http://engineroom.teamwork.com/how-to-securely-provide-a-zip-download-of-a-s3-file-bundle/
Selon le manuel PHP, l'extension ZIP fournit un zip: wrapper.
Je ne l'ai jamais utilisé et je ne connais pas son fonctionnement interne, mais logiquement, il devrait être en mesure de faire ce que vous cherchez, en supposant que les archives ZIP peuvent être diffusés, dont je ne suis pas entièrement sûr de.
Quant à votre question sur la "pile LAMP" il ne devrait pas être un problème tant que PHP est pas configuré pour de tampon de sortie.
Edit: je suis en train de mettre une preuve de concept, mais cela ne semble pas trivial. Si vous n'êtes pas expérimenté avec PHP de cours d'eau, il peut se révéler trop compliqué, si c'est encore possible.
Edit(2): en relisant votre question après avoir pris un coup d'oeil à ZipStream, j'ai trouvé ce que va être votre principal problème ici, quand vous dites (italiques ajoutés)
Que la partie sera très difficile à mettre en œuvre parce que je ne pense pas que PHP fournit un moyen de déterminer comment complète d'Apache du tampon est. Donc, la réponse à votre question est non, vous ne serez probablement pas en mesure de le faire en PHP.
Il semble, vous pouvez éliminer toute sortie de la mémoire tampon liés à des problèmes en utilisant fpassthru(). J'utilise aussi
-0
d'économiser du CPU fois depuis mes données est compact déjà. J'ai utiliser ce code pour servir un dossier entier, zip à la volée: