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.