pl: imagepng und Content-Length

problematische Seite

Die Grafik wird mit PHP erzeugt.

   imagepng($im);
   imagedestroy($im);
   header("Content-Type: image/png");

Wie in PHP so üblich, landet die Binary im Out Buffer und um die Ausgabe samt HTTP-Header kümmert sich PHP. Leider fehlt der Header Content-Length, wie bringe ich PHP dazu einen Solchen anzuhängen?

MFG

  1. Deine Frage beantwortet ein kleines Experiment:

    <?php
    # Erzwinge das das Starten einer Instanz des Output-Puffers:
    ob_start();
    # Eine Ausgabe von 8 Zeichen Länge:
    echo "Ödipus".PHP_EOL;
    # Holen des Puffers
    $output = ob_get_clean();
    # Ausgabe der Länge des Puffers
    trigger_error( 'Bytes: ' . strlen( $output ), E_USER_NOTICE );
    # Ausgabe des Puffers:
    echo $output;
    

    Blob oder Text ist strlen() übrigens egal. Ausgabe:

    PHP Notice:  Bytes: 8 in /tmp/test.php on line 9
    Ödipus
    
  2. problematische Seite

    Lieber pl,

       imagepng($im);
       imagedestroy($im);
       header("Content-Type: image/png");
    

    wenn man den Output-Buffer aktiv nutzt, dann kann man header()-Aufrufe egal wo machen, aber ob man unbedingt diese Infrastruktur nutzen will, ist eine Frage des Standpunktes. Anfangs habe ich das auch benutzt, aber mittlerweile schreibe ich meine PHP-Scripte so, dass sie das nicht mehr benötigen.

    Wie in PHP so üblich, landet die Binary im Out Buffer und um die Ausgabe samt HTTP-Header kümmert sich PHP.

    Das kann man so machen, muss man aber nicht. Will man die Daten zuerst in anderer Weise handhaben, kann man sie auch in eine Datei schreiben lassen. Dazu ist der zweite Parameter bei imagepng($res, $to), der dann ungleich nullsein muss.

    Leider fehlt der Header Content-Length, wie bringe ich PHP dazu einen Solchen anzuhängen?

    Wenn Du mit dem Output-Buffer hantierst, dann würde ich das so tun:

    ob_start();
    
    // stand schon etwas im Puffer?
    $buffer_data = ob_get_clean();
    
    // hier findet die Erzeugung Deines Bildes statt...
    
    // Bilddaten in den Puffer schreiben
    imagedestroy($im);
    
    // Bilddaten aus dem Puffer holen
    $img_data = ob_get_contents();
    
    // Bild senden
    header('Content-Type: image/png');
    header('Content-Length: '.strlen($img_data));
    
    // Puffer senden und Script hart beenden
    ob_end_flush();
    exit;
    

    Wenn Dein Script vernünftig aufgebaut ist, kannst Du Dir den Teil mit ob_end_flush() und exit sparen. Auch wäre es eine Idee, eine temporäre Datei anzulegen, in die Du die Bilddaten schreiben lässt, um sie am Ende mit readfile() an den Browser ausgeben zu lassen. Damit wäre die ganze Puffer-Hantiererei nicht nötig und Dein Script könnte im Anschluss weiterlaufen, um z.B. noch diverse andere Dinge (z.B. logging) zu leisten:

    function send_png_image ($im) {
    
      // temporäre Datei erzeugen
      $tmp = tmpfile();
      $path = stream_get_meta_data($tmp)['uri'];
    
      // Bilddaten in temporäre Datei schreiben
      imagepng($im, $tmp);
      imagedestroy($im); // EDIT
    
      // Bild senden
      header('Content-Type: image/png');
      header('Content-Length: '.filesize($path));
    
      readfile($path);
    }
    

    Liebe Grüße

    Felix Riesterer

    1. problematische Seite

      function send_png_image ( $im ) {
      
        // temporäre Datei erzeugen
        $tmpFile = tempnam( '/tmp/', 'PHP_' );
      
        // Bilddaten in temporäre Datei schreiben
        // Das Handbuch sagt:
        # to:
        # The path or an open stream resource
        imagepng( $im, $tmpFile );
        imagedestroy( $im ); // Speicher sparen
      
        // Bild senden
        header( 'Content-Type: image/png' );
        header( 'Content-Length: '.filesize( $tmpFile ) );
      
        readfile( $tmpFile );
        // Nicht vergessen:
        unlink( $tmpFile );
      }
      

      Eigentlich braucht man die Datei nicht. Eine solche zu erzeugen ist z.B. dann sinnvoll, wenn man die Grafik, statt diese aufwendig zu speichern und gleich wieder zu löschen, für wiederholte Abrufe cachen will. Sonst reicht es diese im Speicher zu halten.

      1. problematische Seite

        hi

        Eigentlich braucht man die Datei nicht. Eine solche zu erzeugen ist z.B. dann sinnvoll, wenn man die Grafik, statt diese aufwendig zu speichern und gleich wieder zu löschen, für wiederholte Abrufe cachen will. Sonst reicht es diese im Speicher zu halten.

        Das Cachen habe ich unterdessen über Last-Modified (Erster des Monats) und Expires (Letzter des Monats) geregelt.

        MFG

        1. problematische Seite

          Das Cachen habe ich unterdessen über Last-Modified (Erster des Monats) und Expires (Letzter des Monats) geregelt.

          Dann und genau dann wenn die Grafik mit dem Monat zu tun hat und Du genau einen Nutzer hast ist das sicher eine gute Idee. Aber das weisst Du.

    2. problematische Seite

      Danke für den zweiten [to] Parameter. Habch doch glatt überlesen 😉

      MFG

  3. problematische Seite

    Leider fehlt der Header Content-Length, wie bringe ich PHP dazu einen Solchen anzuhängen?

    • Genau genommen wird der Payload (hier die Grafik) nach einer Leerzeile als Trenner an den HTTP-Header angehangen. (Gerade Du weisst das wohl, aber andere nicht.) Deshalb muss die Länge bekannt sein (sonst kann man sie nicht im Header senden).

    • Eigentlich kann man bei aktiviertem Output-Buffering die Header auch nach dem Payload setzen (PHP sortiert dann die Rückgabe an den Web-Server). Aber: Die von mir und Dedlfix vorgeschlagene Vorgehensweise funktioniert mit JEDER Konfiguration des Output-Buffers (Autostart oder nicht).

    1. problematische Seite

      Lieber Jörg,

      Die von mir und Dedlfix vorgeschlagene Vorgehensweise

      hat sich @dedlfix auch schon hierzu geäußert?

      Liebe Grüße

      Felix Riesterer

      WIESO KANN ICH DEN Beitrag von Felix Riesterer bearbeiten? Dafür aber meine eigenen NICHT?

      (Raketenwissenschaftler)

      @Felix: Sorry!

      1. problematische Seite

        Hallo Jörg,

        der Bug ist behoben. Danke für den Hinweis.

        Das nächste mal wäre es nett, wenn du das mir und/oder Matthias im privaten via Email mitteilst und das im voraus nicht so öffentlich breit trittst. Damit zwingst du mich unmittelbar zu reagieren; in diesem Fall war ich gezwungen meine Jogging-Runde zu unterbrechen 😉 Stichwort responsible disclosure.

        Ob du das nach dem Fix veröffentlichst bleibt dann wieder dir überlassen.

        Freundliche Grüße,
        Christian Kruse

        1. problematische Seite

          Das nächste mal wäre es nett, wenn du das mir und/oder Matthias im privaten via Email mitteilst und das im voraus nicht so öffentlich breit trittst.

          Ja, tut mir leid. Da war ich wohl (10:48) mental überfordert (a.k.a. "in Panik") und kam erst spät (11:11) auf die Idee, mal nach den Mailadressen zu sehen.

          Danke für die schnelle Problembehebung.

          1. problematische Seite

            Hallo Raketenwissenschaftler,

            Das nächste mal wäre es nett, wenn du das mir und/oder Matthias im privaten via Email mitteilst und das im voraus nicht so öffentlich breit trittst.

            Ja, tut mir leid. Da war ich wohl (10:48) mental überfordert (a.k.a. "in Panik") und kam erst spät (11:11) auf die Idee, mal nach den Mailadressen zu sehen.

            Das ist nachvollziehbar.

            Ich bin auch etwas erschrocken darüber, dass dieser Bug bereits seit V 5.0 vorhanden ist und jetzt erst aufgefallen ist… 😂

            Danke für die schnelle Problembehebung.

            👍 wie gesagt, danke für den Hinweis.

            Freundliche Grüße,
            Christian Kruse

            1. problematische Seite

              Ich bin auch etwas erschrocken darüber, dass dieser Bug bereits seit V 5.0 vorhanden ist und jetzt erst aufgefallen ist…

              Da bin ich mir gar nicht so sicher, dass dieser Bug schon so lange lebt. Ich denke, das hätte ich vorher auch schon sehen können bzw. müssen. Vielleicht lebte er erst als "Fernwirkung" einer anderen Änderung auf.

              Allerdings waren die Voraussetzungen der Entdeckung schon hoch:

              • Man durfte nicht angemeldet sein.
              • Die betroffenen Beiträge mussten noch in dem Zeitraum liegen, in welchem eine Bearbeitung zulässig ist und
              • noch keine Antworten haben.
              • Dann musste auch noch erkannt werden, dass da eine Bearbeiten-Taste aufscheint, die da nicht hingehört.

              Was mich (über mich) wundert: In der Panik habe ich einerseits falsch reagiert aber gleichzeitig die Ursache des Problems richtig eingegrenzt - ohne die Software zu kennen. (Freilich kenne ich das "grundsätzliche Wirkprinzip"...)

              Womöglich eine Schwäche und eine Stärke: "Augen auf bei der Berufswahl".

              1. problematische Seite

                Hallo Raketentest-,

                Da bin ich mir gar nicht so sicher, dass dieser Bug schon so lange lebt.

                Ich schon.

                Ich denke, das hätte ich vorher auch schon sehen können bzw. müssen. Vielleicht lebte er erst als "Fernwirkung" einer anderen Änderung auf.

                Jain. Es ist dir deshalb nicht aufgefallen, weil wir seit kurzem die Identitätscookies nicht mehr setzen. Deshalb bist du vermehrt unerkannt (im Sinne der Software) hier unterwegs, und deshalb konnte der Bug überhaupt erst auftreten. Siehe auch.

                • Man durfte nicht angemeldet sein.
                • Die betroffenen Beiträge mussten noch in dem Zeitraum liegen, in welchem eine Bearbeitung zulässig ist und
                • noch keine Antworten haben.
                • Dann musste auch noch erkannt werden, dass da eine Bearbeiten-Taste aufscheint, die da nicht hingehört.

                Da fehlt noch eine Bedingung 😉 du musstest ohne Identitätscookie unterwegs sein.

                Freundliche Grüße,
                Christian Kruse

    2. problematische Seite

      Diese Zeile und das Topic "zu diesem Forum" hinzugefügt. Siehe Subjekt. Ich kann praktisch nur mein eigenes Zeug und das von nicht angemeldeten Nutzern (hier: PL) NICHT bearbeiten. (Und Beiträge mit Antworten oder die, die zu alt sind). Ein falscher Vergleich der Session?


      Hallo,

      aus aktuellem Anlass, weil dieser Fehler sehr oft gemacht wird und immer mein inneres Schmerzzentrum stimuliert:

      • Genau genommen wird der Payload (hier die Grafik) nach einer Leerzeile als Trenner an den HTTP-Header angehangen.

      Nein, angehängt.

      Das Partizip Perfekt von hängen ist gehängt, wenn es transitiv verwendet wird, und gehangen, wenn es intransitiv verwendet wird.

      Beispiel:
      "Sieh mal, Schulze hat ein Bild im Korridor aufgehängt."
      "Ja, das hat vorher monatelang in seinem Büro gehangen."

      Ich bitte um Beachtung.

      Herzlichen Dank,
       Martin

      --
      Nein, ich bin kein Klugscheißer. Ich weiß es wirklich besser.
      1. Dieser Beitrag wurde gelöscht: Der Beitrag ist außerhalb des durch dieses Forum abgedeckten Themenbereichs.
    3. problematische Seite

      Ich habe hier einen Apache der sendet die Grafik mit Transfer-Encoding chunked. D.h. daß der Apache den Puffer schon bekommt wenn die Grafik noch gar nicht fertig ist, also noch Daten kommen aus dem Puffer. Also der Apache die fertige Länge nicht kennt aber seinerseits den Puffer schon ausgibt.

      Wenn Transfer-Encoding gzip konfiguriert ist, packt der Apache die Daten erst zusammen bevor er die sendet. In diesem Fall wird auch Content-Length gesendet weil der Apache ja die Länge der Zipdatei kennt.

      MFG

      1. problematische Seite

        Ich habe hier einen Apache der sendet die Grafik mit Transfer-Encoding chunked. D.h. daß der Apache den Puffer schon bekommt wenn die Grafik noch gar nicht fertig ist, also noch Daten kommen aus dem Puffer. Also der Apache die fertige Länge nicht kennt aber seinerseits den Puffer schon ausgibt.

        Dann reicht der Puffer nicht.

        Mit free nachschauen wie der Speicher aussieht ung ggf. größeren Outputbuffer festlegen. Die paar KB sollten nicht stören.

        Wenn Transfer-Encoding gzip konfiguriert ist, packt der Apache die Daten erst zusammen bevor er die sendet. In diesem Fall wird auch Content-Length gesendet weil der Apache ja die Länge der Zipdatei kennt.

        Na dann. Welcher Browser mags denn noch ungezippt?

        1. problematische Seite

          Ich habe hier einen Apache der sendet die Grafik mit Transfer-Encoding chunked. D.h. daß der Apache den Puffer schon bekommt wenn die Grafik noch gar nicht fertig ist, also noch Daten kommen aus dem Puffer. Also der Apache die fertige Länge nicht kennt aber seinerseits den Puffer schon ausgibt.

          Dann reicht der Puffer nicht.

          Dieses Verhalten hat damit nichts zu tun sondern ist in HTTP begründet.

          Wenn ich mit

          $this->CONTENT = ob_get_clean();
          header("Content-Length: ".strlen($this->CONTENT));
          

          denselben Puffer am Stück ausgebe incl. Content-Length, gibt es auch kein Transfer-Encoding.

          MFG

          1. problematische Seite

            Kurze Darstellung: PHP puffert bis zur in der PHP.INI eingestellten Grenze und schickt den Puffer zum Apache, der diesem zum Client schickt.

            Das hat also sehr wohl primär mit PHP zu tun. Schau mach den wirksamen PHP.inis such die Puffergröße, setze diese auf einen Wert, der größer als Deine Graphik ist und, wenn PHP als Modul läuft, dann starte den Apache neu.

            1. problematische Seite

              Lange Darstellung:

              1. PHP puffert bis zur in der PHP.INI eingestellten Grenze und schickt den Puffer zum Apache, der diesem zum Client schickt. Da noch nicht fertig: Goto 1.
              2. Beim Skriptende schickt PHP den Puffer (also seinen Inhalt) ebenfalls dem Apache, der diesen - mit EOT - alsdann auch zum Client schickt.