HTTP-Kommunikation zwischen PHP-Script und Ladebalken
Kristallrainer
- php
Hallo!
Ich habe ein PHP-Script, welches .jpg-Dateien aus einem .zip-Archiv entpackt, die entpackten Bilder verkleinert, Thumbnails erstellt und in einer Datenbank katalogisiert.
So weit so gut, nur kann es unter Umständen einige Minuten dauern bis das Script seine Arbeit getan hat, was zu einem Timeout führt.
Abhilfe soll hier ein AJAX-Ladebalken schaffen, der mit jeder verarbeiteten Datei (also mit jedem Durchlauf einer while-Schleife) eine Kerbe weiter rückt.
Der Ladebalken ist mit Hilfe von MooTools vorhanden und wartet darauf per sb.set(prozent) vorwärts geschoben zu werden.
Eben jene einzufügende Prozentzahl lasse ich mein PHP-Script errechnen.
Die Frage ist:
Wie bekomme ich die PHP-Variable in das JavaScript?
XMLHTTPRequest soll hier mein Freund sein.
Wie ich das verstanden habe kann ich diesen Request auf eine PHP-Variable gucken lassen und meine JavaScript-Funktion starten, sobald sich die PHP-Variable ändert.
Nur wie?
Und schon mal präventiv gefragt: Wird das mein Timeout-Problem lösen?
Viele Grüße,
Rainer
Hallo Rainer,
Ich habe ein PHP-Script, welches .jpg-Dateien aus einem .zip-Archiv entpackt, die entpackten Bilder verkleinert, Thumbnails erstellt und in einer Datenbank katalogisiert.
okay - es tut nichts zur Sache, aber warum packt jemand JPEG-Grafiken in ein zip-Archiv? Die Kompression, die damit noch zu erzielen ist, dürfte winzig sein.
Eben jene einzufügende Prozentzahl lasse ich mein PHP-Script errechnen.
Wie bekomme ich die PHP-Variable in das JavaScript?
Durch einen HTTP-Request, wer hätte das gedacht? ;-)
XMLHTTPRequest soll hier mein Freund sein.
Ja, das wird der eleganteste Ansatz sein.
Wie ich das verstanden habe kann ich diesen Request auf eine PHP-Variable gucken lassen und meine JavaScript-Funktion starten, sobald sich die PHP-Variable ändert.
Ja. Nur hast du vielleicht noch nicht erkannt, dass du es hier mit *zwei* PHP-Scripts zu tun hast. Eins werkelt vor sich hin und bearbeitet Bilder, ein zweites wird immer mal wieder hingeschickt um nachgucken, wie weit der Kollege mittlerweile ist, diese Information an den Client schicken und dann wieder nach Hause gehen.
Nun sind diese beiden Scripts aber im Arbeitsspeicher völlig getrennt voneinander. Script B (der Fortschrittsmelder) kann nicht einfach auf die Variablen von Script A (dem Image Worker) zugreifen. Du bräuchtest also noch einen Mechanismus, mit dem diese beiden Scripts miteinander kommunizieren können. Eventuell kann der Fortschrittsmelder auch einfach nachsehen, wieviele Dateien schon im Thumbnail-Verzeichnis liegen.
Und schon mal präventiv gefragt: Wird das mein Timeout-Problem lösen?
Nein. ;-)
Der Image Worker hat immer noch dasselbe Arbeitspensum zu bewältigen, das auch immer noch genauso lange dauert. Vielleicht sogar länger, wenn die Kommunikation der beiden ungünstig realisiert ist.
Wenn dein Hoster nicht den safe_mode für PHP verwendet, kann das Script aber mit set_time_limit() sein eigenes Zeitlimit hochsetzen; durch die Angabe von 0 als Argument sogar die Zeitüberwachung komplett abstellen (würde ich aber nicht tun).
So long,
Martin
Hallo Martin!
warum packt jemand JPEG-Grafiken in ein zip-Archiv?
Da ich sonst keine Möglichkeit sehe (userfreundlich) über ein Formular einen Haufen Bilder gleichzeitig hochzuladen. Es geht also mehr darum die Bilder zusammenzufassen als sie zu komprimieren.
Durch einen HTTP-Request, wer hätte das gedacht? ;-)
Geht es nur mir so, oder ist die MooTools Doku nichts für Leute, die von AJAX keine Ahnung haben?
Ich bin mittlerweile so weit, dass ich zwischen Browser und Server ein JSON-Objekt (http://mootools.net/docs/Request/Request.JSON) hin und her werfen will, aber das genaue Funktionsprinzip ist mir noch ein Rätsel :(
Ja. Nur hast du vielleicht noch nicht erkannt, dass du es hier mit *zwei* PHP-Scripts zu tun hast.
Das habe ich in der Tat noch nicht.
Ich dachte das JavaScript würde dem PHP auf die Finger gucken.
Wie kann ich denn zwei PHP-Scripte gleichzeitig ausführen?
Ein "normales" PHP-Script wird doch abgebrochen, wenn kein dazu gehöriges Browserfenster geöffnet ist, oder?
Und schon mal präventiv gefragt: Wird das mein Timeout-Problem lösen?
Nein. ;-)
Das habe ich mir schon fast gedacht. :(
Der "Image Worker" arbeitet sich durch ein vom Unzipper angelegtes temporäres Verzeichnis, welches er am Ende löscht.
Ich könnte ihn also ein Bild abarbeiten und löschen lassen, und dann das Script so lange erneut auf das temporäre Verzeichnis anwenden bis es leer ist.
Wie kann ich denn ein Script aus sich selbst heraus am effektivsten neu starten?
Stark vereinfacht denke ich mir das in der Form:
<?php //image_worker.php
function image_worker($verzeichnis) {
if($verzeichnis nicht leer) {
bild_bearbeiten($bild);
unlink($bild);
// script abbrechen und image_worker.php?verz=temporaer neu starten
}
else rmdir($verzeichnis);
}
image_worker($_GET['verz']);
?>
Mir ist nur die header("Location: ") funktion bekannt, welche aber sämtliche Ausgaben verbieten würde.
Wo ich nun aber so lang drüber gegrübelt habe will ich diesen Statusbalken jetzt auch in Aktion sehen ;)
Viele Grüße und Dank,
Rainer
Hallo Rainer,
warum packt jemand JPEG-Grafiken in ein zip-Archiv?
Da ich sonst keine Möglichkeit sehe (userfreundlich) über ein Formular einen Haufen Bilder gleichzeitig hochzuladen. Es geht also mehr darum die Bilder zusammenzufassen als sie zu komprimieren.
okay, ich dachte mir schon sowas in der Art.
Geht es nur mir so, oder ist die MooTools Doku nichts für Leute, die von AJAX keine Ahnung haben?
Keine Ahnung - ich habe noch nie eine allgemein bekannte JS-Bibliothek angewendet und meide das auch weitgehend. Einmal weil ich JS an sich vermeide, wenn's auch ohne geht; zum anderen weil ich das bisschen an Funktionalität, was ich für ein Projekt brauche, lieber selbst schreibe (denn dann weiß ich, was der Code tut, was er kann und was er nicht kann) als mich durch umfangreiche Dokumentationen zu wühlen. Und dann womöglich festzustellen, dass ich 99% der Bibliothek unnütz rumschleppe.
Nur hast du vielleicht noch nicht erkannt, dass du es hier mit *zwei* PHP-Scripts zu tun hast.
Das habe ich in der Tat noch nicht.
Ich dachte das JavaScript würde dem PHP auf die Finger gucken.
Hast du eine Vorstellung, wie das gehen soll? Javascript und PHP können sich in der Regel nur über HTTP untereinander austauschen: Ein Request transportiert Informationen vom Client (JS im Browser) zum Server (PHP), ein Response liefert Informationen zurück. Das Dumme ist nun in deinem Fall, dass jeder Request das angesprochene PHP-Script neu startet, und dass die laufenden Instanzen nichts voneinander "wissen", so wie mehrere HTTP-Requests auch technisch keine Beziehung zueinander haben.
Wie kann ich denn zwei PHP-Scripte gleichzeitig ausführen?
Indem du eins aufrufst, und während dein Javascript noch auf die Antwort von PHP wartet, startest du mit einem weiteren Request ein anderes Script. Was hieß noch gleich das erste 'A' in AJAX? ;-)
Ein "normales" PHP-Script wird doch abgebrochen, wenn kein dazu gehöriges Browserfenster geöffnet ist, oder?
Das ist stark vereinfacht. Soweit ich weiß, beendet der Server das Script, wenn der Client von sich aus die Verbindung beendet. Ein Browserfenster ist dazu nicht nötig, auch jedes andere Programm kann ein PHP-Script auf dem Server starten, indem es einfach einen entsprechenden HTTP-Request absetzt.
Der "Image Worker" arbeitet sich durch ein vom Unzipper angelegtes temporäres Verzeichnis, welches er am Ende löscht.
Ich könnte ihn also ein Bild abarbeiten und löschen lassen, und dann das Script so lange erneut auf das temporäre Verzeichnis anwenden bis es leer ist.
Wie kann ich denn ein Script aus sich selbst heraus am effektivsten neu starten?
Da fehlt mir bisher auch jegliche Erfahrung. Auf jeden Fall müsste dein Hoster dir die Möglichkeit geben, von PHP aus weitere Prozesse zu starten. Ohne diese Berechtigung geht wahrscheinlich nichts.
Mir ist nur die header("Location: ") funktion bekannt, welche aber sämtliche Ausgaben verbieten würde.
Aber das wäre ja in deinem Beispiel kein Problem, oder? Dein Worker macht doch sowieso keine Ausgaben.
So long,
Martin
Hallo Martin,
Keine Ahnung - ich habe noch nie eine allgemein bekannte JS-Bibliothek angewendet und meide das auch weitgehend.
Bislang hielt ich das auch immer so, nur macht bei 2-3 Minuten Verarbeitungszeit ein Statusbericht schon irgendwo Sinn.
Für eine Bibliothek habe ich mich entscheiden, da alles was funktioniert auch wirklich browserübergreifend den selben Funktionsumfang bietet - und was man im fertigen Projekt nicht braucht kann man sehr einfach online aussortieren, muss es also nicht mehr mitschleppen.
Im Grunde genommen sind die MooTools auch recht einfach zu bedienen, nur wird einem der Einstieg nicht wirklich leicht gemacht.
Hast du eine Vorstellung, wie das gehen soll? Javascript und PHP können sich in der Regel nur über HTTP untereinander austauschen.
Und genau das meinte ich mit "auf die Finger gucken" :)
Es geht ja in meinem Fall nur darum einen Integer zwischen 0 und 100 JS und PHP zur Laufzeit zugänglich zu machen. JS empfangend, PHP sendend.
Indem du eins aufrufst, und während dein Javascript noch auf die Antwort von PHP wartet...
Das ist ein guter Gedanke. Da eh JavaScript im Spiel ist kann ich mein PHP so umdichten, dass das JavaScript die Bearbeitung eines konkreten Bildes auslöst, wartet bis dies fertig ist und dann das nächste in Auftrag gibt... sobald ich diese Requestgeschichte vollständig nachvollziehen kann wird das der Plan... so hätte ich dann auch kein Problem mehr damit den Balken voran zu treiben :)
Mir ist nur die header("Location: ") funktion bekannt, welche aber sämtliche Ausgaben verbieten würde.
Aber das wäre ja in deinem Beispiel kein Problem, oder? Dein Worker macht doch sowieso keine Ausgaben.
Im Prinzip nicht, aber wenn ich einen neuen Header absetze ist der vorherige Inhalt der Browsers (der ja weiterhin am Statusbalken arbeiten soll) hinfällig. Zumindest ist es das was ich bislang beobachtet habe.
Viele Grüße,
Rainer
Hallo,
wie ists, wenn Du deinen imageworker die Anzahl der bearbeiteten Bilder
in eine Datei schreiben lässt und diese dann mit dem anderen Skript
ausliest?
Grüßle,
Z.
Lieber Kristallrainer,
wie im bisherigen Verlauf des Threads bereits erörtert wurde, benötigst Du mehr als einen Thread, um Dein Vorhaben zu realisieren. Ein wesentliches Problem dabei könnte sein, dass die verschiedenen PHP-Prozesse nicht miteinander kommunizieren können (zumindest kann der eine nicht auf Variablen des anderen zugreifen).
Vielleicht könnte eine Session Abhilfe schaffen? Nehmen wir an, dass der "Image Worker" in einer Session-Variablen den aktuellen Stand der Verarbeitung (z.B. $_SESSION['image-upload-progress'] = 0.33;
) festhält. Nun könnte ein zweiter PHP-Prozess ebenfalls eine Session starten und bekäme damit Zugriff auf den Wert dieser Variablen. Und damit könnte er den AJAX-Request beantworten...
Ich kann allerdings nicht versprechen, dass zwei PHP-Prozesse dieselbe Session "benutzen" können. Kann das jemand verifizieren?
Liebe Grüße,
Felix Riesterer.
Moin!
Ich kann allerdings nicht versprechen, dass zwei PHP-Prozesse dieselbe Session "benutzen" können. Kann das jemand verifizieren?
Solange ein Skript läuft, ist die Sessiondatei für Zugriffe anderer Aufrufe gesperrt, diese Skripte würden beim Befehl session_start() also angehalten und müssten warten.
Man kann diese Zwangspause beenden, indem das erste Skript nach dem Ende aller schreibenden Zugriffe auf Sessionvariablen mit dem Befehl session_write_close() den Zugriff für EINEN der anderen wartenden Prozesse freigibt - denn dort gilt natürlich wieder das gleiche Spielchen, nur ein Prozess kann zur Zeit den exklusiven Zugriff auf die speichernde Sessiondatei erhalten.
Diese Exklusiv-Sperre hat übrigens ihren sehr wichtigen Sinn, denn dadurch werden Sicherheitslücken vermieden, die unter anderem durch Race-Conditions entstehen könnten. Da das PHP-Sessionsystem auf allgemeine, beliebige Nutzung ausgelegt sein muss, kommt man um diese Funktionsweise im Standard-Speichermodul nicht herum.
Es gibt allerdings in PHP noch viele Alternativen zur Errichtung eines prozessübergreifenden gemeinsamen Speicherbereichs. Eine sehr simple Methode wäre der gemeinsame Zugriff auf eine Datenbank. Und weil's weiter oben erwähnt war: Wer den Standard-Sessionspeicher-Mechanismus mittels session_set_save_handler() austauscht und z.B. auf Datenbankspeicherung umstellt, sich aber keine Gedanken über das prozessübergreifende Locking von Schreib- und Lesezugriffen auf den gemeinsamen Datenspeicher macht, öffnet seine Applikation grundsätzlich für Angreifbarkeiten durch Race-Conditions.
- Sven Rautenberg
Lieber Sven,
danke für Deine klärenden Einwände. Ich sehe, dass man das Problem wohl am elegantesten durch eine (Text-)Datei löst, in die man den numerischen (float-)Wert schreibt, der dann von einem PHP-Script ausgelesen werden kann. Das erspart den Umweg über eine Datenbank und ist gegen Angriffe sicher.
Race-Conditions sind in diesem Falle unerheblich, da ein beendeter "Image Worker" in diese Datei eine "0" schreibt. Ansonsten kann dort nur der aktuelle Stand eines laufenden Image Workers stehen, am besten zusammen mit einem Session-Name oder irgendwie eingerichteten Hash-Wert (entweder per URL-Parameter oder Cookie übertragen), damit parallele Uploads voneinander getrennt betrachtet werden können.
Wäre das ein gangbarer Weg?
Liebe Grüße,
Felix Riesterer.
Danke euch beiden!
Ich habe es noch nicht ausprobiert, nur glaube ich, dass neue Prozesse das Timeout-Problem nicht beheben werden, da doch ein Prozess den ganzen Vorgang initiieren und zwangsläufig auf dessen Fertigstellung warten muss.
Also auch wenn ich immer neue Prozesse starte, die allesamt nach 3 Sekunden abgeschlossen sind wird der Mutterprozess nach 60 Sekunden abgebrochen.
Ich sehe also gerade die einzige Möglichkeit den "Image Worker" Bild für Bild aus einem Medium aufzurufen, in dem ich so lange rechnen darf wie ich will - aus dem Browser per JavaScript.
Oder verstehe ich hier irgendwas miss?
Viele Grüße,
Rainer
Lieber Kristallrainer,
Oder verstehe ich hier irgendwas miss?
ja, Du missverstehst den Sinn der Funktion set_time_limit(), die Dir bereits angeboten wurde.
Wenn Du zu Beginn jedes Bildes, das Du bearbeitest, diese Funktion mit einem sinnvollen Wert aufrufst, dann sollte Dein Timeoutproblem gelöst sein. Ich skizziere das einmal für Dich, damit Du mich verstehst:
// ZIP-Datei wurde geöffnet... Jetzt Bilder verarbeiten
foreach ($bilder as $bild) { // das ist _die_ Schleife
set_time_limit(30); // weitere 30 Sekunden bis Timeout
// Hier wird jetzt das einzelne Bild verarbeitet.
}
// jetzt ZIP-Datei löschen, Script beenden - fertig
Liebe Grüße,
Felix Riesterer.