正如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 GetAsString
和GetAsStream
作为类型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.