Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Programmieren allgemein (https://www.delphipraxis.net/40-programmieren-allgemein/)
-   -   Delphi Große Datei über WebBroker als 'application/octet-stream' zur Verfügung stellen (https://www.delphipraxis.net/214915-grosse-datei-ueber-webbroker-als-application-octet-stream-zur-verfuegung-stellen.html)

fisipjm 4. Apr 2024 11:29

Große Datei über WebBroker als 'application/octet-stream' zur Verfügung stellen
 
Hi,

kurzer Umgebungsüberblick.
- Delphi Version 10.2
- WebBroker aktuell als eigenständiger Webserver lauffähig (später ISAPI Modul im IIS)
- Dateien mit Größe 100mb +

Also, ich versuche ein Funktion zu erstellen, die mir eine Datei auf dem Filesystem als 'application/octet-stream' zur Verfügung stellt.
Funktioniert soweit auch schon. Verwende dafür einfach einen
Delphi-Quellcode:
TByteStream.LoadFromFile('FilePath')
.

das ganze sieht dann so aus:
Delphi-Quellcode:
 Response.Content := '';
 Response.ContentType := 'application/octet-stream';
 Response.ContentStream := TBytesStream.Create;
 TBytesStream(Response.ContentStream).LoadFromFile('FilePath');
Funktioniert Problemlos bis Dateien von ca. 50MB. Dann bekomme ich einen Socket Fehler 10054 Die Verbindung wurde von Peer zurückgesetzt. Ich nehme mal an, das hängt mit dem reservierten Speicher zusammen. Kennt das jemand und wie würde man das umgehen?
Die Abfrage in Chunks aufteilen könnte helfen, aber ich weiß nicht wie ich das mit dem Websocket umsetzen kann, der Sendet ja zum schluss erst alles gebündelt raus und nicht schon zwischenzeitlich, oder?

vG
PJM

Michael II 4. Apr 2024 16:26

AW: Große Datei über WebBroker als 'application/octet-stream' zur Verfügung stellen
 
Kenne Indy leider nicht. Timeoutfehler?
Wieso verwendest du nicht einen TFileStream und zeigst dann mit Contentstream auf diesen? Loadfromfile entfällt dann.

Anderer Ansatz: Falls auf Clientseite ein Browser genutzt wird: Du könntest als Antwort auch einfach auf die Location des Files zeigen (vielleicht willst du das nicht). Also zum Beispiel Statuscode 302 (oder sonst einen Redirectcode) und Response.Location:=<filename> zurückgeben.

Olli73 4. Apr 2024 23:29

AW: Große Datei über WebBroker als 'application/octet-stream' zur Verfügung stellen
 
Wie geht denn der Code weiter? Gibst du vielleicht "Response.ContentStream" selber frei (und das bevor er komplett übertragen ist)?

fisipjm 8. Apr 2024 08:32

AW: Große Datei über WebBroker als 'application/octet-stream' zur Verfügung stellen
 
Zitat:

Zitat von Michael II (Beitrag 1535318)
Kenne Indy leider nicht. Timeoutfehler?
Wieso verwendest du nicht einen TFileStream und zeigst dann mit Contentstream auf diesen? Loadfromfile entfällt dann.

Anderer Ansatz: Falls auf Clientseite ein Browser genutzt wird: Du könntest als Antwort auch einfach auf die Location des Files zeigen (vielleicht willst du das nicht). Also zum Beispiel Statuscode 302 (oder sonst einen Redirectcode) und Response.Location:=<filename> zurückgeben.

Das System hat leider eine Mischspeicherung aus Blobs in einer DB und Files auf dem Dateisystem. Deshalb kleinster gemeinsamer Nenner der TByte Stream. Aber das LoadFromFile funktioniert ja, er knallt erst irgendwo in den Tiefen des Brokers, nachdem meine Funktion eigentlich schon abgearbeitet ist. Bekomme auch leider nur das CPU Fenster beim DEBUGGING angezeigt. :|

fisipjm 8. Apr 2024 08:32

AW: Große Datei über WebBroker als 'application/octet-stream' zur Verfügung stellen
 
Zitat:

Zitat von Olli73 (Beitrag 1535337)
Wie geht denn der Code weiter? Gibst du vielleicht "Response.ContentStream" selber frei (und das bevor er komplett übertragen ist)?

Nein, wird extra nicht frei gegeben, weil ich sowas auch schon vermutet hatte. Denke das, muss der Broker machen, weil ich nicht entscheiden kann wann der Stream fertig übermittelt wurde.

Michael II 8. Apr 2024 14:54

AW: Große Datei über WebBroker als 'application/octet-stream' zur Verfügung stellen
 
Ich habe rasch dies mit deinem Code getestet. Delphi 11.2. Win 11 Pro, aktueller IIS. Als CGI in IIS.

Delphi-Quellcode:
program filedownloadcgi;

{$APPTYPE CONSOLE}

uses
  WebBroker,
  CGIApp,
  WebModuleUnit1 in 'WebModuleUnit1.pas' {WebModule1: TWebModule};

{$R *.res}

begin
  Application.Initialize;
  Application.WebModuleClass := WebModuleClass;
  Application.Run;
end.
Delphi-Quellcode:
unit WebModuleUnit1;

interface

uses
  System.SysUtils, System.Classes, HTTPApp;

type
  TWebModule1 = class(TWebModule)
    procedure WebModuleBeforeDispatch(Sender: TObject; Request: TWebRequest;
      Response: TWebResponse; var Handled: Boolean);
  private
    { Private-Deklarationen }
  public
    { Public-Deklarationen }
  end;

var
  WebModuleClass: TComponentClass = TWebModule1;

implementation


{$R *.dfm}

procedure TWebModule1.WebModuleBeforeDispatch(Sender: TObject;
  Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);

var
    fn : string;

begin
  fn := paramstr(1) + 'test.bin';

  Response.Content := '';
  Response.ContentType := 'application/octet-stream';
  Response.ContentStream := TBytesStream.Create;
  TBytesStream(Response.ContentStream).LoadFromFile(fn);

  Handled := true;
end;

end.
Funktioniert bei mir auch mit 300MB Files ohne Probleme.
Ist nicht 100%ig das, was du machst - aber Webbroker funktioniert also.

Falls du immer noch glaubst, dass es am Speicher liegt, dann prüf doch mal, ob der Tipp mit Response.Location :=... funktioniert. Dann regelt IIS für dich die Übertragung der Files. Daran denken: In IIS die MIME Typen setzen.

Der von dir erwähnte WSock Error 10054 sollte doch als Windows Ereignis geloggt werden? Findest du dort keine Infos?


Und halt doch noch einmal wegen Speicher... Wenn ich mit perfmon beobachte, was passiert, wenn ich deinen LoadFromFile Ansatz verwende, dann sind nach dem LoadFromFile die 300MB Dateidaten zusätzlich im Arbeitsspeicher (erwartet).

Falls ich perfmon vertrauen kann, dann ist es mit TfileStream (erwartet) nicht so. Ich hab's nun auch überprüft: Setze in Web.HttpApp bei procedure TWebResponse.SendStream(AStream: TStream); einen Breakpoint, dann siehst du wie Buffer für Buffer (1MB) gesendet wird und so der Arbeitsspeicher vom Server nicht überlastet wird.

Also so:
Delphi-Quellcode:
procedure TWebModule1.WebModuleBeforeDispatch(Sender: TObject;
  Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
var
    fn : string;
    tf : tfileStream;

begin
  fn := 'C:\inetpub\wwwroot\test.bin';
  Response.Content := '';
  Response.ContentType := 'application/octet-stream';
  Response.CustomHeaders.Values['Content-Disposition'] := 'attachment; filename=test.bin';
  tf := tfilestream.Create( fn, fmopenread or fmShareDenyWrite );
  Response.ContentStream := tf;
  handled := true;
end;
Ich setze hier zusätzlich auch noch den gewünschten Filenamen (muss natürlich nicht dem Filenamen auf dem Server entsprechen).

fisipjm 10. Apr 2024 15:22

AW: Große Datei über WebBroker als 'application/octet-stream' zur Verfügung stellen
 
Zitat:

Zitat von Michael II (Beitrag 1535514)

Falls ich perfmon vertrauen kann, dann ist es mit TfileStream (erwartet) nicht so. Ich hab's nun auch überprüft: Setze in Web.HttpApp bei procedure TWebResponse.SendStream(AStream: TStream); einen Breakpoint, dann siehst du wie Buffer für Buffer (1MB) gesendet wird und so der Arbeitsspeicher vom Server nicht überlastet wird.

Also so:
Delphi-Quellcode:
procedure TWebModule1.WebModuleBeforeDispatch(Sender: TObject;
  Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
var
    fn : string;
    tf : tfileStream;

begin
  fn := 'C:\inetpub\wwwroot\test.bin';
  Response.Content := '';
  Response.ContentType := 'application/octet-stream';
  Response.CustomHeaders.Values['Content-Disposition'] := 'attachment; filename=test.bin';
  tf := tfilestream.Create( fn, fmopenread or fmShareDenyWrite );
  Response.ContentStream := tf;
  handled := true;
end;
Ich setze hier zusätzlich auch noch den gewünschten Filenamen (muss natürlich nicht dem Filenamen auf dem Server entsprechen).

Hi Michael,
danke für deine Hilfe! Ich habe jetzt mal deinen Code getestet, gleiches Verhalten. Wie bist du in das SendStream rein gekommen? Ich habe hier lediglich ein
Delphi-Quellcode:
procedure SendResponse; virtual; abstract;
bzw ein
Delphi-Quellcode:
procedure SendStream(AStream: TStream); virtual; abstract;
. Leider nur die deklaration und keine Implementierung in der Web.HTTPApp.

Ich habe mir auch in dem WEbmodul einen Pfad definiert, mache es also nicht im BeforeDispatch, aber das sollte ja eigentlich nicht stören oder?
vG
PJM

fisipjm 10. Apr 2024 15:47

AW: Große Datei über WebBroker als 'application/octet-stream' zur Verfügung stellen
 
Hi nochmal,

ich verwendet jetzt die Filestream Lösung, weil wie von Michael schon beschrieben, besseres Speichermanagement.
Das Problem lag am Postman. Ich teste mit diesem Programm die Abfragen und wie die Meldung vom Websocket schon korrekterweise beschreibt:
Delphi-Quellcode:
Socket Fehler 10054 Die Verbindung wurde von Peer zurückgesetzt
wurde die Verbindung vom Peer, also der anfragenden Seite, zurückgesetzt. :|

War ja aber nicht ganz Umsonst, da das Speichermanagement jetzt besser läuft :-)
Vielen Dank!


Alle Zeitangaben in WEZ +1. Es ist jetzt 20:47 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz