DataSnap Limits Inbound Request to 16k - delphi

I built a DataSnap server with Delphi XE2 that implements TDSHTTPService. When the inbound request comes in, TIdIOHandler.InitComponent is called in a thread before execution is handed to the method called in TServerMethods. I do not have any Indy components in the server, so DataSnap is using Indy 10 under-the-hood.
.InitComponent() sets the IO handler's max line length to a hard-coded value (FMaxLineLength := IdMaxLineLengthDefault;), which is 16384. I can't find a way to increase the value. I even tried copying the IdIOHandler Unit to the project folder and changing the constant value. But it still picks up the IdIOHandler.dcu from the Indy 10 build, and ignores the copied file in my project folder. I also tried adding a TIdIOHandlerStream component to the server project and setting its MaxLineLength to no avail.
Plan A = Properly set the MaxLineLength value in the DataSnap server.
Plan B = Somehow compile a modified IdIOHandler.pas file into my project.
Are either of these possible? I've been working on this for hours and can't find anything similar in all my searching, and can't seem to make any headway by experimenting.

After recompiling all Indy packages in Delphi XE3, having changed the IdMaxLineLengthDefault constant to 512 * 1024, and working as expected after that, I began searching for the simplest solution to this problem. So, I found out this an easy workaround for this limit.
You can implement a procedure for the OnContextCreated event of the TIdHTTPWebBrokerBridge object used in the main unit of the DataSnap REST Server project. In that event, the AContext object is received, which is created for each request to the DataSnap server. So, in the code for this procedure you just have to override the default value for this property as follows:
procedure TForm1.FormCreate(Sender: TObject);
begin
FServer := TIdHTTPWebBrokerBridge.Create(Self);
{Here you assign the new procedure for this event}
FServer.OnContextCreated:= OnContextCreated;
end;
procedure TForm1.OnContextCreated(AContext: TIdContext);
begin
AContext.Connection.IOHandler.MaxLineLength:= 512*1024 {or whatever value you need);
end;

Short of removing the Delphi XE2 install of Indy 10 and downloading the source, tweaking the constant values and compiling / maintaining my own build forever going forward..., I solve the issue.
I created an additional method in the DataSnap server so that I could create a record in the database with a call to the first method, and then incrementally stream the rest of the data by passing it to the second method 16k at a time -- buffering it in the DataSnap server until all parts are received. Then I update the record in the database with the fully buffered value from the DataSnap server.
Maybe not the most effective solution, but it works and it will scale as needed.

Related

IBStoredProc does not commit insert if returns data?

I have a stored procedure which insert/update, and then returns result.
create or alter procedure sp_update_system_sticker (
i_sticker_id integer,
i_file_name file_name_type,
i_sticker_name item_name_type,
i_group_id integer)
returns (
o_result integer)
as
begin
update
system_stickers s
set
s.file_name = :i_file_name,
s.name = :i_sticker_name,
s.fk_stickers_groups = :i_group_id
where
s.id = :i_sticker_id;
o_result = -1;
suspend;
end
I am setting it in Delphi in a IBStoredProc, and execute it as follow:
procedure TDataModule_.updateSystemSticker(stickerId, groupId: integer;
stickerName, fileName: String);
var
r : Integer;
begin
with IBStoredProc_UpdateSystemSticker do
begin
Transaction.Active := true;
ParamByName( 'I_STICKER_ID' ).AsInteger := stickerId;
ParamByName( 'I_GROUP_ID' ).AsInteger := groupID;
ParamByName( 'I_STICKER_NAME' ).AsString := stickerName;
ParamByName( 'I_FILE_NAME' ).AsString := fileName;
ExecProc;
Transaction.Commit;
end;
end;
Anyway it does not commit the result into the database.
If I remove the returns - it start to commit.
How to execute and commit properly stored procedure with IBStoreProc which returns results ?
The problem is the presence of SUSPEND. This makes your stored procedure a selectable procedure, and not an executable procedure. When you use a selectable procedure, then all work done since the previous fetched row will be undone when the cursor is closed (which happens on commit). If you fetched nothing, this means that it is as if no work was performed by the stored procedure*.
In other words, you need to remove the SUSPEND (an executable stored procedure outputs a single row immediately on execute without having to wait for a fetch).
I don't program Delphi, so I can't comment on the specifics of getting results in Delphi.
*: Recent versions of Firebird can prefetch rows, so this might not be entirely accurate
Since you made it a "selectable stored procedure" by adding SUSPEND PSQL statement - just do a select from it.
Use regular TIBQuery instead of TIBStoredProc with a command like
select * from StoredProcedureName( :input_param1, :input_param2, :input_param3 )
I would not recommended to use IBExpress to directly call stored procedures in Firebird. Interbase turned up to have a bug in stored procedures execution, AFAIR something wrong with errors handling. To counter it IBX team added an intentional bug of executing SPs twice (under some conditions), which usually (not always) neutralized the Interbase bug. When Firebird team fixed the server bug - this IBX counter-bug started breaking data. IBX team refused to revert to normal behavior for Firebird databases as Firebird was considered competitor to Interbase.
Equally, IBX would not support Firebird-specific changes made after Interbase 6.x/Firebird 0.9 split. For example:
client DLL name change to avoid collision: it became fbclient.dll or fbembed.dll from gds32.dll, however IBX only supports the legacy name. It is hardcoded and can not be changed. If you have IBX sources you may patch it and recompile the library - but why bother?
Firebird's new datatypes like 64-bit integer and boolean. Again, if you have IBX sources...
Firebird's new APIs are not supported.
That said, there is an IBX add-on library by Dmitry Loginov, IBX FB Utils, and it has a number of rather comfy wrappers, on top of IBX. It alone might be a good pro-IBX argument.
FPC (FreePascal) folks started IBX fork they named IBX2, which would hopefully have first-class support for Firebird. I do not know about quality and development speed of it, but i suspect it might appear an easiest migration route out of IBX, or not.
Personally for Firebird-centric Delphi projects i prefer opensource UIB (Unified Interbase) library. However
Being "lean thin API wrapper" it is not TDataSet derived, albeit having a read-only TDataSet wrapper and trying to keep API closely resembling one of TDataSet.
being Henri's Delphi project it has little documentation (tests and examples mostly) and is abandoned by the author (albeit some other guy was adding patches later)
it has neat features like SQL scripter component (but you might need to extend it to support all Firebird new SQL commands, at least i did it to support FB2's MERGE) and for RECORD in SQLQuery do... loop (albeit you can extract it and make it into a separate add-on over any your DB library of choice)

when packing a dbf table, an error is file is in use

When trying to make a request, it displays an error:
File is in use
How can I solve that program?
procedure TForm1.Button4Click(Sender: TObject);
var data,ffg:string;
begin
data:=formatdatetime('ddmm',(DateTimePicker1.Date));
Adoquery2.SQL.Clear;
adoquery2.SQL.text:='Delete from g_rabn where data=data';// deleting data from g_rabn
adoquery2.ExecSQL;
ShowMessage(SysErrorMessage(GetLastError));
end;
procedure TForm1.Button5Click(Sender: TObject);
begin
Adoquery3.close;
Adoquery3.SQL.Clear;
adoquery3.SQl.text:='pack table g_rabn';// packing tablr g_rabn
adoquery3.Open;
ShowMessage(SysErrorMessage(GetLastError));
end;
end.
I can not delete data from the table, they are marked as deleted but require packaging. How to do it programmatically? He writes that file is in use when packing what to do?
You should execute the statement, not open it as a query. One way to achieve that, is to run it using an TADOCommand, not an TADOQuery, or use the ExecSQL method of the TADOQuery.
Also, all other connections to the DBF must be closed, otherwise you can't get the exclusive access that you need for packing the table.
I found this thread from 2005 on another forum, where somebody made this work with two notable parameters:
Using the provider VFPOLEDB.1
Using just the command pack filename.dbf (without the table keyword).
Lastly, I'm not so sure about the line ShowMessage(SysErrorMessage(GetLastError));. This will show you the last API error, but that's on a low level. You are using the ADO components, so if anything is going wrong, you should expect ADO to throw an exception. For all you know ADO already worked around the issue one way or the other, and the error message you're seeing is not even relevant.
I am surprised that I could not get the solution suggested in Golez Troi's answer
to work, especially as it refers to a newsgroup post from someone who had seemingly managed to pack a dBASE table using ADO; as I said in a comment, if I try to call 'Pack xxxx' to pack a dBASE table via ADO, however I do it, I get
Invalid SQL Statement; DELETE, INSERT, PROCEDURE, SELECT or UPDATE expected
.
I was also surprised to notice something in the MS ODBC dBASE docs that I'd not noticed before, namely that the MS ODBC driver for dBASE files requires the BDE
Note
Accessing dBASE ISAM files through the ODBC Desktop Database Drivers requires installation of the Borland database engine
So, seeing as accessing dBASE files via Ado requires the BDE anyway, there seems to me to be
no point avoiding using the BDE to pack the dBASE table using the standard BDE method, namely to call DbiPackTable. I added a TDatabase and TTable
to my ADO test project, after which I was able to execute this code without any problem
procedure TForm1.Button2Click(Sender: TObject);
begin
try
// Insert code here to close any Ado object (TAdoConnection, TAdoCommand, etc) pointing
// at the dBASE table/file
// Also check that not Ado object pointing at it is open in the IDE
//
// Then ...
Database1.DatabaseName := 'MADBF2';
Database1.Connected := True;
Table1.TableName := 'MATest.Dbf';
Table1.Exclusive := True;
Table1.Open;
// Following uses a call to DbiPackTable to pack the target table
Check(DbiPackTable(Table1.DBHandle, Table1.Handle, nil, nil,True));
finally
Table1.Close;
Database1.Connected := False;
end;
end;
FWIW, while I was writing this answer, I noticed that the BDE.Int file (which gives the declarations but not the implementation of the BDE interface) was on the D7 distribution CD but was apparently not installed by default).

Indy FTP Failing to upload miserably

Using a simple code, such as:
procedure TForm1.cxButton1Click(Sender: TObject);
begin
ftp.Host := 'domain';
ftp.Username := 'user';
ftp.Password := 'password';
ftp.Connect;
ftp.Put('C:\_Projects\testpicture.JPG');
ftp.Quit;
ftp.Disconnect;
end;
I'm getting the following results:
Application freezes while uploading (ergo unable to see Progress Bar position).
Uploaded file goes corrupted (corrupts anything more than a few bytes).
What on earth am I doing wrong?
Thank you.
The app freezes because Indy uses blocking operations. While the code is running, the main message loop is not running, so new messages are not being processed until cxButton1Click() exits. To solve that, either place a TIdAntiFreeze component onto your TForm, or else move the TIdFTP code to a separate worker thread, and then use TIdSync or TIdNotify to update the UI safely when needed.
The file will be "corrupted" if you are transferring it in ASCII mode instead of in binary mode. Make sure the TIdFTP.TransferType property is set to ftBinary. Indy 9 and earlier defaulted to ftBinary, but Indy 10 defaults to ftASCII instead (to match the FTP protocol specs).

ADO error 'Operation cancelled' in Delphi

I have the following code for executing a sql stored procedure that returns multiple resultsets, then reads this result from stream. For background info: it returns xml blocks as strings and then transforms it into complete xml.
It has worked well over a year but now i have a case that results in error message: Operation cancelled. Debugger shows: Project x raised exception class EOleException with message Operation cancelled.
I have no idea whats causing it. Any help or suggestions would be great.
const
adExecuteStream = $00000400; //Indicates that the results of a command execution should be returned as a stream.
var
objCmd, InputStream, XML, XSLT, Template, Processor, objConn, strmResults : Variant;
ATStreamClass : TMemoryStream;
Adapt : TStreamAdapter;
OutputStream: IStream;
objCmd := CreateOLEObject('ADODB.Command');
objCmd.ActiveConnection := dmABaasMock.dbRaAndm.ConnectionObject;
objCmd.CommandType := adCmdText;
objCmd.CommandText := <sp proc name with params>;
strmResults := CreateOLEObject('ADODB.Stream');
strmResults.Open;
objCmd.Properties['Output Stream'] := strmResults;
objCmd.Execute(EmptyParam, EmptyParam, adExecuteStream); // HERE COMES THE EXCEPTION
strmResults.Position := 0;
xmlMemo.text := strmResults.ReadText;
Difficult to guess what is going wrong without seeing more.
Three things you can do to get more debugging information:
What has changed between when it was working and now? OS version (or ADO), DB, stored proc,...
Is the stored proc working when launched directly in the SQL environment?
Could you rewrite your code to use the regular ADO components instead of doing late binding and get the results in DataSets instead of an ADODB.Stream OleObject. Only having Variant objects doesn't help much if you need to debug and drill down in the code? You can't debug a black box...
I recently had some strange error message when connecting to ADO, and not having the connection string right.
I'm not 100% sure it was the same error message (sorry, I forgot to take a screenshot back then), but if it is, then this might help:
In .NET, when you connect to ADO and use integrated security, you can specify Integrated Security="True", but using the native providers (not only in Delphi, but from any native environment), you will have to specify Integrated Security="SSPI".
I got into the situation because I was fiddling with connection strings (to connect from Delphi native win32 to a server that I previously connected to from .NET) and forgot to copy just the relevant parts.
--jeroen

Synapse and string problems with HTTPSend in Delphi 2010

I have been trying to get to the bottom of this problem off and on for the past 2 days and I'm really stuck. Hopefully some smart folks can help me out.
The issue is that I have a function that I call in a thread that downloads a file (using Synapse libraries) from a website that is passed to it. However, I've found that every once in a while there are sites where it will not pull down a file, but wget or Firefox/IE will download it without issue.
Digging into it, I've found some curious things. Here is the relevant code:
uses
//[..]
HTTPSend,
blcksock;
//[..]
type
TMyThread = class(TThread)
protected
procedure Execute; override;
private
{ Private declarations }
fTheUrl: string;
procedure GetFile(const TheUrl: string);
public
property thrd_TheUrl: string read fTheUrl write fTheUrl;
end;
implementation
[..]
procedure TMyThread.GetFile(const TheUrl: string);
var
HTTP: THTTPSend;
success: boolean;
sLocalUrl: string;
IsSame : boolean;
begin
HTTP := THTTPSend.Create;
try
HTTP.UserAgent :=
'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 2.0.50727)';
HTTP.ProxyHost := 'MYPROXY.COM';
HTTP.ProxyPort := '80';
sLocalUrl :=
'http://web.archive.org/web/20071212205017/energizer.com/usbcharger/download/UsbCharger_setup_V1_1_1.exe';
IsSame := SameText(sLocalUrl, sTheUrl); //this equals True when I debug
///
///
/// THIS IS WHERE THE ISSUE BEGINS
/// I will comment out 1 of the following when debugging
///
HTTP.HTTPMethod('GET', sLocalUrl); // ----this works and WILL download the file
HTTP.HTTPMethod('GET', sTheUrl); // --- this always fails, and HTTP.ResultString contains "Not Found"
success := SysUtils.UpperCase(HTTP.ResultString) = 'OK';
if HTTP.ResultCode > 0 then
success := True; //this is here just to keep the value around while debugging
finally
HTTP.Free;
end;
end;
procedure TMyThread.Execute
begin
//fTheURL contains this value: http://web.archive.org/web/20071212205017/energizer.com/usbcharger/download/UsbCharger_setup_V1_1_1.exe
GetFile(fTheUrl);
end;
The problem is that when I assign a local variable to the function and give it the URL directly, everything works. However, when passing the variable into the function, it fails. Anyone have any ideas?
HTTP.HTTPMethod('GET', sLocalUrl); // ----this works and WILL download the file
HTTP.HTTPMethod('GET', sTheUrl); // --- this always fails, and HTTP.ResultString contains "Not Found"
I'm using the latest version of Synapse from their SVN repository (version from 2 days ago).
NOTE: The file I am attempting to download is known to have a virus, the program I am writing is meant to download malicious files for analysis. So, don't execute the file once you download it.
However, I'm using this URL b/c this is the one I can reproduce the issue with.
Your code is missing the crucial detail how you use TMyThread class. However, you write
every once in a while there are sites where it will not pull down a file, but wget or Firefox/IE will download it without issue.
which sounds like a timing issue.
Using the local variable works every time. Using the function parameter works only some of the time. That may be caused by the function parameter not containing the correct URL some of the time.
You need to be aware that creating a non-suspended thread may result in it starting to execute immediately (and possibly even to complete), before the next line after the construction call has even started to execute. Setting any property of the thread object after the thread has been created may therefore not work, as the thread execution may be past the point where the property is read. The fTheUrl field of the thread object will be an empty string initially, so whether the thread downloads the file will depend on it being set before.
Your fTheUrl field isn't even protected by a synchronization primitive. Both the thread proc and the code in the main thread can access it concurrently. Sharing data in this way between threads is an unsafe thing to do and may result in any thing from wrong behaviour to actual crashes.
If your thread is really used to download a single file you should remove the write access to the property, and write a custom constructor with a parameter for the URL. That will properly initialize the field before the thread starts.
If you are downloading several files in your program you should really not create a thread for each. Use a pool of threads (may be only one even) that will be assigned the files to download. For that a thread property is the right solution, but then it will need to be implemented with synchronization, and the thread should block when no file is to be downloaded, and unblock when the property is set. The download thread (or threads) would be consumer(s) in a producer-consumer implementation. Stack Overflow has questions and answers regarding this in the Delphi tag, in particular in the questions where alternatives to Suspend() and Resume() are discussed.
One last thing: Don't let unhandled exceptions escape the Execute() method. I'm not sure about whether Delphi 2010 handles these exceptions in the VCL, but unhandled exceptions in a thread may lead to problems like app crashes or freezes.
Well, I'm almost embarrassed to report it, but I owe it to those that took the time to respond.
The issue had nothing to do with Synapse, or TThread, but instead had everything to do with the fact that the URL is case-sensitive!
In my full application, I had a helper function that lowercased the URL (for some reason). I removed that and everything started working again...
Please update last Synapse Revision 127.

Resources