DataSnap ServerMethod函数返回为ftStream参数被错误地截断

时间:2017-02-22 10:47:52

标签: delphi

正如DataSnap用户所知,其ServerMethods会向其呼叫者返回值 作为DataSnap参数。

有关SO和其他地方的一些关于问题的报告 返回ServerMethod的DataSnap服务器结果为ftStream参数,表示流被截断 过早或空虚。这里有一个例子: Can't retrieve TStreams bigger than around 260.000 bytes from a Datasnap Server

我已经汇总了一个可重复的测试用例,我打算将其提交给 Emba的质量门户网站作为MCVE,但在此之前,我想要一些帮助固定下来 出现问题的地方。我在Win64上使用Delphi Seattle,编译为32位,顺便说一句。

我的MCVE是完全独立的(即包括服务器和客户端)并且确实如此 不依赖于任何数据库数据。它的ServerMethods模块包含一个函数 (下面代码中的BuildString)返回一个调用者指定长度的字符串 和两个返回结果的ServerMethod GetAsStringGetAsStream 作为类型ftString和ftStream的参数。

GetString方法成功返回任意请求长度的字符串 我测试的最大值是32000000(3200万)字节。

Otoh,GetStream方法可以达到30716的请求大小;在上面, 返回的流的大小为-1,为空。当然是预期的行为 它应该能够使用更大的尺寸,就像GetString那样。

在出站(服务器)端,在某个时刻将返回的流传入 DataSnap的JSon层在前往tcp / ip传输层和入站端的路径上,类似地,流被检索 来自JSon层。我希望能做什么,以及这个q是什么, 是捕获AsStream的出站和入站JSon表示 人类易读形式的参数值,以便我识别是否不需要 截断其数据发生在服务器或客户端。我该怎么做?

我之所以这么说是因为尽管看了几个小时我还是无法准确辨认出来 JSon转换发生的位置。这就像在大海捞针中寻找一根针。 如果你看一下Data.DBXStream中的方法TDBXJSonStreamWriter.WriteParameter, 它不会写的一件事是流的内容!

我能够建立的一件事是关于Data.DBXStream中的第4809行

Size := ((FBuf[IncrAfter(FOff)] and 255) shl 8) or (FBuf[IncrAfter(FOff)] and 255)

在函数TDBXRowBuffer.ReadReaderBlobSize中。进入 这个方法,Size初始化为零,这条线将Size设置为30716 对于所有请求的流大小> =该值。但我不知道这是因果关系, 即是否已经进行了流分离,或者它是否属于这条线 导致它。

我的代码如下;为它的长度道歉,但DataSnap项目需要 在最好的时候,相当多的包袱,我已经包含了一些代码 初始化一些组件以避免必须发布.DFM。

ServerMethods代码:

unit ServerMethods2u;
interface
uses System.SysUtils, System.Classes, System.Json, variants, Windows,
    Datasnap.DSServer, Datasnap.DSAuth, DataSnap.DSProviderDataModuleAdapter;

{$MethodInfo on}
type
  TServerMethods1 = class(TDSServerModule)
  public
    function GetStream(Len: Integer): TStream;
    function GetString(Len: Integer): String;
  end;
{$MethodInfo off}

implementation

{$R *.dfm}

uses System.StrUtils;

function BuildString(Len : Integer) : String;
var
  S : String;
  Count,
  LeftToWrite : Integer;
const
  scBlock = '%8d bytes'#13#10;
begin
  LeftToWrite := Len;
  Count := 1;
  while Count <= Len do begin
    S := Format(scBlock, [Count]);
    if LeftToWrite >= Length(S) then
    else
      S := Copy(S, 1, LeftToWrite);
    Result := Result + S;
    Inc(Count, Length(S));
    Dec(LeftToWrite, Length(S));
  end;
  if Length(Result) > 0 then
    Result[Length(Result)] := '.'
end;

function TServerMethods1.GetStream(Len : Integer): TStream;
var
  SS : TStringStream;
begin
  SS := TStringStream.Create;
  SS.WriteString(BuildString(Len));
  SS.Position := 0;
  Result := SS;
end;

function TServerMethods1.GetString(Len : Integer): String;
begin
  Result := BuildString(Len);
end;

ServerContainer代码:

unit ServerContainer2u;
interface
uses System.SysUtils, System.Classes, Datasnap.DSTCPServerTransport,
  Datasnap.DSServer, Datasnap.DSCommonServer, Datasnap.DSAuth, IPPeerServer,
  DataSnap.DSProviderDataModuleAdapter;

type
  TServerContainer1 = class(TDataModule)
    DSServer1: TDSServer;
    DSTCPServerTransport1: TDSTCPServerTransport;
    DSServerClass1: TDSServerClass;
    procedure DataModuleCreate(Sender: TObject);
    procedure DSServerClass1GetClass(DSServerClass: TDSServerClass;
      var PersistentClass: TPersistentClass);
  end;

var
  ServerContainer1: TServerContainer1;

implementation

{$R *.dfm}

uses ServerMethods2u;

procedure TServerContainer1.DataModuleCreate(Sender: TObject);
begin
  DSServerClass1.Server := DSServer1;
  DSTCPServerTransport1.Server := DSServer1;
end;

procedure TServerContainer1.DSServerClass1GetClass(
  DSServerClass: TDSServerClass; var PersistentClass: TPersistentClass);
begin
  PersistentClass := TServerMethods1;
end;

端。

ServerForm代码:

unit ServerForm2u;
interface
uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
  System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs,
  Vcl.StdCtrls, DBXJSON, Data.DBXDataSnap, IPPeerClient,
  Data.DBXCommon, Data.FMTBcd, Data.DB, Data.SqlExpr, Data.DbxHTTPLayer,
  DataSnap.DSServer;

type
  TForm1 = class(TForm)
    btnGetStream: TButton;
    edStreamSize: TEdit;
    SQLConnection1: TSQLConnection;
    SMGetStream: TSqlServerMethod;
    Memo1: TMemo;
    Label1: TLabel;
    btnGetString: TButton;
    Label2: TLabel;
    edStringSize: TEdit;
    SMGetString: TSqlServerMethod;
    procedure FormCreate(Sender: TObject);
    procedure btnGetStreamClick(Sender: TObject);
    procedure btnGetStringClick(Sender: TObject);
  private
  public
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin

  SqlConnection1.ConnectionData.Properties.Values['CommunicationProtocol'] := 'tcp/ip';
  SqlConnection1.ConnectionData.Properties.Values['BufferKBSize'] := '64';

  SMGetStream.Params.Clear;
  SMGetStream.Params.CreateParam(ftInteger, 'Len', ptInput);
  SMGetStream.Params.CreateParam(ftStream, 'Result', ptOutput);

  SMGetString.Params.Clear;
  SMGetString.Params.CreateParam(ftInteger, 'Len', ptInput);
  SMGetString.Params.CreateParam(ftString, 'Result', ptOutput);

end;

procedure TForm1.btnGetStreamClick(Sender: TObject);
var
  SS : TStringStream;
  S : TStream;
begin
  Memo1.Lines.Clear;
  SS := TStringStream.Create;
  try
    SMGetStream.Params[0].AsInteger := StrtoInt(edStreamSize.Text);
    SMGetStream.ExecuteMethod;
    S := SMGetStream.Params[1].AsStream;
    S.Position := 0;
    if S.Size > 0 then begin
      try
        SS.CopyFrom(S, S.Size);
        Memo1.Lines.BeginUpdate;
        Memo1.Lines.Text := SS.DataString;
        Memo1.Lines.Insert(0, IntToStr(S.Size));
      finally
        Memo1.Lines.EndUpdate;
      end;
    end
    else
      ShowMessage(IntToStr(S.Size));
  finally
    SS.Free;
  end;
end;

procedure TForm1.btnGetStringClick(Sender: TObject);
var
  S : String;
  Size : Integer;
begin
  Memo1.Lines.Clear;
  Size :=  StrtoInt(edStringSize.Text);
  SMGetString.Params[0].AsInteger := Size;
  SMGetString.ExecuteMethod;
  S := SMGetString.Params[1].AsString;
  if Length(S) > 0 then begin
    try
      Memo1.Lines.BeginUpdate;
      Memo1.Lines.Text := S;
      Memo1.Lines.Insert(0, IntToStr(Length(S)));
    finally
      Memo1.Lines.EndUpdate;
    end;
  end;
end;

end.

0 个答案:

没有答案