[PHP] Frage zur Serverbelastung meines Scripts.
lima-city → Forum → Programmiersprachen → PHP, MySQL & .htaccess
abbruch
browser
buffer
code
dank
datei
header
http
laden
liste
manual
mime
problem
senden
setzen
status
stunden
test
type
url
-
Hey ;)
Ich habe bereits einen Thread zum Thema "Dateien vor direktzugriff via Link schützen." und kam danke Hilfe auch zu einer Lösung.
reimann hat mich allerdings bereits auf diverse Probleme aufmerksam gemacht. Ich kenne mich nicht soo gut mit PHP aus, und daher wollte ich einfach mal die Profis unter euch fragen, wie das denn genau ist...
Hier mal das bestehende Script:
<?php // Hier findet eine validierung statt. // Wenn alles passt, wird folgender Code ausgeführt -> User erhält datei. $filepath = "./test.rar"; // Der Dateipfad. (Muss später durch LookUp in DB ersetzt werden. $filename = "test.rar"; // Der Dateiname. (Muss später von $filepath abgeleitet werden. $fi = new finfo(FILEINFO_MIME); // finfo = bessere Alternative zu mime_content_type() $mime_type = $fi->buffer($filepath); // Ermittelt den Mime-Type der Datei. header('Cache-Control: public'); // Wird für den IE benötigt. header('Content-Type: '.$mime); // Setzt den Content-Type. header('Content-Disposition: attachment; filename="'.$filename.'"'); // Öffnet den Open/Save Dialog des Browsers und bietet Datei mit Namen $filename zum Download an. readfile($filepath); // Liest die Datei aus. (-> Datei wird an User gesendet) exit; // Script stoppen, da selbst Leerzeilen hier zum Problem werden könnten. ?>
Dieses Script macht nichts anderes, als eine gewünschte Datei (in dem Fall noch HardCoded) an den Browser zu schicken. Der User klickt nun nicht mehr auf einen Link, der direkt zur Datei führt, sondern auf eine PHP Datei, die ihm die gewünschte Datei zuschickt.
Mir wurde gesagt, dass es bei größeren Dateien (> 2GB) zum Problem kommt, weil der Buffer überläuft.
Nun habe ich auf php.net folgendes Script gefunden, das mein Problem lösen dürfte:
<?php function readfile_chunked($filename,$retbytes=true) { $chunksize = 1*(1024*1024); // how many bytes per chunk $buffer = ''; $cnt =0; // $handle = fopen($filename, 'rb'); $handle = fopen($filename, 'rb'); if ($handle === false) { return false; } while (!feof($handle)) { $buffer = fread($handle, $chunksize); echo $buffer; ob_flush(); flush(); if ($retbytes) { $cnt += strlen($buffer); } } $status = fclose($handle); if ($retbytes && $status) { return $cnt; // return num. bytes delivered like readfile() does. } return $status; } ?>
Ich hab's noch nicht ausprobiert, aber rein von der Logik sieht's recht gut aus.
Dieses Script sendet eine Datei Megabyte für Megabyte und löscht zwischendurch immer den Buffer. Der User merkt keinen Unterschied, aber wie sieht's mit dem Server aus?
Meine Fragen lauten nun:
Kann ich auf diese Weise dann auch Dateien um die 4GB übertragen?
Wie schaut es mit der Serverbelastung aus? (Sie spielt fast keine Rolle, ich hoste das CMS dann nicht hier auf LC, aber es würde mich trotzdem interessieren.)
Falls das so nicht passt, was müsste ich tun, damit es funktioniert?
lg
Sincer
Beitrag zuletzt geändert: 14.7.2011 12:23:19 von sincer -
Diskutiere mit und stelle Fragen: Jetzt kostenlos anmelden!
lima-city: Gratis werbefreier Webspace für deine eigene Homepage
-
sincer schrieb:
Meine Fragen lauten nun:
Kann ich auf diese Weise dann auch Dateien um die 4GB übertragen?
Wie schaut es mit der Serverbelastung aus? (Sie spielt fast keine Rolle, ich hoste das CMS dann nicht hier auf LC, aber es würde mich trotzdem interessieren.)
Falls das so nicht passt, was müsste ich tun, damit es funktioniert?
Die Einschränkungen bestehen seitens von FTP und zwar in der PHP.INI über die Einstellung max_execution_time. Dauert das Laden der Datei also länger, wird das Script also abgebrochen. Außerdem kannste bei LC garkeine Dateien hochladen, die 4GB groß sind ;)
Beitrag zuletzt geändert: 14.7.2011 14:03:52 von nikic -
Ich habe es gerade mit einer 2,5GB-Datei ausprobiert und es gab keinen nennenswerten Anstieg des verwendeten Arbeitsspeichers. Allerdings ist es dem FF natürlich nicht möglich gewesen, zu erkennen, wie lange der Download noch dauert, oder wie groß die Datei ist.
-
Weil dem Ganzen gewisse Header fehlen :)
-
fabo schrieb:
Kannst du mir sagen, mit welchem Header man dem Browser das mitteilt? Das interessiert mich nämlich. Um das mit der 2,5GB-Datei zu testen, habe ich nur den Mime-Type gesetzt und die Funktion, die Sincer gepostet hat, aufgerufen.
Weil dem Ganzen gewisse Header fehlen :)
Beitrag zuletzt geändert: 14.7.2011 19:40:01 von drafed-map -
z.B.
$filesize = filesize($filepath); header('Content-Length: '.$filesize); // Dateigröße an den Browser senden
-
Danke für eure Antworten :)
fabo schrieb:
$filesize = filesize($filepath); header('Content-Length: '.$filesize); // Dateigröße an den Browser senden
Das nehme ich auf jeden Fall ins Script auf, danke :)
drafed-map schrieb:
Ich habe es gerade mit einer 2,5GB-Datei ausprobiert und es gab keinen nennenswerten Anstieg des verwendeten Arbeitsspeichers.
Gut zu wissen, das war mir wichtig, danke :)
trueweb schrieb:
Die Einschränkungen bestehen seitens von FTP und zwar in der PHP.INI über die Einstellung max_execution_time. Dauert das Laden der Datei also länger, wird das Script also abgebrochen.
Alles klar, danke. Dem kann ich scheinbar entgegenwirken, wenn ich die Max-Execution-Time = 0 -> Unendlich für dieses Script setze.
ini_set('max_execution_time', 0);
Sollte ja funktionieren, oder?
Was passiert aber, wenn dann ein Fehler auftritt? Ich will schließlich nicht, dass mein Server abschmiert, nur weil jemand eine Datei herunter lädt :P
thx
Sincer -
fabo schrieb:
Danke. Da habe ich ja wieder was gelernt. Woher kennst du diesen Header-Befehl und gibt es eine Liste, in der alle aufgeführt sind? Ich konnte eine solche nicht finden.
$filesize = filesize($filepath); header('Content-Length: '.$filesize); // Dateigröße an den Browser senden
@Sincer: Wenn du PHP nicht in der Konsole ausführst, setze diesen Wert bitte niemals auf 0. Setze ihn sehr hoch, aber nicht auf unendlich. -
Eine Liste dafür gibts nicht wirklich, weil es unzählige Header gibt. Aber dass die Dateigröße bei einem Dateidownload mitgesendet wird bzw. werden soll, ist eigentlich logisch :)
Ich persönlich hab max_execution_time auch auf 0 und sogar ignore_user_abort aktiviert :p
Beitrag zuletzt geändert: 15.7.2011 1:12:02 von fabo -
fabo schrieb:
Das ist logisch. Ich kannte diesen Header aber nicht.
Eine Liste dafür gibts nicht wirklich, weil es unzählige Header gibt. Aber dass die Dateigröße bei einem Dateidownload mitgesendet wird bzw. werden soll, ist eigentlich logisch :)
fabo schrieb:
Dir sollte klar sein, dass das ganz schnell zu einer sehr bösen Kombination werden kann.
Ich persönlich hab max_execution_time auch auf 0 und sogar ignore_user_abort aktiviert :p -
fabo schrieb:
Eine Liste dafür gibts nicht wirklich, weil es unzählige Header gibt.
Unzählige sind das jetzt auch nicht, aber einige.
drafed-map schrieb:
Woher kennst du diesen Header-Befehl und gibt es eine Liste, in der alle aufgeführt sind? Ich konnte eine solche nicht finden.
Eine Liste aller standardisierten und einiger nicht standardisierter Header gibts hier-> http://en.wikipedia.org/wiki/List_of_HTTP_header_fields -
@Chatter
Ich hab da etwas tiefgehender gedacht :p
@drafed-map
Hier wäre auch noch was: http://tools.ietf.org/html/rfc2616 -
Hey ;)
drafed-map schrieb:
@Sincer: Wenn du PHP nicht in der Konsole ausführst, setze diesen Wert bitte niemals auf 0. Setze ihn sehr hoch, aber nicht auf unendlich.
Ok, dann würde ich ihn auf 300 setzen. 5 Minuten müssten ja eig. reichen, um eine Schleife ~4000 mal (für eine 4 GB Datei) auszuführen, oder?
Oder verstehe ich das falsch? Das hängt ja nur mit der Ausführungszeit des Scriptes zusammen, und nicht damit, wie lange es dauert, bis die Datei herunter geladen wurde, richtig?
lg
Sincer -
Ich habe es gerade ausprobiert. Nein, das ist nicht so. Die Scriptlaufzeit dauert so lange an, wie der User braucht, um die Datei herunter zu laden. Wenn der Download über das Internet erfolgen soll, brauchst du also definitiv eine längere maximale Ausführungszeit.
Beitrag zuletzt geändert: 15.7.2011 12:57:22 von drafed-map -
Hey ;)
drafed-map schrieb:
Die Scriptlaufzeit dauert so lange an, wie der User braucht, um die Datei herunter zu laden.
Danke für's austesten :)
Ok, das ist schlecht...
Dann gibt's für mich eig. nur die Option, das ganze auf 0 zu setzen. Ich kann ja nicht wissen, ob der Vorgang nun 10 Minuten, oder 1 Stunde benötigt...
Es sei denn, es gibt eine Möglichkeit, das dynamisch zu erweitern.
Also sobald die Zeit ausläuft, wird sie immer wieder um 5 Minuten verlängert. Wenn ein Fehler aufgetreten ist, wird sie nicht mehr verlängert. Sowas in der Richtung...
Zum Thema "unendlich": Wird das Script nicht automatisch abgebrochen, wenn der User den Download abbricht, bzw. wenn die Verbindung abbricht? (Also wenn ein "Fehler" passiert.) Was sind die genauen Risiken, wenn ich die max execution time nur für dieses Script auf unendlich setze?
lg
Sincer
// EDIT:
Habe gerade was zum Thema set_time_limit() gefunden...
Source: Link - Französisch / Englisch
To change the maximum execution time of a PHP script you need to use the ‘set_time_limit’ function. The function takes one argument which is the maximum number of seconds a script is allowed to run. If seconds is set to ‘0′, no time limit is imposed on the script and it may run indefinitely. Use the following snippet just before your time consuming code begins:
<?php set_time_limit(0); ?>
Note that, if you set the number of seconds to a value very high, you may recieve the fatal error just after 1 second. Its better to use ‘set_time_limit(0)’ instead. Also, remember that when you are finished with the portion that performs the busy computation, its a good idea to call:
<?php set_time_limit(30); ?>
Wenn ich die Zeit also vor der Schleife auf 0 setze, und danach auf 10. Würde das mein Problem lösen?
(Also zusätzlich zu ini_set('max_execution_time', 0); )
Beitrag zuletzt geändert: 15.7.2011 13:20:33 von sincer -
Setze den Wert doch einfach auf 86400, das sind 24 Stunden. Länger kann eine Verbindung von einem normalen Internetanschluss auch nicht bestehen. 24 Stunden sind immer noch besser, als unendlich.
Du musst doch gar nicht wissen, wie lange der, der das herunter lädt, dafür braucht. Du musst nur ein Maximum angeben. Und das Maximum für eine normale Verbindung sind sowieso 24 Stunden. Danach gibt es eine neue IP.
Wenn du ignore_user_abort nicht auf true gesetzt hast, sorgt der Abbruch des Seitenaufbaus (da gehört der Abbruch eines Downloads auch dazu) durch den User im Regelfall zum Abbruch des Scripts. Darauf sollte man sich aber nicht verlassen. Deswegen niemals auf 0 setzen, sondern auf 86400. -
Hey ;)
Danke für eure Hilfe :)
Ich werde jetzt folgenden Code verwenden:
<?php // Hier findet eine validierung statt. // Wenn alles passt, wird folgender Code ausgeführt -> User erhält datei. $filepath = "./test.rar"; // Der Dateipfad. (Muss später durch LookUp in DB ersetzt werden. $filename = "test.rar"; // Der Dateiname. (Muss später von $filepath abgeleitet werden. $filesize = filesize($filepath); // Die größe der Datei, die übertragen werden soll. $fi = new finfo(FILEINFO_MIME); // finfo = bessere Alternative zu mime_content_type() $mime_type = $fi->buffer($filepath); // Ermittelt den Mime-Type der Datei. header('Cache-Control: public'); // Wird für den IE benötigt. header('Content-Type: '.$mime); // Setzt den Content-Type. header('Content-Disposition: attachment; filename="'.$filename.'"'); // Öffnet den Open/Save Dialog des Browsers und bietet Datei mit Namen $filename zum Download an. header('Content-Length: '.$filesize); // Dateigröße an den Browser senden // ob_end_flush(); // Sendet & Leert den Buffer und deaktiviert die AusgabeBufferung. // readfile($filepath); // Liest die Datei aus. (-> Datei wird an User gesendet) if(ignore_user_abort()) { // Abfragen, wie die Konfiguration lautet, falls der User das Laden abbricht. ignore_user_abort(false); // Falls ein Abbruch laut Config ignoriert wird, wird hier das Gegenteil festgelegt. Script bricht bei Download-Abbruch ab. } // Die Maximale Ausfürhungszeit für dieses Script auf 24 Stunden setzen. (Besser als auf 0 = unendlich) ini_set('max_execution_time', 86400); // 60*60*24 = 86400 readfile_chunked($filepath); exit; // Script stoppen, da selbst Leerzeilen hier zum Problem werden könnten. /** * Diese Methode wird verwendet, um die Datei in 1MB Stücken zu übertragen. (Grund: Buffer Overflow.) * Ausgeborgt von http://php.net/manual/de/function.readfile.php **/ function readfile_chunked($filename) { $chunksize = 1*(1024*1024); // How many bytes per chunk -> 1MB $buffer = ''; // Start with clear buffer. $handle = fopen($filename, 'rb'); if ($handle === false) { return false; } while (!feof($handle)) { $buffer = fread($handle, $chunksize); echo $buffer; ob_flush(); flush(); } $status = fclose($handle); return $status; } ?>
Falls du noch Anregungen hast, um das zu verbessern, freue ich mich natürlich :)
thx
Sincer -
Aus Macht der Gewohnheit, setze ich ini-Anweisungen an den Beginn der PHP Datei und sämtliche Funktionen über den eigenglichen Hauptprozess.
Zudem sagst du "Der Dateiname. (Muss später von $filepath abgeleitet werden.". Das könnte man mit basename lösen. Prüfen, ob die Datei überhaupt existiert und/oder lesbar ist, würde ich zudem auch noch.
<?php if(ignore_user_abort()) { // Abfragen, wie die Konfiguration lautet, falls der User das Laden abbricht. ignore_user_abort(false); // Falls ein Abbruch laut Config ignoriert wird, wird hier das Gegenteil festgelegt. Script bricht bei Download-Abbruch ab. } // Die Maximale Ausfürhungszeit für dieses Script auf 24 Stunden setzen. (Besser als auf 0 = unendlich) ini_set('max_execution_time', 86400); // 60*60*24 = 86400 /** * Diese Methode wird verwendet, um die Datei in 1MB Stücken zu übertragen. (Grund: Buffer Overflow.) * Ausgeborgt von http://php.net/manual/de/function.readfile.php **/ function readfile_chunked($filename) { $chunksize = 1*(1024*1024); // How many bytes per chunk -> 1MB $buffer = ''; // Start with clear buffer. $handle = fopen($filename, 'rb'); if ($handle === false) { return false; } while (!feof($handle)) { $buffer = fread($handle, $chunksize); echo $buffer; ob_flush(); flush(); } $status = fclose($handle); return $status; } // Hier findet eine validierung statt. // Wenn alles passt, wird folgender Code ausgeführt -> User erhält datei. $filepath = "./test.rar"; // Der Dateipfad. (Muss später durch LookUp in DB ersetzt werden. if(file_exists($filepath)) { $filesize = filesize($filepath); // Die größe der Datei, die übertragen werden soll. $fi = new finfo(FILEINFO_MIME); // finfo = bessere Alternative zu mime_content_type() $mime_type = $fi->buffer($filepath); // Ermittelt den Mime-Type der Datei. header('Cache-Control: public'); // Wird für den IE benötigt. header('Content-Type: '.$mime); // Setzt den Content-Type. header('Content-Disposition: attachment; filename="'.basename($filepath).'"'); // Öffnet den Open/Save Dialog des Browsers und bietet Datei mit Namen $filename zum Download an. header('Content-Length: '.$filesize); // Dateigröße an den Browser senden readfile_chunked($filepath); } exit; // Script stoppen, da selbst Leerzeilen hier zum Problem werden könnten. ?>
Beitrag zuletzt geändert: 15.7.2011 17:46:56 von fabo -
Danke, ist übernommen :)
Ursprünglich hatte ich die header() Befehle immer ganz oben, aber da dann die header() Abhängig von diversen Variablen wurden, sind sie immer wieder runter gerückt xD
Die Einstellung "Config ganz oben" macht durchaus Sinn :)
Der Pfad zur Datei wird aus der Datenbank abgefragt. Es zu überprüfen ist mir gar nicht eingefallen. Gut dass du's sagst. So kann ich auch fehlende Dateien in eine Logfile schreiben, etc.
thx
Sincer -
Diskutiere mit und stelle Fragen: Jetzt kostenlos anmelden!
lima-city: Gratis werbefreier Webspace für deine eigene Homepage