I have client-server system that uses DataSnap. I want to log the client application data so I use the TDSServer - OnConnect Event. In this event I can access what I want with the following code:
IP:= DSConnectEventObject.ChannelInfo.ClientInfo.IpAddress
ClientPort:= DSConnectEventObject.ChannelInfo.ClientInfo.ClientPort
Protocol:= DSConnectEventObject.ChannelInfo.ClientInfo.Protocol
AppName:= DSConnectEventObject.ChannelInfo.ClientInfo.AppName
first 3 lines are OK but AppName is empty!!!
(I run server and client on the same computer i.e. localhost)
I have been unable to find any online information about how to specify the AppName when the client connects via TCP/IP. If you look at the code
procedure TDSTCPChannel.Open;
var
ClientInfo: TDBXClientInfo;
begin
inherited;
FreeAndNil(FChannelInfo);
FChannelInfo := TDBXSocketChannelInfo.Create(IntPtr(FContext.Connection), FContext.Connection.Socket.Binding.PeerIP);
ClientInfo := FChannelInfo.ClientInfo;
ClientInfo.IpAddress := FContext.Connection.Socket.Binding.PeerIP;
ClientInfo.ClientPort := IntToStr(FContext.Connection.Socket.Binding.PeerPort);
ClientInfo.Protocol := 'tcp/ip';
FChannelInfo.ClientInfo := ClientInfo;
end;
in DataSnap.DSTCPServerTransport.Pas it is evident that the ClientInfo.AppName is not set.
However, the following work-around works with the Seattle demo DataSnap Basic Server + Client:
In the client, add a Param 'AppName' to the SqlConnection1 component's Params and
set its value to something like 'MyTestApp'. Recompile the client.
Open the server in the IDE and modify the ServerContainerForm's code as shown below.
Code:
uses
[...], DBXTransport;
procedure TForm8.DSServer1Connect(DSConnectEventObject: TDSConnectEventObject);
var
S : String; // added
Info : TDBXClientInfo; // added
begin
ActiveConnections.Insert;
if DSConnectEventObject.ChannelInfo <> nil then
begin
ActiveConnections['ID'] := DSConnectEventObject.ChannelInfo.Id;
ActiveConnections['Info'] := DSConnectEventObject.ChannelInfo.Info;
end;
ActiveConnections['UserName'] := DSConnectEventObject.ConnectProperties[TDBXPropertyNames.UserName];
ActiveConnections['ServerConnection'] := DSConnectEventObject.ConnectProperties[TDBXPropertyNames.ServerConnection];
ActiveConnections.Post;
InsertEvent('Connect');
// following added to get AppName from client
S := DSConnectEventObject.ConnectProperties['AppName'];
Info := DSConnectEventObject.ChannelInfo.ClientInfo;
Info.AppName := S;
DSConnectEventObject.ChannelInfo.ClientInfo := Info;
Caption := DSConnectEventObject.ChannelInfo.ClientInfo.AppName;
end;
As you can see, it works by picking up the value set for AppName in the client's
SqlConnection1.Params in the call to `DSConnectEventObject.ConnectProperties['AppName']'
and then display it on the Caption of the ServerContainerForm.
Obviously, you could pass any other name/value pair by adding them to the SqlConnection's Params on the client and then pick them up on the server by calling DSConnectEventObject.ConnectProperties[].
Related
I'm developing a FTP Server with Delphi XE 6 and Indy10. The problem is that i need to limit the speed of download (must be configurable Ex. 1 KB/s, 1 MB/s, etc.) and i don't make it work. I know some props like BitsPerSec, etc. but this only affect the protocol data exchange, not the file exchange with RETR command. I see in IdFTPServer.pass, and the Stream is converted to string and send with a repeat/until loop (with IOHandler.Write()) but i need some form of control the upload/download process and be able to limit the speed for all incoming client connections. Some help to achieve this please?.
PD: Sorry for my poor english.
I try to implement a CommandHandler for the RETR command with this code:
procedure TForm1.IdFTPServer1CommandHandlers0Command(ASender: TIdCommand);
var
LContext : TIdFTPServerContext;
vStream: TFileStream;
path: String;
vX: Integer;
LEncoding: IIdTextEncoding;
begin
LEncoding := IndyTextEncoding_8Bit;
LContext := ASender.Context as TIdFTPServerContext;
path := 'D:\indy_in_depth.pdf';
try
vStream := TFileStream.Create(path, fmOpenRead);
//LContext.DataChannel.FtpOperation := ftpRetr;
//LContext.DataChannel.Data := VStream;
LContext.DataChannel.OKReply.SetReply(226, RSFTPDataConnClosed);
LContext.DataChannel.ErrorReply.SetReply(426, RSFTPDataConnClosedAbnormally);
ASender.Reply.SetReply(150, RSFTPDataConnToOpen);
ASender.SendReply;
Memo1.Lines.Add('Sending a file with: ' + IntToStr(vStream.Size) + ' bytes');
//Make control of speed here !
for vX := 1 to vStream.Size do
begin
vStream.Position := vX;
LContext.Connection.IOHandler.Write(vStream, 1, False);
if vX mod 10000 = 0 then
Memo1.Lines.Add('Sended byte: ' + IntToStr(vX));
end;
//LContext.DataChannel.InitOperation(False);
//LContext.Connection.IOHandler.Write(vStream);
//LContext.KillDataChannel;
LContext.Connection.Socket.Close;
VStream.Free;
except
on E: Exception do
begin
ASender.Reply.SetReply(550, E.Message);
end;
end;
end;
But the Filezilla FTP Client show stream data like part of other command.
Filezilla Client
The problem is that i need to limit the speed of download (must be configurable Ex. 1 KB/s, 1 MB/s, etc.) and i don't make it work. I know some props like BitsPerSec, etc. but this only affect the protocol data exchange, not the file exchange with RETR command.
BitsPerSec is a property of the TIdInterceptThrottler class (along with RecvBitsPerSec and SendBitsPerSec properties). An instance of that class can be assigned to any TIdTCPConnection object via its Intercept property. This is not limited to just the command connection, it can be used for transfer connections, too.
You can access the TIdTCPConnection object of a file transfer in TIdFTPServer via the TIdFTPServerContext.DataChannel.FDataChannel member, such as in the OnRetrieveFile event:
type
// the TIdDataChannel.FDataChannel member is 'protected',
// so use an accessor class to reach it...
TIdDataChannelAccess = class(TIdDataChannel)
end;
procedure TForm1.IdFTPServer1RetrieveFile(ASender: TIdFTPServerContext;
const AFileName: TIdFTPFileName; var VStream: TStream);
var
Conn: TIdTCPConnection;
begin
Conn := TIdDataChannelAccess(ASender.DataChannel).FDataChannel;
if Conn.Intercept = nil then
Conn.Intercept := TIdInterceptThrottler.Create(Conn);
TIdInterceptThrottler(Conn.Intercept).BitsPerSec := ...;
VStream := TFileStream.Create(AFileName, fmOpenRead or fmShareDenyWrite);
end;
An alternative is to create your own T(File)Stream derived class to override the virtual Read() and Write() methods to perform your own throttling as needed, and then assign an instance of that class to the VStream parameter of the OnRetrieveFile and OnStoreFile events.
I see in IdFTPServer.pas, and the Stream is converted to string and send with a repeat/until loop (with IOHandler.Write())
No, a stream is not converted to a string. You are misreading the code. A stream's content is sent as-is as binary data in a single call to the IOHandler.Write(TStream) method.
but i need some form of control the upload/download process and be able to limit the speed for all incoming client connections.
See above.
I try to implement a CommandHandler for the RETR command
You should NOT be handling the RETR command directly. TIdFTPServer already does that for you. You are meant to use the OnRetrieveFile event instead to prepare downloads, and the OnStoreFile event for uploads.
I am trying to access a URL in Delphi using a TIdHTTP Indy Tool.
I have done the following:
Set Accept Cookies = True
Set Handle Redirect = True
Added a TIdCookieManager
http://sms.saicomvoice.co.za:8900/saicom/index.php?action=login&username=SOME_USERNAME&password=SOME_PASSWORD&login=login
The Post request works and it returns the HTML. The problem is it doesn't return the correct HTML (See Image Below).
If I take that URL ( Filling in the username and password ) and paste it into my browser exactly The Same as my Delphi Application would then logs into the correct website. But as soon as I do it with my Delphi App it returns the HTML for the login page.
The request is supposed to be executed timeously in a TTimer in Delphi.
Can anyone lead me unto the right path or point me in a direction as to how I can solve this problem ?
Some Additional Information
WriteStatus is a Procedure That writes output to a TListBox
BtnEndPoll Stops the timer
Procedure TfrmMain.TmrPollTimer(Sender: TObject);
Var
ResultHTML: String;
DataToSend: TStringList;
Begin
Inc(Cycle, 1);
LstStatus.Items.Add('');
LstStatus.Items.Add('==================');
WriteStatus('Cycle : ' + IntToStr(Cycle));
LstStatus.Items.Add('==================');
LstStatus.Items.Add('');
DataToSend := TStringList.Create;
Try
WriteStatus('Setting Request Content Type');
HttpRequest.Request.ContentType := 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8';
WriteStatus('Setting Request User Agent');
HttpRequest.Request.UserAgent := 'Mozilla/5.0 (Windows NT 5.1; rv:2.0b8) Gecko/20100101 Firefox/4.0b8';
WriteStatus('Posting Request');
ResultHTML := HttpRequest.Post(FPostToURL, DataToSend);
WriteStatus('Writing Result');
FLastResponse := ResultHTML;
WriteStatus('Cycle : ' + IntToStr(Cycle) + ' -- FINISHED');
LstStatus.Items.Add('');
Except
On E: Exception Do
Begin
MakeNextEntryError := True;
WriteStatus('An Error Occured: ' + E.Message);
If ChkExceptionStop.Checked Then
Begin
BtnEndPoll.Click;
WriteStatus('Stopping Poll Un Expectedly!');
End;
End;
End;
End;
* Image Example *
HttpRequest.Request.ContentType := 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,/;q=0.8';
That is not a valid ContentType value. That kind of value belongs in the Request.Accept property instead. It tells the server which ContentTypes the client will accept in the response.
ResultHTML := HttpRequest.Post(FPostToURL, DataToSend);
You are posting a blank TStringList. Putting a URL into a browser's address bar sends a GET request, not a POST request, so you should be using TIdHTTP.Get() instead:
ResultHTML := HttpRequest.Get('http://sms.saicomvoice.co.za:8900/saicom/index.php?action=login&username=SOME_USERNAME&password=SOME_PASSWORD&login=login');
You would use TIdHTTP.Post() if you wanted to simulate the HTML webform being submitted to the server (since it specifies method=post), eg:
DataToSend.Add('username=SOME_USERNAME');
DataToSend.Add('password=SOME_PASSWORD');
DataToSend.Add('login=Login');
ResultHTML := HttpRequest.Post('http://sms.saicomvoice.co.za:8900/saicom/index.php?action=login', DataToSend);
Background :
Application written in Delphi-7 or -6 (have search through the .exe file). Called Sigmanest.
I have moved to new server and that's left is SigmaNest database running under SQL server. After many hours of troubleshooting for migrate that database to new server. The trouble have covered all aspect of this SQL server app. services not started at default, non working wizard, lack of full-text something, ridiculous many places to right click etc .. The lost goes on and on.
At the moment I have a working SQL server with the database SNDBase (sigmanest) at the new hardware but not be able to connect from client.
Na this can't be right I thought and searched for alternatives.. 4-5 click in MySQL workbench and I have it up an running on test linux box... Fine ..
But now it comes to problem.
SigmaNest uses a ini files for its config.
So inside one ini file I found
; 1 = Paradox, 2 = MSSQLServer or MSDE
ADOConnectionString=Provider=SQLOLEDB.1;Data Source=ODIN\SIGMANEST;User ID=sigmanest;Password="";Persist Security Info=True;Initial Catalog=SNDBase;
Okej .. installed Mysql odbc driver on the client and made the connection . All working so far ..
Turned to google and found the ADOConnectionsstring for mysql ...
ADOConnectionString=DRIVER={MySQL ODBC 5.2a Driver};SERVER=192.168.100.19;PORT=3306;DATABASE=SNDBase;UID=sigmanest;PASSWORD=;OPTION=4;
But the app will not start . just return to sigmanest's config tool for db connection.
Have posted this to SigmaTek but the only response I have been giving is a mail with ADOConnection string for MS sql server through SQLOLEDB.
Have searched the drive and haven't found anny dbex*.dll files witch means that they don't uses dbExpress component (my guess).
So is there some missing dll files that didn't come with the app. Or is this kind of thing hardcoded inside the program?
Anyone having a idea how to proceed ?
Or should I drop the mysql dream and go for the waste of space sql server backend.
Per Nils
PS.
The SigmaNest.exe have a time stamp 2006-05-19
DS.
Sorry but I didn't know where to go with this issue. And stackoverflow seems to have gadder ed all the talent people in the world under the same roof so to speak.
Ken White : Yes you are right I can't use MySQL on this application (after hours of googling and testing).
Anyway I managed to make the connection like this (maybe something is usefully for others some of it is Delphi related)
First you have to download the mysql connector http://dev.mysql.com/downloads/connector/odbc/
Make a ODBC conenction from control panel-> administration tools -> Data sources (ODBC) under tab "User DSN"
The you make a new text file with notepad.
Rename the file with extension .udl
Double click on it and fill in the dialog boxes ...
Open the file in notepad and there you have your adoconnection string.
But your problem is not over.. Difference in SQL vs MySQL will make your app to halt .. for example boolean in MySQL is declared as a tinyint (0=false 1=true)
A workaround is mention here http://www.i-logic.com/utilities/MySQL.htm
So my struggle gave nothing in return but some of above could be a interested for others.
Per Nils ..
ODBC can be configured at runtime, below is an example of configuring ODBC to connect to SQL Server using BDE.
unit uBDEConnectionSqlServer;
interface
uses
DBTables, Windows, Classes, SysUtils;
type TBDEConnectionSqlServer = class(TComponent)
private
{ Private declarations }
function CreateOBDCConnection(dataBase : string; server: string):Boolean;
public
{ Public declarations }
Function CreateBDEConnection(dataBase: TDatabase; dataBaseName : string; server:string; userName:string; password:string): Boolean;
end;
const
ODBC_ADD_DSN = 1; // Add data source
ODBC_CONFIG_DSN = 2; // Configure (edit) data source
ODBC_REMOVE_DSN = 3; // Remove data source
ODBC_ADD_SYS_DSN = 4; // add a system DSN
ODBC_CONFIG_SYS_DSN = 5; // Configure a system DSN
ODBC_REMOVE_SYS_DSN = 6; // remove a system DSN
ODBC_REMOVE_DEFAULT_DSN = 7; // remove the default DSN
function SQLConfigDataSource(
hwndParent: HWND;
fRequest: WORD;
lpszDriver: LPCSTR;
lpszAttributes: LPCSTR): BOOL; stdcall; external 'ODBCCP32.DLL';
implementation
Function TBDEConnectionSqlServer.CreateBDEConnection(dataBase: TDatabase; dataBaseName : string; server:string; userName:string; password:string): Boolean;
var
retorno: TDatabase;
Begin
result := false;
if (CreateOBDCConnection(dataBaseName, server) = true) then
begin
dataBase.AliasName := 'testedelphi';
dataBase.LoginPrompt := False;
dataBase.DatabaseName := 'testedelphi';
dataBase.Params.Values['DATABASE NAME'] := dataBaseName;
dataBase.Params.Values['USER NAME'] := userName;
dataBase.Params.Values['ODBC DSN'] := dataBaseName;
dataBase.Params.Values['OPEN MODE'] := 'READ/WRITE';
dataBase.Params.Values['BATCH COUNT'] := '200';
dataBase.Params.Values['LANGDRIVER'] := '';
dataBase.Params.Values['MAX ROWS'] := '-1';
dataBase.Params.Values['SCHEMA CACHE DIR'] := '';
dataBase.Params.Values['SCHEMA CACHE SIZE'] := '8';
dataBase.Params.Values['SCHEMA CACHE TIME'] := '-1';
dataBase.Params.Values['SQLPASSTHRU MODE'] := 'SHARED AUTOCOMMIT';
dataBase.Params.Values['SQLQRYMODE'] := '';
dataBase.Params.Values['ENABLE SCHEMA CACHE'] := 'FALSE';
dataBase.Params.Values['ENABLE BCD'] := 'FALSE';
dataBase.Params.Values['ROWSET SIZE'] := '20';
dataBase.Params.Values['BLOBS TO CACHE'] := '64';
dataBase.Params.Values['BLOB SIZE'] := '32';
dataBase.Params.Values['PASSWORD'] := password;
result := true;
end;
end;
function TBDEConnectionSqlServer.CreateOBDCConnection(dataBase : string; server: string):Boolean;
var
resultado: BOOL;
begin
Result := False;
resultado := SQLConfigDataSource(
0,
ODBC_ADD_DSN,
'SQL Server',
PChar(
'DSN='+dataBase+#0 +
'SERVER='+server+#0 +
'ADDRESS='+server+#0 +
'NETWORK=dbmssocn'#0 +
'DATABASE='+dataBase+#0 +
'DESCRIPTION='+server+dataBase+#0 +
#0
)
);
if(StrToInt(BoolToStr(resultado)) <> 0) then
Result := True;
end;
end.
I use following procedure to encode stream.
procedure SaveEncodedStream(Strm:TStream; LicFileName:String);
var
C:TCodec;
CL:TCryptographicLibrary;
Sg:TSignatory;
KFS,DFS:TFileStream;
Dir:String;
begin
CL:=TCryptographicLibrary.Create(nil);
C:=TCodec.Create(nil);
SG:=TSignatory.Create(nil);
Dir := ExtractFilePath(ParamStr(0));
KFS:=TFileStream.Create(Dir+PublicKeyFile,fmOpenRead);
DFS:=TFileStream.Create(LicFileName,fmCreate);
try
C.CryptoLibrary:=CL;
C.BlockCipherId := 'native.RSA';
C.ChainModeId := 'native.CBC';
C.AsymetricKeySizeInBits := 1024;
SG.Codec:=C;
SG.LoadKeysFromStream(KFS,[partPublic]);
C.EncryptStream(Strm,DFS);
finally
CL.Free;
C.Free;
SG.Free;
KFS.Free;
DFS.Free;
end;
end;
And receive "Wrong Mode" error on
C.EncryptStream(Strm,DFS); call
Stepping into the code I discovered that it even does not try to load keys as Codec is not initialized. When I place componets on the form - everything works. But I do not need Form or DataModule.
Have not found solution to get rid of DataModule. It looks like components need one to properly initialize themselves. So as workaround I have created global DataModule with all components configured in design mode. I use that module in SaveEncodedStream like that:
uses
... EncryptDataModule;
...
var
BeenHere:Boolean = false;
...
procedure SaveEncodedStream(Strm:TStream; LicFileName:String);
var
KFS,DFS:TFileStream;
Dir:String;
begin
Dir := ExtractFilePath(ParamStr(0));
KFS:=TFileStream.Create(Dir+PublicKeyFile,fmOpenRead);
DFS:=TFileStream.Create(LicFileName,fmCreate);
try
DataModule.SG.LoadKeysFromStream(KFS,[partPublic]);
if BeenHere then DataModule.C.Reset;
DataModule.C.EncryptStream(Strm,DFS);
BeenHere:=true;
finally
KFS.Free;
DFS.Free;
end;
end;
I am using the following code to setup DataSnap Connection pragmatically
procedure TConnectThreed.Execute;
var
DataSnapCon : TSQLConnection;
proxy : TSystemRDMClient;
begin
proxy := nil;
DataSnapCon := nil;
try
DataSnapCon := TSQLConnection.Create(nil);
DataSnapCon.Connected := False;
DataSnapCon.DriverName := 'DATASNAP';
DataSnapCon.LoginPrompt := False;
DataSnapCon.Params.Values['port'] := '211';
DataSnapCon.Params.Values['HostName'] := DevicesAddr;
//
// What code must be added here to setup a Zlib + PC1 +RSA Filter ?
//
try
DataSnapCon.Open;
proxy := TSystemRDMClient.Create(DataSnapCon.DBXConnection);
QUESTION:
How do I setup a Zlib & PC1 & RSA filter pragmatically?
If you have a look at the dfm file you see what is going on with the magic Driver property in the Object Inspector. The selections you make are stored in TSQLConnection.Params for name Filters.
To add the filters you can do this.
DataSnapCon.Params.Values['Filters'] :=
'{"ZLibCompression":{"CompressMoreThan":"1024"},'+
'"PC1":{"Key":"LiveStrongLance!"}}';
But this will still get Connection Closed Gracefully that you have experienced here Delphi XE – Datasnap Filter problems.