Marcus: PHP: Seite ausgeben und Datei-Download gleichzeitig

Hallo,

mein PHP-Skript generiert eine XML-Datei, die auf den Client-Rechner übertragen werden soll. Gleichzeitig soll im Layout der Homepage eine Bestätigungsseite angezeigt werden.

Bei mir startet der Download wie geplant, doch die Ausgabe im Browser bleibt unverändert. Klicke ich noch einmal auf den Weiter-Button, wird die Datei ein zweites Mal heruntergeladen.

Wo liegt der Fehler?

Viele Grüße Marcus

  1. Wo liegt der Fehler?

    Ist doch klar! Du hast was falsch gemacht. Was Du falsch gemacht hast können wir nicht wissen.

    Aufgabe: Überlege, warum das so ist. Lösungshilfe: Lese Deinen Beitrag und finde heraus, ob und welche Informationen Du preisgibst und dann, ob und wie Du uns dabei hilfst, Dir zu helfen.

    1. Hm.

      Wie so oft, sind das zwei Seiten einer Medaille. Du hättest gern mehr Informationen, ich habe erst einmal keine Vorstellung, was relevant sein könnte. Klar, ich könnte jetzt das ganze Skript posten, doch das ist recht komplex und mind. 95% der Fülle ist für die Lösung irrelevant.

      Ich hoffe auf kleine Tipps, z. B. Habe ich darauf spekuliert, dass man zwei Header senden könnte - einen für das HTML- und einen für das XML-Format.

      1 Request - 1 Response. Klare Aussage, was ich möchte, scheint ohne Weiteres nicht möglich. Damit kann man was anfangen.

      1. Klar, ich könnte jetzt das ganze Skript posten, doch das ist recht komplex und mind. 95% der Fülle ist für die Lösung irrelevant.

        Die irrelevanten Teile will auch keiner sehen.

        Du wirst also hübsch selbst einen Test bauen müssen, der ohne jedes Geraffel zeigt, wie Du den Download hoffnungsfroh veranlasst (Link, Formular, Skript) und wie Du den Download zwar ebenso hoffnungsfroh, aber nicht mit dem gewünschtem Ergebnis bewerkstelligst (ggf. Skript, Einstellungen des Webservers.) Und dann zeigst Du den Test vor.

        Möglicherweise stellt sich ja heraus, dass der Download und die Anzeige funktionieren würde, wenn es denn nicht in den 95%-Geraffel einen ganz anderen Skriptfehler gäbe.

        Also wirst Du auch die Serverlogs und die Ausgaben der JS-Konsole in der Entwicklungsumgebung des Browser auf Fehlermeldungen untersuchen und ggf. diese und die betroffenen Zeilen der Ressourcen zeigen müssen.

        Und sollten da Namen von Ressourcen (oft: Dateien) und Zeilennummern aufscheinen, dann musst Du diese Zeilen (und die relevanten zuvor) natürlich auch zeigen. Ohne Deinen Code zu kennen können wir Deinen Code nicht debuggen. So einfach ist das.

        Hier ist SELFhtml.org, das hat eine öffentlich bekannte IP-Adresse und nicht getFuckingScriptNow.com, welches durch seine Kontonummer charakterisiert ist.

        Deshalb hat keiner Lust, Dir oder anderen drölf Lösungen zu zeigen und sich von Dir gemäß des Lieds vom Topf und dem Loch dann jeweils „sagen“ zu lassen, dass diese Lösung „nicht funktioniert“ - und das womöglich auch noch ohne Begründung. (Und der Erfahrung nach hin- und wieder sogar auf pampige Weise oder in einem herabwürdigendem Ton.)

      2. und einen für das XML-Format.

        Wenn Du einen Download bewerkstelligen willst, dann würde ich genau das vermeiden.

        Sende für Downloads einen Content-Type „application/octet-stream“.

        Grund: XML wollen viele der Browser ebenso wie PDF, PNG, HTML, Text ... selbst öffnen und darstellen. Nur bei obiger Angabe kann man einen Download - also das Angebot, die Datei zu speichern (oder dieses stillschweigend zu tun) „garantieren“.

        1. Hallo Raketenwilli,

          d.h. ein Response-Header "Content-Disposition: attachment" kann vom Browser schnöde ignoriert werden?

          Man kann aber auch auf die Bestätigungsseite einen Button "Speichern" setzen, der in Wahrheit ein Link mit dem download-Attribut ist. Der sollte seit ein paar Jahren definitiv für einen Download sorgen, unabhängig vom Content-Disposition Header.

          Die Challenge ist noch, den Download nach dem Empfang der Bestätigungsseite automatisch anzustoßen. Dinge wie window.open() oder click() auf einen Link werden in JavaScript ja nur erlaubt, wenn das Script durch eine Benutzerinteraktion angestoßen wurde - und das ist in einer Ajax-Response oder in einem DOMContentLoaded- oder load-Event nicht der Fall. Geht das überhaupt? Wenn ja, wie genau? Ich denke, dass Browser sich mit Händen und Füßen gegen einen drive-by Download wehren.

          Rolf

          --
          sumpsi - posui - obstruxi
          1. Ich hab mir das bei Ubuntu mal angesehen.

            Da wird per Java-Script der Download via window.location.href='...' angestoßen und sich von Anfang an durch Sichtbarmachen eines Danke-Abschnitts für den Download, das Vertrauen und so weiter bedankt.

            Da gibt es allen Ernstes einen Stapel JavaSkript-Funktionen, der dann in

            function delayStartDownload(downloadLink, delay) {
              window.setTimeout(function () {
                window.location.href = downloadLink;
              }, delay);
            }
            

            endet. Das Delay steht bei 3 Sekunden (wahrscheinlich haben sich 3 von 4 Testern, „die gegenüber dem Internet keine Vorbehalte oder Voreinstellung haben“ dafür entschieden, dass 3 Sekunden zu warten, etwas Tolles sei. Nur habe ich in der Zeit was besseres zu tun und meistens schon den Link zum direkten Download angeklickt, denn:

            Einen skriptfreien Downloadlink gibt es a) via

            <noscript><a href="...">Klicken Sie hier um pla blubber</a></noscript>
            

            und b) dann nochmal bei oben gezeigtem „Danke“, a.k.a. „Wenn der Download nicht funktioniert klicken sie bitte hier“.

            Und das alles verpackt in 72 die Umwelt belastende Kilobytes…

            Ich denke, dass Browser sich mit Händen und Füßen gegen einen drive-by Download wehren.

            Naja: Immerhin fragt der Browser, wo er das hin tun soll. Und es gibt in diesem Dialog eine Taste zum Abbrechen.

            d.h. ein Response-Header "Content-Disposition: attachment" kann vom Browser schnöde ignoriert werden?

            Why not? Mindestens der IE hat (sofern ich mich recht erinnere) sogar den explizit gesetzten Content-Type manchmal ignoriert, den Mime-Type selbst untersucht und dann das getan, was er (aus Sicht eines vernünftigen und verständigen Benutzers) tunlichts nicht tun sollte ...

            1. d.h. ein Response-Header "Content-Disposition: attachment" kann vom Browser schnöde ignoriert werden?

              Why not? Mindestens der IE …

              Ich hab hier einen schönen Test-Case: (ist ein halbes Jahr alt...)

              https://home.fastix.org/Tests/download_me.php

              Mist. Jetzt hat der Markus - wenn er nachdenkt - doch sein fertiges Skript von mir bekommen. Er muss nur von mir gepostetes und gezeigtes Zeug richtig zusammenstöpseln.

  2. Hallo Marcus,

    mein PHP-Skript generiert eine XML-Datei, die auf den Client-Rechner übertragen werden soll. Gleichzeitig soll im Layout der Homepage eine Bestätigungsseite angezeigt werden.

    dann musst du einen Mechanismus bauen, der diese beiden Transfers nacheinander ausführt.

    Bei mir startet der Download wie geplant, doch die Ausgabe im Browser bleibt unverändert. Klicke ich noch einmal auf den Weiter-Button, wird die Datei ein zweites Mal heruntergeladen.

    Beschreib mal, wie du das realisiert hast. Und wie du meinst, dass das funktioniert.

    Wo liegt der Fehler?

    Im falschen Verständnis von HTTP. Hier gilt ganz klar: Ein Request, eine Response.

    Einen schönen Tag noch
     Martin

    --
    Kaffee ist nur schädlich, wenn Ihnen ein ganzer Sack aus dem 5. Stock auf den Kopf fällt.
  3. Hello,

    mein PHP-Skript generiert eine XML-Datei, die auf den Client-Rechner übertragen werden soll. Gleichzeitig soll im Layout der Homepage eine Bestätigungsseite angezeigt werden.

    Bei mir startet der Download wie geplant, doch die Ausgabe im Browser bleibt unverändert. Klicke ich noch einmal auf den Weiter-Button, wird die Datei ein zweites Mal heruntergeladen.

    Wo liegt der Fehler?

    Beim Verständnis für das Protokoll:

    Ein Request -> eine Response

    Wäre schlimm, wenn das anders wäre!

    Die Erstantwort (Anzeigeseite) muss eine weitere öffnen, die den Download verursacht. Die wird aber dann eventuell als offene Seite stehen bleiben.

    Glück Auf
    Tom vom Berg

    --
    Es gibt soviel Sonne, nutzen wir sie.
    www.Solar-Harz.de
    S☼nnige Grüße aus dem Oberharz
    1. Hallo TS,

      Die Erstantwort (Anzeigeseite) muss eine weitere öffnen, die den Download verursacht. Die wird aber dann eventuell als offene Seite stehen bleiben.

      Es gibt aber durchaus Seiten, die das hinkriegen. Es kommt eine Antwortseite und es startet automatisch ein Download. Ich hab das mal zu googeln versucht, die Tipps hießen: iframe, der den Download auslöst, oder ein window.open.

      Ich selbst habe sowas noch nicht gebaut und weiß nicht, was davon in welchem Brauser wie gut funktioniert. Auf jeden Fall wird man einen Download-Link als Fallback bereitstellen müssen.

      Rolf

      --
      sumpsi - posui - obstruxi
      1. Hello,

        Die Erstantwort (Anzeigeseite) muss eine weitere öffnen, die den Download verursacht. Die wird aber dann eventuell als offene Seite stehen bleiben.

        Es gibt aber durchaus Seiten, die das hinkriegen. Es kommt eine Antwortseite und es startet automatisch ein Download. Ich hab das mal zu googeln versucht, die Tipps hießen: iframe, der den Download auslöst, oder ein window.open.

        Schrieb ich doch: eine zweite Seite.
        Ein iframe oder ein Popup sind ein weiteres Dokument.

        Glück Auf
        Tom vom Berg

        --
        Es gibt soviel Sonne, nutzen wir sie.
        www.Solar-Harz.de
        S☼nnige Grüße aus dem Oberharz
      2. Prima, das ist doch schon mal ein konstruktiver Lösungsvorschlag. Der Hinweis auf 1 Request - 1 Response ist ja nachvollziehbar.

        Ich versuche mich mal in einer Umsetzung.

        1. Hallo Marcus,

          wie wäre das:

          <?php
          // XML generieren
          $xml = '<?xml version="1.0" encoding="UTF-8"?>
          <root>
            <element1>Wert 1</element1>
            <element2>Wert 2</element2>
          </root>';
          
          // Dateinamen für XML generieren
          $file_name = 'example.xml';
          
          // XML-Datei auf Server speichern
          file_put_contents($file_name, $xml);
          
          // Header für Dateidownload senden
          header('Content-Type: application/xml');
          header('Content-Disposition: attachment; filename="' . $file_name . '"');
          header('Content-Length: ' . filesize($file_name));
          
          // XML-Datei an den Client senden
          readfile($file_name);
          
          // Bestätigungsseite anzeigen
          echo '<html>
                  <head>
                      <title>Bestätigung</title>
                  </head>
                  <body>
                      <h1>XML-Datei erfolgreich generiert und heruntergeladen!</h1>
                  </body>
              </html>';
          
          // XML-Datei vom Server löschen
          unlink($file_name);
          ?>
          

          Gruss
          Henry

          --
          Meine Meinung zu DSGVO & Co:
          „Principiis obsta. Sero medicina parata, cum mala per longas convaluere moras.“
          1. Hallo Henry,

            meine Einschätzung ist: damit sendest Du XML und HTML in einem Datenstrom und speicherst beides zusammen in einer Datei.

            Daher füge ich mal ein Minus hinzu. Zur wiederholten Erklärung: das bedeutet "Das ist nicht hilfreich als Problemlösung". Es besagt nicht "Ich mag Henry nicht".

            Rolf

            --
            sumpsi - posui - obstruxi
          2. Hallo,

            dein Lösungsvorschlag ist in mehreren Punkten kritikwürdig.

            Zum Beispiel: Warum speicherst du das XML-Dokument überhaupt als Datei? Wenn schon, kannst du den XML-String auch direkt an den Browser senden.

            Aber viel problematischer ist das hier:

            // Header für Dateidownload senden
            header('Content-Type: application/xml');
            header('Content-Disposition: attachment; filename="' . $file_name . '"');
            header('Content-Length: ' . filesize($file_name));
            
            // XML-Datei an den Client senden
            readfile($file_name);
            
            // Bestätigungsseite anzeigen
            echo '<html>
                    <head>
                        <title>Bestätigung</title>
                    </head>
                    <body>
                        <h1>XML-Datei erfolgreich generiert und heruntergeladen!</h1>
                    </body>
                </html>';
            
            // XML-Datei vom Server löschen
            unlink($file_name);
            ?>
            

            Per HTTP-Header kündigst du dem Client an: Hier kommt jetzt ein XML-Dokument mit einer Länge von soundsoviel Bytes. Das wird er dann auch empfangen und womöglich zum Speichern anbieten.

            Dass danach noch ein Stück HTML kommt, wird er aber ignorieren. Denn erstens ist die angekündigte Nutzdatenlänge erreicht, weiterlesen wäre also Unsinn; zweitens würde, selbst wenn die Länge entsprechend korrigiert wird, das Stück HTML noch dem XML-Dokument zugeschlagen.

            Einen schönen Tag noch
             Martin

            --
            Im Englischen hat eine Katze neun Leben. Im Deutschen vielleicht auch, aber nach Abzug der Steuern bleiben nur noch sieben übrig.
            1. Hello Martin,

              außerdem darf man den Dateinamen nicht "unkastriert" an den Client senden. Da gibt es immer noch welche, die den dann wörtlich nehmen würden!

              Und man kann bei den meisten Browsern heute auch einen MIME-Type

              mixed

              senden. Dafür muss das Dokument dann genauso aufgebaut werden, wie für eine HTML-eMail (mixed, multipart).

              Käme also auf einen Versuch an, wieviele Browser das schon verstehen.

              Außerdem lohnt es sich mMn auch, nochmal die HTTP-Status zu durchforsten, was da an Responsetypen bereits vorgesehen ist.

              Der 401 akzeptiert nämlich durchaus eine nachfolgende HTML-Sequenz für den Fall, dass man auf "Abbrechen" o. ä. klickt.

              Andere Status (pl.) können das auch.

              Glück Auf
              Tom vom Berg

              --
              Es gibt soviel Sonne, nutzen wir sie.
              www.Solar-Harz.de
              S☼nnige Grüße aus dem Oberharz
              1. Hallo TS,

                Der 401 akzeptiert nämlich durchaus eine nachfolgende HTML-Sequenz für den Fall, dass man auf "Abbrechen" o. ä. klickt.

                Das tun die 400er genau wie die 500er. Bei den 500ern hab ich damit viele Erfahrungen, eine ASP.NET Anwendung bringt nämlich bei einer ungefangenen Exception einen 500er und - sofern nicht für Produktion abgeschaltet - einen ausführlichen Fehler-Dump.

                Den MIME-Type "mixed" hätte ich gern näher erklärt. Ein Link auf ein Beispiel reicht…

                Rolf

                --
                sumpsi - posui - obstruxi
                1. Hello,

                  Der 401 akzeptiert nämlich durchaus eine nachfolgende HTML-Sequenz für den Fall, dass man auf "Abbrechen" o. ä. klickt.

                  Das tun die 400er genau wie die 500er. Bei den 500ern hab ich damit viele Erfahrungen, eine ASP.NET Anwendung bringt nämlich bei einer ungefangenen Exception einen 500er und - sofern nicht für Produktion abgeschaltet - einen ausführlichen Fehler-Dump.

                  Das verstehe ich jetzt überhaupt nicht.

                  Könntest Du das bitte nochmal ausführlich in deutscher Sprache erklären? Das würde mir helfen.

                  Glück Auf
                  Tom vom Berg

                  --
                  Es gibt soviel Sonne, nutzen wir sie.
                  www.Solar-Harz.de
                  S☼nnige Grüße aus dem Oberharz
                  1. Hallo TS,

                    reden wir aneinander vorbei? Ich spreche davon, dass diese Analyse nicht wirklich nötig ist, denn ein Webserver kann eigentlich bei jedem HTTP Status eine Antwort mitsenden. Die wird zwar nicht unbedingt angezeigt - das hängt aber auch am Browser.

                    Was ich dann schreiben wollte, war: Dieser Inhalt kann z.B. aus einem Fehlerbericht bestehen, wenn das Programm abgestürzt ist, das den Inhalt generieren sollte.

                    Beispiel mit PHP: Eine Funktion, die mittels set_error_handler, set_exception_handler oder register_shutdown_function eingerichtet wurde, könnte je nach Lage den HTTP Status auf 500 setzen und eine HTML Seite mit einem Fehlerbericht und Stacktrace ausgeben (natürlich nur, solange headers_sent() false zurückgibt, sonst kann man keinen Response-Status mehr setzen).

                    In ASP.NET kann das so aussehen - dem fällt das leicht, weil die .net Runtime immer unter dem User-Code steckt…

                    Rolf

                    --
                    sumpsi - posui - obstruxi
          3. Hallo an alle Kritiker,

            ihr habt vollkommen recht. Das war ein Experiment, ich wollte eure ungefilterte objektive Meinung dazu haben. Heute läuft im TV ZDF Maybritt Illner 22:15 Uhr "Künstliche Intelligenz – Maschine gegen Mensch?" Vor allem zum Thema ChatGPT und da dachte ich mir, guter Zeitpunkt die geballte Schwarmintelligenz hier mal darauf zu jagen ohne Voreingenommenheit. Danke 😉

            Übrigens auch ausserhalb vom IT Bereich, oft katastrophale Falschaussagen, ja geradezu Lügen, die jede Suchmaschine besser beantwortet. So werden Gesetzestexte zitiert mit Paragraph und allem die völlig anderen Inhalt haben. Das Programm entschuldigst sich dann zwar, aber versucht gleichzeitig dem Fragesteller die Schuld zu geben.

            Gruss
            Henry

            --
            Meine Meinung zu DSGVO & Co:
            „Principiis obsta. Sero medicina parata, cum mala per longas convaluere moras.“
            1. Hallo Henry,

              🤣

              Nette Idee, uns eine ChatGPT Antwort ungefiltert vorzusetzen.

              Und ja, dieser Bot weist immer wieder gefährliche Lücken auf. Neulich hatten wir mal das Punkt-vor-Strich Thema. Angeblich ist das mittlerweile behoben.

              Für dein Codebeispiel hatte er entweder eine kaputte Vorlage, die er ungefiltert wiedergegeben hat, oder er hat frech zwei Vorlagen zusammengemixt. Das zeigt wieder einmal die Gefahr falscher Freunde. Das englische Wort "intelligence" kann, aber muss nicht mit „Intelligenz“ übersetzt werden. Gelegentlich kann AI auch für "Atrocious Imbecile" stehen.

              Rolf

              --
              sumpsi - posui - obstruxi