Server Frequently Asked Questions
- How can I convert a VCL RODA Server to a DLL Server?
- How can I get IP address of the remote client from the service code?
- How can I log details (IP address, method name, parameters, duration, etc.) of every call made to a server?
- How can I shut down a server?
- How to secure your communication
- Why Delphi server can freeze during shutdown?
- Why do I get an 'An instance of type ... was being created, and a valid license could not be granted for the type ...' exception when one of Remoting SDK or DataAbstract classes is instantiated?
How can I convert a VCL RODA Server to a DLL Server?
I have a standard RODA Server application that works great. Now, I want to create a new version for a single user that has simple install and simple usage.
Step 1
: Create a new RO DLL Server and add it to the current project group. Right click on the project group and select Add New Project ...
In the ensuing dialog, select the Remoting SDK for Delphi
tab and DLL Server
from the options. Leave all of the default options except do not create a test client (check box lower left). You'll get a new project created that has only project source available.
Step 2
: With the new project created, add all the files from the original server with the exception of that projects main form - we don't want forms in our DLL!
If you have built a standard RODA server, you'll have a datamodule that gets loaded before your servers main form. We need to emulate this in the DLL. I changed my standard DLL project code to read like this:
library EBHD;
{#ROGEN:MyLibrary.rodl} // RemObjects: Careful, do not remove! }
uses
uROComInit,
Windows,
uRODLLServer,
uROBinMessage,
{ My added unit files };
{$R *.RES}
{$R RODLFile.RES} // RemObjects: Careful, do not remove!
procedure ROProc(Reason:integer);
begin
case Reason of
DLL_PROCESS_ATTACH: begin
DMServer := TDMServer.Create(nil);
RegisterMessage(DMServer.ROMessage);
end;
DLL_PROCESS_DETACH: begin
DMServer.Free;
end;
end
end;
begin
DLLProc:=@ROProc;
ROProc(DLL_PROCESS_ATTACH);
end.
Note that I removed the var section including the global BinMessage variable. I also removed the instantiation and freeing of BinServer in the DLL_PROCESS_ATTACH and DLL_PROCESS_DETACH areas respectively, and added the instantiation and freeing of my datamodule instead (the datamodule variable DMServer is declared as a global variable in the datamodule itself). So far, so good.
If you check your standard server datamodule, you'll see that it already contains a server component and a message component. Our DLL server has it's own server component called DLLServer, created in the uRODLLServer unit (check the initialization section of the unit). In other words, the standard server component is superfluous to requirements. That said, we only want to maintain one set of source code here. Time for some conditional compilation.
Step 3
: Add a {$DEFINE DLL}
statement at the top of the datamodule and then add some conditional statements. The idea is that we'll instantiate the standard ROServer in the standard datamodule only if the datamodule will not be used in a DLL. Here's my example code:
type
TDMServer = class(TDataModule)
DADriverMgr: TDADriverManager;
ROMessage: TROBinMessage;
InMemSessMgr: TROInMemorySessionManager;
DAIBODriver1: TDAIBODriver;
DAConnectionMgr: TDAConnectionManager;
procedure DataModuleCreate(Sender: TObject);
procedure DataModuleDestroy(Sender: TObject);
public
{$IFNDEF DLL}
ROServer: TROIndyHTTPServer;
{$ENDIF}
end;
...
procedure TDMServer.DataModuleCreate(Sender: TObject);
{$IFNDEF DLL}
var
ROMD: TROMessageDispatcher;
begin
ROServer := TROIndyHTTPServer.Create( Self );
ROMD := ROServer.Dispatchers.Add as TROMessageDispatcher;
ROMD.Message := ROMessage;
ROServer.Active := True;
{$ENDIF}
end;
The declaration and instantiation of the server is simple enough but note also the declaration, instantiation and assignment of the TROMessageDispatcher.
In essence, that's it! To test your new DLL Server, select Run
| Parameters
from the Delphi main menu. Declare the client app that you'll use to test the DLL, set your breakpoints in the DLL and then run from within the IDE.
How can I get IP address of the remote client from the service code?
.NET
The Service class has a public property ServerChannel
which returns the IServerChannelInfo interface reference. For IP-based channels this interface can be casted to INetworkServerChannelInfo interface which is defined as follows:
public interface INetworkServerChannelInfo : IServerChannelInfo
{
IPEndPoint LocalEndPoint { get; }
IPEndPoint RemoteEndPoint { get; }
}
So you can get access to both IP enpoints used by the current connection, local and remote. System.Net.IPEndPoint
class allows to get corresponding IP adresses, port numbers and so on.
Sample code:
public virtual System.DateTime GetServerTime()
{
var sci = ServerChannel as INetworkServerChannelInfo;
if (sci != null)
{
Console.WriteLine(String.Format("Remote endpoint: {0}", sci.RemoteEndPoint.ToString()));
Console.WriteLine(String.Format("Local endpoint: {0}", sci.LocalEndPoint.ToString()));
}
else Console.WriteLine("INetworkServerChannelInfo is not supported");
return DateTime.Now;
}
Delphi
Use TRORemoteDataModule.OnGetDispatchInfo event:
procedure TNewService.RORemoteDataModuleGetDispatchInfo(
const aTransport: IROTransport; const aMessage: IROMessage);
var
tcpinfo: IROTCPtransport;
httpinfo: IROHTTPTransport;
client_address: string;
begin
client_address := '';
if Supports(aTransport, IROTCPtransport, tcpinfo) then begin
client_address := tcpinfo.GetClientAddress;
end;
if client_address = '' then begin
if Supports(aTransport, IROHTTPTransport, httpinfo) then begin
client_address := httpinfo.Headers['REMOTE_ADDR'];
if client_address = '' then client_address := httpinfo.Headers['HTTP_CLIENT_IP'];
if client_address = '' then .... // check for others headers like REMOTE_ADDR,
//HTTP_CLIENT_IP, HTTP_X_FORWARDED_FOR (can be comma delimited list of IPs),
//HTTP_X_FORWARDED, HTTP_X_CLUSTER_CLIENT_IP,
//HTTP_FORWARDED_FOR, HTTP_FORWARDED
end;
end;
//Log('Client ' + client_address + ' connected!');
end;
How can I log details (IP address, method name, parameters, duration, etc.) of every call made to a server?
Check Dispatch Notifier sample.
How can I shut down a server?
Set server channel's Active
property to False.
The server will stop listening for client calls and its resources will be released in due course.
Why Delphi server can freeze during shutdown?
When Delphi server is shutting down during execution service method dead lock will happen and it will stay in memory. It is Delphi specific problem: you can't destroy 2 form in the same time because Delphi uses
TMultiReadExclusiveWriteSynchronizer
during destroying forms and datamodules.
When you close application, it destroys form/datamodule that contains server component. The server component can't be destroyed while exist at least one spawned thread. by other hand, spawned thread waits while service method will be completed. so it is deadlock.
There are several workarounds:
- Free server in OnClose event like:
procedure TServerForm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
ROServer.Free;
end;
- Put destroying of server component into finalization section like:
procedure TServerForm.FormCreate(Sender: TObject);
begin
with TROHTTPDispatcher(ROServer.Dispatchers.Add) do begin
Message := ROMessage;
Enabled := True;
PathInfo := 'Bin';
end;
ROServer.Port := 8099;
ROServer.Active := True;
end;
procedure TServerForm.FormDestroy(Sender: TObject);
begin
ROServer.Dispatchers.Clear;
end;
initialization
ROServer := TROIpHTTPServer.Create(nil);
finalization
ROServer.Free;
end.
- Choose Simple object as an ancestor instead of RO SDK Remote Datamodule (that is used by default), for your _impl.
Why do I get an 'An instance of type ... was being created, and a valid license could not be granted for the type ...' exception when one of Remoting SDK or DataAbstract classes is instantiated?
Please double-check the licenses.licx
file in your project.
If it uses strong names of the assemblies like in the example below:
RemObjects.SDK.Server.IpTcpServerChannel, RemObjects.SDK.Server, Version=..., Culture=..., PublicKeyToken=...
then you will need to remove version and culture information and leave the row in the format (
RemObjects.SDK.Server.IpTcpServerChannel, RemObjects.SDK.Server
It is usually better to use license definitions like the last one, with no version specified. In this case there will be no need to correct it to set correct assembly version information after future Remoting SDK or DataAbstract upgrades.
If there is no licenses.licx file in your project folder then you have to add it to the project manually and add there line in the format (
RemObjects.SDK.Server.IpTcpServerChannel, RemObjects.SDK.Server
and then set its build action to Embedded Resource
.