0
votes

I am prototyping a datasnap client and server application. I want to stream a file from the server to the client.

This is working, but every file that i stream stays in memory at the server. It is never released, not even when i close the connection / client.

I tried using: GetInvocationMetaData.CloseSession := True; in the method call on the server, but then i don't get a file, it is 0 Kb.

What am I doing wrong?

Serverside code: (excuse me for the naming) Lifecycle is invocation

unit ServerMethodsUnitNKDataSnap;

interface

uses System.SysUtils, System.Classes, Datasnap.DSServer, Datasnap.DSAuth,
  Data.DB, IBCustomDataSet, IBQuery, IBDatabase, Datasnap.Provider, DataSnap.DSSession, Data.DBXPlatform;

type
  TServerMethods1 = class(TDSServerModule)
  private
//    FStream:TStream;
  public
    { Public declarations }
    function EchoString(Value: string): string;
    function ReverseString(Value: string): string;
    function DownloadFile(sFile:String): TStream;
    function BestaatFile(sFile:String):Boolean;
    procedure FreeStream;
  end;

implementation

{$R *.dfm}

uses System.StrUtils;

function TServerMethods1.EchoString(Value: string): string;
begin
  Result := Value;
end;

procedure TServerMethods1.FreeStream;
begin
//  FreeAndNil(FStream);
  GetInvocationMetaData.CloseSession := True;
end;

function TServerMethods1.ReverseString(Value: string): string;
begin
  Result := System.StrUtils.ReverseString(Value);
  GetInvocationMetaData.CloseSession := True;
end;

function TServerMethods1.DownloadFile(sFile:String): TStream;
begin
{
  if Assigned(FStream) then
    FreeAndNil(FStream);
  FStream := TFileStream.Create(sFile, fmOpenRead or fmShareDenyNone);

  Result := FStream;
  TFileStream(Result).Seek(0, TSeekOrigin.soBeginning);
}
  result := TFileStream.Create(sFile, fmOpenRead or fmShareDenyNone);
  TFileStream(Result).Seek(0, TSeekOrigin.soBeginning);
//  GetInvocationMetaData.CloseSession := True;
end;

function TServerMethods1.BestaatFile(sFile: string):Boolean;
begin
  Result := FileExists(sFile);
  GetInvocationMetaData.CloseSession := True;
end;

end.

dfm

object ServerContainer2: TServerContainer2
  OldCreateOrder = False
  Height = 271
  Width = 415
  object DSServer1: TDSServer
    Left = 96
    Top = 11
  end
  object DSHTTPService1: TDSHTTPService
    HttpPort = 7070
    Server = DSServer1
    Filters = <>
    Left = 96
    Top = 135
  end
  object DSServerClass1: TDSServerClass
    OnGetClass = DSServerClass1GetClass
    Server = DSServer1
    LifeCycle = 'Invocation'
    Left = 200
    Top = 11
  end
end

client side code generated source

//
// Created by the DataSnap proxy generator.
// 2-2-2015 14:27:21
//

unit ClientClassesUnit1;

interface

uses Datasnap.DSProxyRest, Datasnap.DSClientRest, Data.DBXCommon, Data.DBXClient, Data.DBXDataSnap, Data.DBXJSON, Datasnap.DSProxy, System.Classes, System.SysUtils, Data.DB, Data.SqlExpr, Data.DBXDBReaders, Data.DBXCDSReaders, Data.DBXJSONReflect;

type
  TServerMethods1Client = class(TDSAdminRestClient)
  private
    FEchoStringCommand: TDSRestCommand;
    FReverseStringCommand: TDSRestCommand;
    FDownloadFileCommand: TDSRestCommand;
    FDownloadFileCommand_Cache: TDSRestCommand;
    FBestaatFileCommand: TDSRestCommand;
    FFreeStreamCommand: TDSRestCommand;
  public
    constructor Create(ARestConnection: TDSRestConnection); overload;
    constructor Create(ARestConnection: TDSRestConnection; AInstanceOwner: Boolean); overload;
    destructor Destroy; override;
    function EchoString(Value: string; const ARequestFilter: string = ''): string;
    function ReverseString(Value: string; const ARequestFilter: string = ''): string;
    function DownloadFile(sFile: string; const ARequestFilter: string = ''): TStream;
    function DownloadFile_Cache(sFile: string; const ARequestFilter: string = ''): IDSRestCachedStream;
    function BestaatFile(sFile: string; const ARequestFilter: string = ''): Boolean;
    procedure FreeStream;
  end;

const
  TServerMethods1_EchoString: array [0..1] of TDSRestParameterMetaData =
  (
    (Name: 'Value'; Direction: 1; DBXType: 26; TypeName: 'string'),
    (Name: ''; Direction: 4; DBXType: 26; TypeName: 'string')
  );

  TServerMethods1_ReverseString: array [0..1] of TDSRestParameterMetaData =
  (
    (Name: 'Value'; Direction: 1; DBXType: 26; TypeName: 'string'),
    (Name: ''; Direction: 4; DBXType: 26; TypeName: 'string')
  );

  TServerMethods1_DownloadFile: array [0..1] of TDSRestParameterMetaData =
  (
    (Name: 'sFile'; Direction: 1; DBXType: 26; TypeName: 'string'),
    (Name: ''; Direction: 4; DBXType: 33; TypeName: 'TStream')
  );

  TServerMethods1_DownloadFile_Cache: array [0..1] of TDSRestParameterMetaData =
  (
    (Name: 'sFile'; Direction: 1; DBXType: 26; TypeName: 'string'),
    (Name: ''; Direction: 4; DBXType: 26; TypeName: 'String')
  );

  TServerMethods1_BestaatFile: array [0..1] of TDSRestParameterMetaData =
  (
    (Name: 'sFile'; Direction: 1; DBXType: 26; TypeName: 'string'),
    (Name: ''; Direction: 4; DBXType: 4; TypeName: 'Boolean')
  );

implementation

function TServerMethods1Client.EchoString(Value: string; const ARequestFilter: string): string;
begin
  if FEchoStringCommand = nil then
  begin
    FEchoStringCommand := FConnection.CreateCommand;
    FEchoStringCommand.RequestType := 'GET';
    FEchoStringCommand.Text := 'TServerMethods1.EchoString';
    FEchoStringCommand.Prepare(TServerMethods1_EchoString);
  end;
  FEchoStringCommand.Parameters[0].Value.SetWideString(Value);
  FEchoStringCommand.Execute(ARequestFilter);
  Result := FEchoStringCommand.Parameters[1].Value.GetWideString;
end;

function TServerMethods1Client.ReverseString(Value: string; const ARequestFilter: string): string;
begin
  if FReverseStringCommand = nil then
  begin
    FReverseStringCommand := FConnection.CreateCommand;
    FReverseStringCommand.RequestType := 'GET';
    FReverseStringCommand.Text := 'TServerMethods1.ReverseString';
    FReverseStringCommand.Prepare(TServerMethods1_ReverseString);
  end;
  FReverseStringCommand.Parameters[0].Value.SetWideString(Value);
  FReverseStringCommand.Execute(ARequestFilter);
  Result := FReverseStringCommand.Parameters[1].Value.GetWideString;
end;

function TServerMethods1Client.DownloadFile(sFile: string; const ARequestFilter: string): TStream;
begin
  if FDownloadFileCommand = nil then
  begin
    FDownloadFileCommand := FConnection.CreateCommand;
    FDownloadFileCommand.RequestType := 'GET';
    FDownloadFileCommand.Text := 'TServerMethods1.DownloadFile';
    FDownloadFileCommand.Prepare(TServerMethods1_DownloadFile);
  end;
  FDownloadFileCommand.Parameters[0].Value.SetWideString(sFile);
  FDownloadFileCommand.Execute(ARequestFilter);
  Result := FDownloadFileCommand.Parameters[1].Value.GetStream(FInstanceOwner);
end;

function TServerMethods1Client.DownloadFile_Cache(sFile: string; const ARequestFilter: string): IDSRestCachedStream;
begin
  if FDownloadFileCommand_Cache = nil then
  begin
    FDownloadFileCommand_Cache := FConnection.CreateCommand;
    FDownloadFileCommand_Cache.RequestType := 'GET';
    FDownloadFileCommand_Cache.Text := 'TServerMethods1.DownloadFile';
    FDownloadFileCommand_Cache.Prepare(TServerMethods1_DownloadFile_Cache);
  end;
  FDownloadFileCommand_Cache.Parameters[0].Value.SetWideString(sFile);
  FDownloadFileCommand_Cache.ExecuteCache(ARequestFilter);
  Result := TDSRestCachedStream.Create(FDownloadFileCommand_Cache.Parameters[1].Value.GetString);
end;

function TServerMethods1Client.BestaatFile(sFile: string; const ARequestFilter: string): Boolean;
begin
  if FBestaatFileCommand = nil then
  begin
    FBestaatFileCommand := FConnection.CreateCommand;
    FBestaatFileCommand.RequestType := 'GET';
    FBestaatFileCommand.Text := 'TServerMethods1.BestaatFile';
    FBestaatFileCommand.Prepare(TServerMethods1_BestaatFile);
  end;
  FBestaatFileCommand.Parameters[0].Value.SetWideString(sFile);
  FBestaatFileCommand.Execute(ARequestFilter);
  Result := FBestaatFileCommand.Parameters[1].Value.GetBoolean;
end;

procedure TServerMethods1Client.FreeStream;
begin
  if FFreeStreamCommand = nil then
  begin
    FFreeStreamCommand := FConnection.CreateCommand;
    FFreeStreamCommand.RequestType := 'GET';
    FFreeStreamCommand.Text := 'TServerMethods1.FreeStream';
  end;
  FFreeStreamCommand.Execute;
end;

constructor TServerMethods1Client.Create(ARestConnection: TDSRestConnection);
begin
  inherited Create(ARestConnection);
end;

constructor TServerMethods1Client.Create(ARestConnection: TDSRestConnection; AInstanceOwner: Boolean);
begin
  inherited Create(ARestConnection, AInstanceOwner);
end;

destructor TServerMethods1Client.Destroy;
begin
  FEchoStringCommand.DisposeOf;
  FReverseStringCommand.DisposeOf;
  FDownloadFileCommand.DisposeOf;
  FDownloadFileCommand_Cache.DisposeOf;
  FBestaatFileCommand.DisposeOf;
  FFreeStreamCommand.DisposeOf;
  inherited;
end;

end.

Own source

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls,
  Vcl.Grids, Vcl.DBGrids, IPPeerClient, Datasnap.DSClientRest;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Edit1: TEdit;
    Label4: TLabel;
    Button5: TButton;
    Edit5: TEdit;
    Label5: TLabel;
    Edit6: TEdit;
    Label6: TLabel;
    DSRestConnection1: TDSRestConnection;
    procedure Button1Click(Sender: TObject);
    procedure Button5Click(Sender: TObject);
  private
    procedure DownloadFile(sSourceFile,sTargetFile:string);
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

uses ClientClassesUnit1;

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var oServerMethodsClient:TServerMethods1Client;
begin
  oServerMethodsClient := nil;
  try
    try
      oServerMethodsClient := TServerMethods1Client.Create(DSRestConnection1, True);
      ShowMessage(oServerMethodsClient.EchoString(edit1.Text));
      ShowMessage(oServerMethodsClient.ReverseString(edit1.Text));
    except
      on E: Exception do
      begin
        showmessage('Oeps echo: ' + E.Message);
      end;
    end;
  finally
    FreeAndNil(oServerMethodsClient);
  end;
end;

procedure TForm1.Button5Click(Sender: TObject);
begin
  DownloadFile(edit5.Text, edit6.Text);
end;

procedure TForm1.DownloadFile(sSourceFile,sTargetFile:String);
var
    RetStream: TStream;
    fs:TFileStream;
    oServerMethodsClient:TServerMethods1Client;
begin
  oServerMethodsClient := nil;
  RetStream := nil;

  try
    try
      oServerMethodsClient := TServerMethods1Client.Create(DSRestConnection1, True);
      RetStream := oServerMethodsClient.DownloadFile(sSourceFile);
//      oServerMethodsClient.FreeStream;
      fs := TFileStream.Create(sTargetFile, fmCreate);
      try
        if RetStream.Size > 0 then
        begin
          RetStream.Position:=0;
          fs.CopyFrom(RetStream, RetStream.Size);
        end;
      finally
         fs.free;
//         retstream.Free;
      end;

    except
      on E: Exception do
      begin
        showmessage('Oeps download: ' + E.Message);
      end;
    end;

  finally
    FreeAndNil(oServerMethodsClient);
  end;
end;

end.

dfm source

object Form1: TForm1
  Left = 0
  Top = 0
  Caption = 'Form1'
  ClientHeight = 519
  ClientWidth = 959
  Color = clBtnFace
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'Tahoma'
  Font.Style = []
  OldCreateOrder = False
  PixelsPerInch = 96
  TextHeight = 13
  object Label4: TLabel
    Left = 208
    Top = 48
    Width = 53
    Height = 13
    Caption = 'Echo string'
  end
  object Label5: TLabel
    Left = 208
    Top = 93
    Width = 22
    Height = 13
    Caption = 'van:'
  end
  object Label6: TLabel
    Left = 208
    Top = 123
    Width = 22
    Height = 13
    Caption = 'naar'
  end
  object Button1: TButton
    Left = 16
    Top = 43
    Width = 75
    Height = 25
    Caption = 'echo'
    TabOrder = 0
    OnClick = Button1Click
  end
  object Edit1: TEdit
    Left = 288
    Top = 45
    Width = 274
    Height = 21
    TabOrder = 1
    Text = 'bla'
  end
  object Button5: TButton
    Left = 16
    Top = 88
    Width = 75
    Height = 25
    Caption = 'haal 1 file'
    TabOrder = 2
    OnClick = Button5Click
  end
  object Edit5: TEdit
    Left = 288
    Top = 93
    Width = 274
    Height = 21
    TabOrder = 3
    Text = 'c:\source\temp.zip'
  end
  object Edit6: TEdit
    Left = 288
    Top = 120
    Width = 274
    Height = 21
    TabOrder = 4
    Text = 'c:\temp\temp.zip'
  end
  object DSRestConnection1: TDSRestConnection
    Host = 'localhost'
    Port = 7070
    LoginPrompt = False
    PreserveSessionID = False
    Left = 120
    Top = 16
    UniqueId = '{B4909821-9EF2-42F1-AFFF-58B320C35F75}'
  end
end
1
You don't free the TFileStream you create in the DownloadFile method. There's some code in comments that tries to deal with that but as it is in comments, it's never executed. (I haven't verified it to be correct either) - Lieven Keersmaekers
The resultstream is owned by the oServerMethodsClient proxy, so does not need to be free'd. If i do that, i get an invalid pointer operation. - marenk vos

1 Answers

2
votes

TStream is not properly supported for DataSnap server methods. The list of supported types can be found in the docwiki:

Exposing DataSnap Server Methods

In your case using TDBXStreamValue seems appropriate.