.NET Delphi

Title: Building Real-Life Web Services
Question: How to build a 'real-world' Web-Service with mixed method calls, data-aware connections, user-authorisation and session support
Answer:
We all knowing about the new technique hype called "SOAP" (Simple Object
Acess Protocal) and "Web-Services". If you are not familiar with this
technologies, please refer to the following Delphi3000 articles:
SOAP:
http://www.delphi3000.com/articles/article_2985.asp
WebServices:
http://www.delphi3000.com/articles/article_2301.asp
There are several sites available with a lot of 'ready-to-use' Web-Services
(e.g. http://www.xmethods.org/) which are simple to include in your own
applications. Most of these services have only one to a few methods to call
and all are stateless and need no authorisation. That's fine for the purposes
they are required, but what's if you plan to create a Client-Server (i.e.
n-tier) application using the newest technique of Web-Services? Well, you're
right if you say "Why should I use a Web-Service, I can do the same using
DCOM or by creating a Data/WebSnap application!". That's the same question
I thought about, but i found the following advantages using Web-Services
instead:
1. I have a 'well-designed' interface (WSDL), which can be directly used in
the application (e.g. (RIA as IMyWebService).GetEmployees; ) instead of
the abstract 'IAppServer' interface of DCOM applications.

2. I'm not settled on using Delphi, if I want to talk with the service. It's
also possible to use Python, Perl, C# or any other programming language
which supports Web-Services (e.g. a HTML Page).

3. I don't need to create a complex server application which must handle
my calls (thread-safe) but can use commercial servers like Apache or
Microsoft's IIS.

4. Computers running the client software don't need special component updates
(like DCOM) except a simple TCP/IP connection.

5. If you plan to make your Server (= Web-Service) World-Wide available, just
send it to your Internet Provider. You don't need to install special proto-
cols except HTTP.

6. It's very simple to encrypt your data send over the LAN/W-LAN/WWW. You only
need a SSL certificate (over HTTPS)!

As I mentiod earlier, there are two critical disadvantages using Web-Services:
1. Web-Services are stateless.
2. Web-Services are authorisation free.
An other major disadvantage of Web-Services is that you are not able to mix
method calls (knowing from 'IAppServer' (DCOM) applications) and data aware
connections (using the 'TSOAPConnection' component).
In this article I'll try to show you some workarounds and techniques you
can use to create statefull Web-Services with an authorisation support.
ScenarioYou want to create a (new) Client-Server application using a Web-Service
where the client can call several (data-aware) methods implemented by the
server. In this case, the server is the Apache or IIS for example and the
client is a native executeable (.exe). The Web-Service itself is attached
to the Server as a dynamic-link-library (.dll) for best performance.
Tip: While developing the service, it's also possible to create a standalone
or WebApp executeable for better debugging purposes!

o--------o o------------o o-------------o o----------o
| Client |--[WWW]--| Web-Server || Web-Service || Database |
o--------o o------------o o-------------o o----------o

SecurityBefore a client can call a method implemented by the Web-Service, a valid
user should be authorized by the system. For a simple Web-Service this
could be ignored but if you plan to create a complex Client-Server
application where critical data were present, an authorisation mechanism is
indispensable. If the user was successfully logged on, the Web-Service method
returns a so-called 'ticket'. The ticket is a simple encrypted string
containing the following data:
- Sign (simple ticket header)
- UserId (identifies the user in the database)
- ApplicationId (should be used to identifies various client applications)
- TimeStamp (actual date and time when the ticket was created)
Each Web-Service method must pass this ticket in one of his calling parameters:
Example:
function CountRegisteredUsers(var Ticket: string): Integer; stdcall;

All available methods validates the passed ticket and will raise an exception
if the ticket is invalid. Otherwise, a new ticket is created, registered and
returned. A ticket is invalid in the following cases:
1. The ticket is not registered as the next valid one.
2. The ticket is older than two hours.

Because a ticket is only valid for one (the next) transaction, it is compareable
with a TAN used by Online-Banking.
|Client|-[Logon: string]-{Ticket}
|
|Client|-[GetEmployees(var Ticket): string]
|
......|Client|-[Logoff(var Ticket): Boolean]

Web-Service Interface (Extract) { IMyAuthorisationService }
IMyAuthorisationService = interface(IInvokable)
['{C21C8D28-4C9A-488B-AC36-FDCD54846D1C}']
function Logon(const AppId, UserName, UserPwd: string;
var Ticket: string): Boolean; stdcall;
function Logoff(var Ticket: string): Boolean; stdcall;
function AddData(var Ticket: string;
const Key: string; const Data: Variant): Boolean; stdcall;
function DelData(var Ticket, Key: string): Boolean; stdcall;
function GetData(var Ticket, Key: string): Variant; stdcall;
end;
{ IMyDataService }
IMyDataService = interface(IInvokable)
['{1C420CF6-07A3-4430-9227-068EC39C1702}']
function GetEmployees(var Ticket: string): string; stdcall;
end;

The 'IMyAuthorisationService' manages the login/logoff mechanism and calls
private methods to create and check the tickets. The service also implements
the session support (AddData, DelData, GetData) which simply uses the 'AppId'
included in the ticket to locate the database rows containing the data.
The 'IMyDataService' manages all data-aware connections to the database like
'GetEmployees' and uses a local 'IMyAuthorisation' instance to validate the
passed tickets.
Web-Service Implementation (Extract)CreateTicket:
function TMyAuthorisationService.CreateTicket(const AppId,
UserID: string): string;
var
Cipher: TCipher_Blowfish;
begin
{ create cipher }
Cipher := TCipher_Blowfish.Create(THash_MD5.CalcString(CipherPwd, nil,
fmtMIME64), nil);
{ set cipher mode }
Cipher.Mode := cmCTS;
{ return encoded Ticket
Ticket-format: "sXp", "","",""

sXp - sign
xxx - actual timestamp
yyy - application id
zzz - user id }
Result := Cipher.CodeString(Format('"sXp", "%g", "%s", "%s"',
[Now, AppId, UserId]), paEncode, fmtMIME64);
end;

The 'CreateTicket' method simply creates the encrypted ticket. I have used
the cipher units from Hagen Reddmann (Version 3.0).
Logon:
function TSXPAuthorisationService.Logon(const AppId, UserName, UserPwd: string;
var Ticket: string): Boolean;
var
UserId: string;
begin
{ set empty Ticket as default }
Ticket := '';

{ check 'UserName' and 'UserPwd' and return valud 'UserId' }
Result := CheckUser(UserName, UserPwd, UserId);
if not Result then
Exit; // UserId not found = UserName or UserPwd invalid!!!
{ create first Ticket from UserId }
Ticket := CreateTicket(AppId, UserId);
end;

This simple logon method doesn't include the database logic which should
be implemented by yourself.
GetEmployees:
function TMyDataService.GetEmployees(var Ticket: string): string;
const
FileName =
'C:\Employees.xml';
var
Line: string;
Stream: TMemoryStream;
begin
{ set default }
Result := '';
{ check Ticket and create new one on success }
if not AuthorisationService.CheckTicket(Ticket) then
raise Exception.CreateFmt('GetEmployees-Error: Invalid Ticket(%s)', [Ticket]);
{ simply load a xml file from disk. in real-life application this should
come from a database! }
Stream := TMemoryStream.Create;
try
Stream.LoadFromFile(FileName);
Line := PChar(Stream.Memory);
Result := MimeEncodeString(Line);
finally
Stream.Free;
end;
end;

This method demonstrates a possible solutions on how to mix method calls
with data-aware connections. As I mentiod earlier, one advantage of Web-
Services is the independence of the programming language. If you use the
DataSnap technologie, you have to use the TSOAPConnection component and
Data-Providers which are only compatible with Delphi, limits the possible
DataModules to only one instance and don't allow mixed method calls except
the damn IAppServer interface. If you implement data-aware methods which
send it's datapackets in an embedded XML string, you can use all benefits
of Web-Services. To translate a datapacket to and from a XML string, you can
simply use a tool coming with Delphi called 'XMLMapper' (look at Delphi's
online help to get further informations).
Client (Extract) procedure TfrmMain.Button1Click(Sender: TObject);
var
EmployeesXML: string;
begin
{ logon user }
if not (RIOAs as IMyAuthorisationService).Logon(
'1234', // Unique ApplicationId
edtUserName.Text, // User Name
edtUserPwd.Text, // User Password
FTicket ) then // Global defined Ticket variable
Exit;
{ get Employees }
EmployeesXML := (RIODs as IMyDataService).GetEmployees(FTicket);

...tranform XML to datapackets...
end;

ConclusionWeb-Services are a way to create new Client-Server applications with a lot
of benefits. Delphi itself comes with a wide range of development tools
and components to make us (the developers) the way of SOAP as easy as
possible. Because SOAP and Web-Services are to be in its infancy I'm taut
what happens in the future.