Henryk Plötz: Uploadstatus ausgeben

Beitrag lesen

Moin,

gibt es eine möglichkeit, festzustellen, wieviele Daten bereits bei einem HTTP-Upload an den Server gesendet wurden? Ob Client- oder Serverseitig ist mir egal, ich hab Root-Rechte auf dem Server.

Ja, das ist eine halbwegs beliebte Frage. Bisher war die Antwort ein klares "Nein", aber ich arbeite grade an einem Feature-Artikel zu dem Thema, bin aber leider noch nicht sehr weit.

Eigentlich habe ich mich mit einer reinen PHP-Lösung befasst (die funktioniert auch), nebenbei ist aber noch eine CGI/Perl-Methode rausgekommen. (BTW: Eigentlich kann ich kein Perl.)

Die Grundidee: CGI-Skripte kriegen POST-Uploads über die Standardeingabe rein. Wenn das Skript also diese Eingabe selbst verarbeitet (use CGI spielt da wahrscheinlich nicht mit), dann weiss es auch wieviel Daten insgesamt kommen sollen (sendet der Browser in einem Header mit) und wieviel Daten es schon empfangen hat.

Durch die Funktionsweise von HTTP ist aber leider nicht möglich aus dem selben Skript etwas an den Browser zu senden. Also muss noch ein zweites Skript her, dass diese Aufgabe übernimmt. Weil ich es mir nicht zu kompliziert machen wollte, habe ich dafür PHP genommen, aber es sollte eigentlich jede Sprache gehen. Zur Kommukiation untereinander benutze ich einen named FIFO, ich bin mir nicht sicher ob eine andere Form von IPC vielleicht angebrachter wäre.

Hier der grobe Überblick: Ein Frameset enthält in einem Frame das Upload-Formular und im anderen Frame den Bereich wo später der Uploadstatus erscheint. Ausserdem ist noch ein bisschen JavaScript vonnöten um die beiden Frames zu koordinieren (ohne JavaScript funktioniert das dann wie ein langweiliger Standard-Upload).

Frameset:
<html>
<head>
<title>Upload-Test</title>
</head>
<frameset rows="13%,*">
<frame name="progress" src="progress.php">
<frame name="upload" src="upltest.html">
</frameset>
</html>

Die bereits angesprochene PHP-Datei progress.php:
<?php
 $tmp_dir = "/tmp/upltest_";
if($doit == "") {
 ?>
Bitte wählen Sie unten eine Datei zum hochloaden aus, und klicken Sie auf 'Hochladen'.
<script language="javascript" type="text/javascript">
document.writeln("Dieses Fenster wird den Upload-Vorgang zeigen.");
</script>
<noscript>
Ihr Browser unterstützt kein JavaScript, sie werden den Uploadvorgang nicht beobachten können.
</noscript>

<?php
} else {
 srand(time());
 $rnd = rand();
 if(file_exists($tmp_dir.$rnd)) die("Eeek, die Datei existiert schon");
 else system("/bin/mknod ".$tmp_dir.$rnd." p");
?>
<script language="javascript">
 top.upload.document.myform.action += "?<?php echo $rnd;?>";
 top.upload.document.myform.submit();
</script>
<?php
 flush();
 $fh = fopen($tmp_dir.$rnd,"r");
 if(!$fh) die("Konnte Pipe nicht öffnen");
 $fertsch = false; $rep = 0;
 while($fertsch == false) {
  $tmp = fgets($fh, 100);
  $per = (int)trim($tmp);
  if($per > $rep) { echo str_repeat("| | ", $per - $rep)."\n"; $rep = $per; flush();}
  if(trim($tmp) == "fertsch") $fertsch = true;
 }
 close($fh);
 unlink($tmp_dir.$rnd);
}

?>

Das HTML-Formular upltest.html:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>test</title>
</head>
<body>
<form action="/cgi-bin/upltest.pl" method="POST" name="myform" enctype="multipart/form-data" onSubmit="top.progress.location='progress.php?doit=true';return false;">
<input type="file" name="test"><input type="submit" value="Hochladen">
</form>
</body>
</html>

Das Perlskript upltest.pl:
#!/usr/bin/perl
print "Content-Type: text/html\n\n";

$tmp_dir = "/tmp/upltest_";

$siz = $ENV{CONTENT_LENGTH};

if($siz < 1) {$siz = 1;}

open (HANDLE, ">".$tmp_dir.$ENV{QUERY_STRING});

$oh = select(HANDLE);
$| = 1;
select($oh);

my $rec = 0;
my $last = -1;
my $per = 0;
while(<STDIN>) {
 $rec += length($_);
 $per = ($rec / $siz) * 100;
 if ($per >= $last+1) {
  print HANDLE $per , "\n";
  $last = $per;
 }
}

print HANDLE "fertsch\n";
close (HANDLE);

print "alles ok, Länge war $siz";

Der Ablauf: Wenn der User auf Hochladen klickt und Javascript aktiviert ist, wird in den Progress-Frame das PHP-Skript mit einem Parameter neu geladen. Das sucht sich dann eine zufällige Zahl aus und erstellt im Verzeichnis für temporäre Dateien einen FIFO mit dieser Zahl im Namen. Dann gibt es ein JavaScript aus dass das Formularziel im anderen Frame verbiegt indem es die Zahl anhängt und das Formular abschickt. Ausserdem macht das PHP-Skript den FIFO zum Lesen auf und wartet darauf dass was eintrudelt.
Im anderen Frame sendet der Browser derweil das Formular an das Perl-Skript. Das macht den FIFO zum Schreiben auf und beginnt die vom User gesendeten Daten einzulesen. Mithilfe des vom Browser gesendeten Content-Length:-Header berechnet es wieviele Prozent des Uploads schon fertig sind und schreibt die Prozentzahl in den FIFO.
Das PHP-Skript generiert mit dieser Prozentzahl dann einen schönen Balken der im Laufe des Uploads voll wird.

So weit, so gut. Was jetzt noch fehlt sind diverse Fehlerüberprüfungen, der Alternativmodus für Browser ohne JavaScript und vor allem müsste das Perl-Skript losgehen und die empfangenen Daten auseinandernehmen.
Ich weiss im Prinzip wie das geht, bin allerdings noch nicht dazu gekommen, vielleicht hilft dir das hier schonmal weiter.

Dran denken: Das ist Proof of Concept und bisher nur im Mozilla gestestet.

--
Henryk Plötz
Grüße aus Berlin