delphidatasnapfiredacdelphi-10.1-berlin

Can't retrieve TStreams bigger than around 260.000 bytes from a Datasnap Server


I have a Delphi 10.1 Berlin Datasnap Server, that can't return Data packets (through a TStream) bigger than around 260.000 bytes.

I have programmed it following the \Object Pascal\DataSnap\FireDAC sample from Delphi, which also shows this problem.

The problem can be seen just opening that sample, setting blank the IndexFieldName of the qOrders component on ServerMethodsUnit.pas, and changing its SQL property to :

select * from Orders
union 
select * from Orders

Now the amount of data to be send is beyond 260.000 bytes, which seems to be the point where you can't retrieve it from the client. Getting a EFDException [FireDAC][Stan]-710. Invalid binary storage format.

The data is sent as a Stream that you get from a FDSchemaAdapter on the server, and you load on another FDSchemaAdpater on the client. The connection between Client and Server is also FireDAC.

This is how the Server returns that Stream :

function TServerMethods.StreamGet: TStream;
begin
  Result := TMemoryStream.Create;
  try
    qCustomers.Close;
    qCustomers.Open;
    qOrders.Close;
    qOrders.Open;
    FDSchemaAdapter.SaveToStream(Result, TFDStorageFormat.sfBinary);
    Result.Position := 0;
  except
    raise;
  end;
end;

And this is how the Client retrieves it :

procedure TClientForm.GetTables;
var
  LStringStream: TStringStream;
begin
  FDStoredProcGet.ExecProc;
  LStringStream := TStringStream.Create(FDStoredProcGet.Params[0].asBlob);
  try
    if LStringStream <> nil then
    begin
      LStringStream.Position := 0;
      DataModuleFDClient.FDSchemaAdapter.LoadFromStream(LStringStream, TFDStorageFormat.sfBinary);
    end;
  finally
    LStringStream.Free;
  end;
end;

The Client doesn't get all the data on the Blob parameter. I save the content of the Stream on the Server, and the content that arrives at the Blob parameter on the Client, and they have the same size, but the content of the Blob parameter has its content truncated, and the last few Kbytes are zeroes.

This is how I save on the Server the content that will go to the Stream:

FDSchemaAdapter.SaveToFile('C:\Temp\JSON_Server.json', TFDStorageFormat.sfJSON);

This is how I check what I get on the Client blob parameter:

TFile.WriteAllText('C:\Temp\JSON_Client.json', FDStoredProcGet.Params[0].asBlob);

I can see that the Client gets the data truncated.

Do you know how to fix it, or a workaround to retrieve all the Stream content from the Datasnap Server to my Client ?.

Update: I have updated to Delphi 10.1 Berlin Update 2, but the problem remains.

Thank you.


Solution

  • I have coded a workaround. Seeing that I can't pass data bigger than 255Kb then I split it in different 255Kb packets and send them separately (I have also added compression to minimize the bandwidth and roundtrips).

    On the server I have changed StremGet to two different calls : StreamGet and StreamGetNextPacket.

    function TServerMethods.StreamGet(var Complete: boolean): TStream;
    var Data: TMemoryStream;
        Compression: TZCompressionStream;
    begin
      try
        // Opening Data
        qCustomers.Close;
        qCustomers.Open;
        qOrders.Close;
        qOrders.Open;
    
        // Compressing Data
        try
          if Assigned(CommStream) then FreeAndNil(CommStream);
          CommStream := TMemoryStream.Create;
          Data := TMemoryStream.Create;
          Compression := TZCompressionStream.Create(CommStream);
          FDSchemaAdapter.SaveToStream(Data, TFDStorageFormat.sfBinary);
          Data.Position := 0;
          Compression.CopyFrom(Data, Data.Size);
        finally
          Data.Free;
          Compression.Free;
        end;
    
        // Returning First 260000 bytes Packet
        CommStream.Position := 0;
        Result := TMemoryStream.Create;
        Result.CopyFrom(CommStream, Min(CommStream.Size, 260000));
        Result.Position := 0;
    
        // Freeing Memory if all sent
        Complete := (CommStream.Position = CommStream.Size);
        if Complete then FreeAndNil(CommStream);
      except
        raise;
      end;
    end;
    
    function TServerMethods.StreamGetNextPacket(var Complete: boolean): TStream;
    begin
      // Returning the rest of 260000 bytes Packets
      Result := TMemoryStream.Create;
      Result.CopyFrom(CommStream, Min(CommStream.Size - CommStream.Position, 260000));
      Result.Position := 0;
    
      // Freeing Memory if all sent
      Complete := (CommStream.Position = CommStream.Size);
      if Complete then FreeAndNil(CommStream);
    end;
    

    CommStream: TStream is declared as private on TServerMethods.

    And the Client retrieves it this way :

    procedure TClientForm.GetTables;
    var Complete: boolean;
        Input: TStringStream;
        Data: TMemoryStream;
        Decompression:  TZDecompressionStream;
    begin
      Input := nil;
      Data := nil;
      Decompression := nil;
    
      try
        // Get the First 260000 bytes Packet
        spStreamGet.ExecProc;
        Input := TStringStream.Create(spStreamGet.ParamByName('ReturnValue').AsBlob);
        Complete := spStreamGet.ParamByName('Complete').AsBoolean;
    
        // Get the rest of 260000 bytes Packets
        while not Complete do begin
          spStreamGetNextPacket.ExecProc;
          Input.Position := Input.Size;
          Input.WriteBuffer(TBytes(spStreamGetNextPacket.ParamByName('ReturnValue').AsBlob), Length(spStreamGetNextPacket.ParamByName('ReturnValue').AsBlob));
          Complete := spStreamGetNextPacket.ParamByName('Complete').AsBoolean;
        end;
    
        // Decompress Data
        Input.Position := 0;
        Data := TMemoryStream.Create;
        Decompression := TZDecompressionStream.Create(Input);
        Data.CopyFrom(Decompression, 0);
        Data.Position := 0;
    
        // Load Datasets
        DataModuleFDClient.FDSchemaAdapter.LoadFromStream(Data, TFDStorageFormat.sfBinary);
      finally
        if Assigned(Input) then FreeAndNil(Input);
        if Assigned(Data) then FreeAndNil(Data);
        if Assigned(Decompression) then FreeAndNil(Decompression);
      end;
    end;
    

    It works fine now.