Manuel Burghammer: Uploadstatus ausgeben

Hallo,
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.

thx4hlp

MfG Manuel

  1. Hoi,

    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.

    Nicht fuer dich. Durchaus fuer den Useragent, aber du kommst nicht an die
    Daten dran.

    Gruesse,
     CK

    1. Hallo,

      Nicht fuer dich. Durchaus fuer den Useragent, aber du kommst nicht an die
      Daten dran.

      Kann man irgendwo die übertragenen Daten auf dem Server auslesen? Irgendwo muss doch eine Temporäre Datei angelegt werden. Wenn ich von der Datei ständig die Grösse abfrage und an den Browser zurückgebe sollte das doch klappen.

      Wie kann ich herausfinden wo diese temporäre Datei steckt?

      System ist ein Linux mit Apache
      Ich hab vollen zugriff und Rotrechte auf dem System

      MfG Manuel

      1. Moin,

        Kann man irgendwo die übertragenen Daten auf dem Server auslesen? Irgendwo muss doch eine Temporäre Datei angelegt werden. Wenn ich von der Datei ständig die Grösse abfrage und an den Browser zurückgebe sollte das doch klappen.

        Klarer Fall von "kommt drauf an", aber eher nein. Ich habe damit wie gesagt einige Experimente gemacht:
        Apache mit PHP-Modul: Es wird keine temporäre Datei angelegt, die Daten werden vollständig im Speicher behalten und erst kurz vor dem Aufruf des PHP-Skripts in einem Rutsch in eine Datei geschrieben.
        Apache mit Perl/CGI: Du kriegst die Daten eh auf der Standardeingabe rein, da lohnt sich keine temporäre Datei.

        Wie kann ich herausfinden wo diese temporäre Datei steckt?
        System ist ein Linux mit Apache
        Ich hab vollen zugriff und Rotrechte auf dem System

        Wenn du gerne experimentieren möchtest, kann ich dir das folgende empfehlen:

        Beende Apache. Starte dann auf der Kommandozeile als root:
        strace httpd -X 2>&1 | tee straceog
        (gegebenenfalls müssen da noch weitere Parameter zwischen das -X und die 2 , hängt von deiner Konfiguration ab).
        Der Apache ist dann im Debug-Modus und ausserdem gibt dir strace alle Systemaufrufe aus (das sind _sehr_ viele), tee sorgt ausserdem dafür dass die ganze Sache in der Datei stracelog mitgeschrieben wird.

        Warte jetzt einige Zeit während der Apache startet und lauter Meldungen auf den Schirm flimmern. Irgendwann stoppt die Ausgabe bei einer Zeile mit accept. Dann ist der Apache bereit und du kannst jetzt das in Frage kommende Skript aufrufen und eine Datei hochladen. Dabei flimmern noch mehr Meldungen über den Schirm die du dir jetzt (durch zurückscrollen oder ansehen der generierten Logdatei) ansehen solltest. Die meisten der Funktionsnamen sind selbsterklärend, vor allem nach open() mit irgendeiner Datei im Verzeichnis für temporäre Dateien solltest du Ausschau halten. Wichtige Nebenbemerkung: Die Nummern die bei Aufrufen wie open oder accept hinter dem Istgleichzeichenstehen sind die Filedeskriptoren. Die werden dann als erster Parameter für read oder close, etc. verwendet.

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

        1. Hallo,
          also ich schreibe meine CGIs in C.

          kann ich auch nicht irgendwie feststellen, wieviele daten das CGI schon empfangen hat?

          Wenn ich einen Char definiere und das Char gefüllt wird müsste ich doch, wenn ich alle paar sekunden anfrage, den aktuellen Füllstand (strlen) auslesen können.

          Aber wenn ich das richtig verstanden hab, wird das Char im CGI erst gefüllt, wenn es komplett übertragen ist. Das macht die sache irgendwie kompliziert :o(
          Ich glaub ich schreib mir da nen kleinen FTP-Client den ich in ne Webseitew integriere. Da sollte das doch dann gehen. Zumindest machen es meine anderen FTP-Clients so :o)

          MfG Manuel

          1. Moin,

            Wenn ich einen Char definiere und das Char gefüllt wird müsste ich doch, wenn ich alle paar sekunden anfrage, den aktuellen Füllstand (strlen) auslesen können.

            Naja, ob Sekunden reichen :)

            Aber wenn ich das richtig verstanden hab, wird das Char im CGI erst gefüllt, wenn es komplett übertragen ist. Das macht die sache irgendwie kompliziert :o(

            Das glaube ich eben nicht, es sei denn es wäre PHP. Mach es so: Schreib dir ein Test-CGI das die Daten entgegennimmt. Das muß doch dann sicher mit einem Puffer arbeiten in den die Daten reingelesen werden. Will sagen du hast dann doch eh eine while-Schleife die nur alle paar Kilobyte weitergeht. Dann machst du einfach am Anfang eine kleine Datei zum Schreiben auf und schreibst dort alle paar Kilobyte (oder auch nur alle 5%, etc) die aktuelle Anzahl der übertragenen Daten und den aktuellen Timestamp je zeilenweise rein. Dann lädst du einmal Daten von einem langsamen Client hoch (oder auch sehr viel Daten über loopback, ich habe meine Experimente mit mehreren MB-großen Dateien gemacht, da dauert das dann auch über Loopback ein paar Sekunden) und schaust am Ende in die Datei ob sich der Timestamp im Verlauf des Uploads ändert. Wenn ja, dann werden die Daten nicht im Server gecached (das ist das wahrscheinlichste) und du kannst mit einer ähnlichen Methode den Uploadstatus anzeigen. Genau das ist in dem Beispielcode von mir weiter oben gemacht.
            Wenn nein, hast du ein Problem. Dann müsstest du den Apachen an geeigneten Stellen modifizieren und ein eleganter und einfacher Weg dazu fällt mir nicht ein.
            --
            Henryk Plötz
            Grüße aus Berlin

  2. 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

    1. Hoi,

      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).

      Ich sehe ein ganz anderes Problem dabei: was passiert, wenn das CGI-Script
      erst gestartet ist, wenn alle Daten vorhanden sind (== der Upload vollzogen
      ist)? Ich wuerde vermuten, ein Webserver startet das Script erst nach erhalt
      der Daten, um unnoetige polls zu vermeiden.

      Gruesse,
       CK

      1. Moin,

        Ich sehe ein ganz anderes Problem dabei: was passiert, wenn das CGI-Script
        erst gestartet ist, wenn alle Daten vorhanden sind (== der Upload vollzogen
        ist)? Ich wuerde vermuten, ein Webserver startet das Script erst nach erhalt
        der Daten, um unnoetige polls zu vermeiden.

        Also zumindest mein Apache tut das nicht. Wieso sollte er erst mal potentiell _sehr_ viele Daten im Speicher behalten wenn er sie doch gleich an das Skript loswerden kann?
        Sicherlich dauert das Starten des Perl-Interpreters einige Zeit und während dieser gehen auch Daten ein die der Webserver sicher nicht wegschmeisst und er wird die Verbindung deswegen auch nicht so lange anhalten bis das Skript bereit ist. Aber ich sehe keinen Grund warum er den ganzen Body cachen sollte. Der empfangene Body geht den Webserver im Prinzip überhaupt nichts an.

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