Mega Code Archive

 
Categories / Delphi / .NET
 

Building Real Life Web Services

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.