Difference between revisions of "Web Service Toolkit"

From Lazarus wiki
Jump to navigationJump to search
(Web services creation)
Line 1: Line 1:
"Web Service Toolkit” is a web services package for FPC and Lazarus;  “Web Service Toolkit” is meant to ease web services consumption by FPC and Lazarus users.
+
"Web Service Toolkit” is a web services package for FPC and Lazarus;  “Web Service Toolkit” is meant to ease web services <b>consumption and creation</b> by FPC and Lazarus users.
  
==Overview==
+
==Client Side ( service consumption )==
 +
===Overview===
  
 
“Web Service Toolkit” is made of two parts, a command line tool “ws_helper” and a collection of support units. Given an interface definition file( describing à web service ), “ws_helper” will create a  FPC  unit containing a proxy implementing that interface.  At runtime when a call targeting the web service is issued, the proxy's role is to :
 
“Web Service Toolkit” is made of two parts, a command line tool “ws_helper” and a collection of support units. Given an interface definition file( describing à web service ), “ws_helper” will create a  FPC  unit containing a proxy implementing that interface.  At runtime when a call targeting the web service is issued, the proxy's role is to :
Line 11: Line 12:
 
Behind the scene, the proxy will take care of the SOAP plumbing details.
 
Behind the scene, the proxy will take care of the SOAP plumbing details.
  
==Example==
+
===Example===
 
We will use the “google web api”, freely available for personal use at this adress “http://www.google.com/apis/”.In order to use this service, we have to translate its exposed WSDL interface to Pascal langage. By now “Web Service Toolkit”  does not contain a WSDL to pascal compiler, so we will assume this translation available as below ( this is an incomplete translation, but it's enough for the sample ).
 
We will use the “google web api”, freely available for personal use at this adress “http://www.google.com/apis/”.In order to use this service, we have to translate its exposed WSDL interface to Pascal langage. By now “Web Service Toolkit”  does not contain a WSDL to pascal compiler, so we will assume this translation available as below ( this is an incomplete translation, but it's enough for the sample ).
  
Line 28: Line 29:
 
   End.
 
   End.
  
Invoking “ws_helper” at the prompt with the file “googlewebapi.pas” as argument will produce a file “googlewebapiimpunit.pas” as below.
+
Invoking “ws_helper” at the prompt with the file “googlewebapi.pas” as argument will produce a file “googlewebapi_proxy.pas” as below.
  
 
   Unit googlewebapiimpunit;
 
   Unit googlewebapiimpunit;
Line 44: Line 45:
  
 
   Implementation
 
   Implementation
 +
  uses TypInfo;
 
   { TGoogleSearch_Proxy implementation }
 
   { TGoogleSearch_Proxy implementation }
 
   function TGoogleSearch_Proxy.doSpellingSuggestion(
 
   function TGoogleSearch_Proxy.doSpellingSuggestion(
Line 50: Line 52:
 
   ):string;
 
   ):string;
 
   Var
 
   Var
     locSerializer : TAbstractFormater;
+
     locSerializer : IFormatterClient;
 
     strPrmName : string;
 
     strPrmName : string;
 
     resTypeInfo : PTypeInfo;
 
     resTypeInfo : PTypeInfo;
Line 79: Line 81:
 
   uses
 
   uses
 
     Classes, SysUtils,
 
     Classes, SysUtils,
     service_intf, soap_imp,
+
     base_service_intf, service_intf, soap_formatter,
 
     //indy_http_protocol,
 
     //indy_http_protocol,
 
     ics_http_protocol,
 
     ics_http_protocol,
     googlewebapi, googlewebapiimpunit;
+
     googlewebapi, googlewebapi_proxy;
  
 
   Const
 
   Const
Line 109: Line 111:
 
   end.
 
   end.
  
The units  service_intf, soap_imp, indy_http_protocol, ics_http_protocol as provided with this toolkit; Below is the result of a execution session spelling for “freepscal lzarus” written with a missing letter 'a' beetwen the letter 'p' and the the letter 's' and a missing letter 'a' beetwen the letter 'L' and the the letter 'z' .
+
The units  base_service_intf, service_intf, soap_formatter, indy_http_protocol, ics_http_protocol as provided with this toolkit; Below is the result of a execution session spelling for “freepscal lzarus” written with a missing letter 'a' beetwen the letter 'p' and the the letter 's' and a missing letter 'a' beetwen the letter 'L' and the the letter 'z' .
  
 
   > .\tests\google_api\test_google_api.exe
 
   > .\tests\google_api\test_google_api.exe
Line 117: Line 119:
  
 
Google spells it correctly : “freepascal lazarus”!
 
Google spells it correctly : “freepascal lazarus”!
 +
 +
==Server Side ( service creation )==
 +
===Overview.===
 +
Web Service Toolkit contains a server side framework for service creation. Key features are:
 +
*Service definition( interface )  is separated from implementation,
 +
*Interface and implementations are not bound to message protocol,
 +
*Support for SOAP 1.1 and a binary protocol,
 +
*The framework is not bound to a transport protocol.
 +
*Easy to add support for application servers
 +
 +
===Example===
 +
In order to create a service, we have to :
 +
*define its interface,
 +
*provide an implementation and register that one for the service,
 +
*provide a binder that will route calls targeting the service to the implementation and register that one,
 +
*host the service into an application server( TCP server, HTTP Server, ... ).
 +
 +
====Defining the service Interface====
 +
We will use the interface defined above for our sample. The complete projects of the example is located in the folder “\tests\tcp_server\calculator”.
 +
 +
  unit calculator;
 +
  {$mode objfpc}{$H+}
 +
  interface
 +
  uses SysUtils, base_service_intf;
 +
  Type
 +
    TBinaryArgsResult = class(TBaseComplexRemotable)
 +
      private
 +
      FArg_A: Integer;
 +
      FArg_B: Integer;
 +
      FArg_OP: string;
 +
      FArg_R: Integer;
 +
    Published
 +
      Property Arg_A : Integer Read FArg_A Write FArg_A;
 +
      Property Arg_B : Integer Read FArg_B Write FArg_B;
 +
      Property Arg_R : Integer Read FArg_R Write FArg_R;
 +
      Property Arg_OP : string Read FArg_OP Write FArg_OP;
 +
    End;
 +
    ICalculator = Interface
 +
      function AddInt(Const A:Integer;Const B:Integer):TBinaryArgsResult;
 +
      function DivInt(Const A:Integer;Const B:Integer):Integer;
 +
    End;
 +
 +
  implementation
 +
  uses base_soap_formatter;
 +
  Initialization
 +
    GetTypeRegistry().Register(sXSD_NS,TypeInfo(TBinaryArgsResult),'TBinaryArgsResult');
 +
  end.
 +
 +
====Providing an implementation for the service====
 +
“ws_helper” has options to generate proxy file, basic implementation skeleton file and a binder file ( see the listing above).
 +
 +
  ws_helper [-p] [-b] [-i] [-oPATH] inputFilename
 +
    -p  Generate service proxy
 +
    -b  Generate service binder
 +
    -i  Generate service minimal implementation
 +
    -o  PATH  Output directory
 +
 +
Executing “ws_helper” with the -i and -b options as above will produce two files : <b>calculator_imp.pas</b> and <b>calculator_binder.pas</b>.
 +
 +
  ws_helper\ws_helper.exe -i -b -osrv tests\tcp_server\calculator\calculator.pas
 +
  ws_helper Copyright (c) 2006 by Inoussa OUEDRAOGO
 +
  File "tests\tcp_server\calculator\calculator.pas" parsed succesfully.
 +
 +
The calculator_imp.pas unit contains a skeleton implementation class for the interface. It defines a procedure named <b>RegisterCalculatorImplementationFactory</b>. The procedure registers the  class as the service implementation provider in the implementation registry.
 +
 +
  Unit calculator_imp;
 +
  {$mode objfpc}{$H+}
 +
  Interface
 +
  Uses SysUtils, Classes,
 +
      base_service_intf, server_service_intf, server_service_imputils, calculator;
 +
  Type
 +
    TCalculator_ServiceImp=class(TSimpleFactoryItem,ICalculator)
 +
    Protected
 +
      function AddInt(Const A : Integer;Const B : Integer):TBinaryArgsResult;
 +
      function DivInt(Const A : Integer;Const B : Integer):Integer;
 +
    End;
 +
    procedure RegisterCalculatorImplementationFactory();
 +
 +
  Implementation
 +
  { TCalculator_ServiceImp implementation }
 +
  function TCalculator_ServiceImp.AddInt( Const A : Integer; Const B : Integer):TBinaryArgsResult;
 +
  Begin
 +
    Result := TBinaryArgsResult.Create();
 +
    Try
 +
      Result.Arg_OP := '+';
 +
      Result.Arg_A := A;
 +
      Result.Arg_B := B;
 +
      Result.Arg_R := A + B;
 +
    Except
 +
      FreeAndNil(Result);
 +
      Raise;
 +
    End;
 +
  End;
 +
  function TCalculator_ServiceImp.DivInt( Const A : Integer; Const B : Integer):Integer;
 +
  Begin
 +
    Result := A div B;
 +
  End;
 +
  procedure RegisterCalculatorImplementationFactory();
 +
  Begin
 +
    GetServiceImplementationRegistry().Register(
 +
      'Calculator',
 +
      TSimpleItemFactory.Create(TCalculator_ServiceImp) as IItemFactory
 +
    );
 +
  End;
 +
  End.     
 +
 +
====Providing a binder for the service.====
 +
The binder's role is to:
 +
*unpack the incoming message,
 +
*set up the call stack,
 +
*make the call against the registered implementation,
 +
*serialize the execution stack to create the return message.
 +
 +
The <b>calculator_binder.pas</b> unit generated by ws_helper, contains :
 +
*<b>TCalculator_ServiceBinder</b> : the actual binder class,
 +
*<b>TCalculator_ServiceBinderFactory</b> a factory class for the binder and
 +
*<b>Server_service_RegisterCalculatorService</b> : the binder factory registration procedure.
 +
The following code extract shows the unit interface part and a method handler of the binder.
 +
 +
  Unit calculator_binder;
 +
  {$mode objfpc}{$H+}
 +
  Interface
 +
  Uses SysUtils, Classes, base_service_intf, server_service_intf, calculator;
 +
  Type
 +
    TCalculator_ServiceBinder=class(TBaseServiceBinder)
 +
    Protected
 +
      procedure AddIntHandler(AFormatter:IFormatterResponse);
 +
      procedure DivIntHandler(AFormatter:IFormatterResponse);
 +
    Public
 +
      constructor Create();
 +
    End;
 +
    TCalculator_ServiceBinderFactory = class(TInterfacedObject,IItemFactory)
 +
    protected
 +
      function CreateInstance():IInterface;
 +
    End;
 +
    procedure Server_service_RegisterCalculatorService();
 +
 +
  Implementation
 +
  uses TypInfo;
 +
  procedure TCalculator_ServiceBinder.AddIntHandler(AFormatter:IFormatterResponse);
 +
  Var
 +
    tmpObj : ICalculator;
 +
    callCtx : ICallContext;
 +
    strPrmName : string;
 +
    procName,trgName : string;
 +
    A : Integer;
 +
    B : Integer;
 +
    returnVal : TBinaryArgsResult;
 +
    locTypeInfo : PTypeInfo;
 +
  Begin
 +
    callCtx := CreateCallContext();
 +
    locTypeInfo := TypeInfo(TBinaryArgsResult);
 +
    If ( locTypeInfo^.Kind in [tkClass,tkInterface] ) Then
 +
      Pointer(returnVal) := Nil;
 +
    strPrmName := 'A';  AFormatter.Get(TypeInfo(Integer),strPrmName,A);
 +
    strPrmName := 'B';  AFormatter.Get(TypeInfo(Integer),strPrmName,B);
 +
    tmpObj := Self.GetFactory().CreateInstance() as ICalculator;
 +
    returnVal := tmpObj.AddInt(A,B);
 +
    locTypeInfo := TypeInfo(TBinaryArgsResult);
 +
    If ( locTypeInfo^.Kind = tkClass ) And Assigned(Pointer(returnVal)) Then
 +
      callCtx.AddObject(TObject(returnVal));
 +
    procName := AFormatter.GetCallProcedureName();
 +
    trgName := AFormatter.GetCallTarget();
 +
    AFormatter.Clear();
 +
    AFormatter.BeginCallResponse(procName,trgName);
 +
      AFormatter.Put('return',TypeInfo(TBinaryArgsResult),returnVal);
 +
    AFormatter.EndCallResponse();
 +
    callCtx := Nil;
 +
  End;
 +
 +
====Host the service into an application server.====
 +
The application server's role is to route incoming service requests to the Web Service Toolkit runtime. For the runtime to process service requests :
 +
*The services and their implementations have to be registered ,
 +
*The message protocol (SOAP, binary,...) have to be registered.
 +
 +
The runtime interface is defined in the server_service_intf unit. This unit contains :
 +
*<b>GetServerServiceRegistry</b>, which returns the service registry,
 +
*<b>GetServiceImplementationRegistry</b> which returns the service implementation registry,
 +
*<b>GetFormatterRegistry</b> which returns the message format registry and
 +
*<b>HandleServiceRequest</b> which is the unique entry point for request processing.
 +
 +
The toolkit is provided with a simple TCP server hosting the sample <b>Calculator</b>  service defined early in this document located in the "\tests\tcp_server folder". The complete source files of the sample is in that directory and the client application in the "\client"  sub folder. The registrations are done in the application's main form <b>OnCreate</b> event as printed above:
 +
 +
  procedure TfMain.FormCreate(Sender: TObject);
 +
  begin
 +
    Server_service_RegisterCalculatorService();
 +
    RegisterCalculatorImplementationFactory();
 +
    Server_service_RegisterSoapFormat();
 +
    Server_service_RegisterBinaryFormat();
 +
  end;
 +
 +
<b>Server_service_RegisterCalculatorService</b> located in the calculator_binder unit ( generated by ws_helper ) registers the Calculator service by calling in turn <b>GetServerServiceRegistry</b>:
 +
 +
  procedure Server_service_RegisterCalculatorService();
 +
  Begin
 +
    GetServerServiceRegistry().Register(
 +
      'Calculator',
 +
      TCalculator_ServiceBinderFactory.Create() as IItemFactory
 +
    );
 +
  End;
 +
 +
<b>RegisterCalculatorImplementationFactory</b>  located in the calculator_imp unit ( generated by ws_helper ) registers the Calculator implementation by calling in turn <b>GetServiceImplementationRegistry</b>:
 +
 +
  procedure RegisterCalculatorImplementationFactory();
 +
  Begin
 +
    GetServiceImplementationRegistry().Register(
 +
      'Calculator',
 +
      TSimpleItemFactory.Create(TCalculator_ServiceImp) as IitemFactory
 +
    );
 +
  End;
 +
 +
<b>Server_service_RegisterSoapFormat</b> located in the server_service_soap unit ( provided by the  toolkit ) registers the SOAP  implementation by calling in turn <b>GetFormatterRegistry</b>:
 +
 +
  procedure Server_service_RegisterSoapFormat();
 +
  begin
 +
    GetFormatterRegistry().Register(
 +
      sSOAP_CONTENT_TYPE,
 +
      TSimpleItemFactory.Create(TSOAPFormatter) as IitemFactory
 +
    );
 +
    RegisterStdTypes();
 +
  end;
 +
 +
<b>Server_service_RegisterBinaryFormat</b>  located in the server_binary_formatter unit ( provided by the toolkit ) registers the Binary message  implementation by calling in turn <b>GetFormatterRegistry</b>:
 +
 +
  procedure Server_service_RegisterBinaryFormat();
 +
  begin
 +
    GetFormatterRegistry().Register(
 +
      sCONTENT_TYPE,
 +
      TBinaryFormatterFactory.Create() as IitemFactory
 +
    );
 +
  end;
 +
 +
<b>HandleServiceRequest</b> is invoked to process incoming request buffer in the socket client processing procedure <b>ProcessData</b> located in the server_unit unit.
 +
 +
  procedure TTcpSrvApp.ProcessData(Client : TTcpSrvClient);
 +
  Var
 +
    buff, trgt,ctntyp : string;
 +
    rqst : IRequestBuffer;
 +
    wrtr : IDataStore;
 +
    rdr : IDataStoreReader;
 +
    inStream, outStream, bufStream : TMemoryStream;
 +
    i : Integer;
 +
  begin
 +
    inStream := Nil;
 +
    outStream := Nil;
 +
    bufStream := Nil;
 +
    Try
 +
      Client.RequestStream.Position := 0;
 +
      Try
 +
        inStream := TMemoryStream.Create();
 +
        outStream := TMemoryStream.Create();
 +
        bufStream := TMemoryStream.Create();
 +
        rdr := CreateBinaryReader(Client.RequestStream);
 +
        trgt := rdr.ReadStr();
 +
        ctntyp := rdr.ReadStr();
 +
        buff := rdr.ReadStr();
 +
        inStream.Write(buff[1],Length(buff));
 +
        inStream.Position := 0;
 +
        rqst := TRequestBuffer.Create(trgt,ctntyp,inStream,bufStream);
 +
        HandleServiceRequest(rqst);
 +
        i := bufStream.Size;
 +
        SetLength(buff,i);
 +
        bufStream.Position := 0;
 +
        bufStream.Read(buff[1],i);
 +
        wrtr := CreateBinaryWriter(outStream);
 +
        wrtr.WriteStr(buff);
 +
        Client.Send(outStream.Memory,outStream.Size);
 +
      Finally
 +
        bufStream.Free();
 +
        outStream.Free();
 +
        inStream.Free();
 +
        Client.FDataLentgh := -1;
 +
        Client.RequestStream.Size := 0;
 +
      End;
 +
    Except
 +
      On e : Exception Do
 +
        Display('ProcessData()>> Exception = '+e.Message);
 +
    End;
 +
  end;
 +
 +
In order to give it a try one have to :
 +
*compile the server,
 +
*compile the client application,
 +
*execute the server and start listening,
 +
*execute the client.
  
 
==Go further==
 
==Go further==
The complete example is located in the “tests\google_api” folder. It demonstrate use of class and array data types by google searching.  
+
The complete Google example is located in the “tests\google_api” folder. It demonstrate use of class and array data types by google searching. The server sample is located in \tests\tcp_server folder.
The core part of the toolkit is the units service_intf and soap_imp; The first one defines the basic interface while the second is a SOAP implementation( base on the FCL XML units).
+
The toolkit is provide with a SOAP 1.1 message format implementation ( based on the FCL XML units) and a binary one.
  
 
==Status==
 
==Status==
 
The toolkit is usable for simple types and for class types. The serialization is designed to allow customization of basic types and class types by implementing classes derived from “TBaseRemotable”. This classes have to be registered in the type registry.  
 
The toolkit is usable for simple types and for class types. The serialization is designed to allow customization of basic types and class types by implementing classes derived from “TBaseRemotable”. This classes have to be registered in the type registry.  
  
==TODO( near future )==
+
==TODO Client-Side==
*<strike>Basic array support</strike>
+
*<strike>Basic array support</strike> for SOAP
 +
*Basic array support for Binary format
 
*XML-RPC formatter  
 
*XML-RPC formatter  
 
*More documentation and samples !
 
*More documentation and samples !
 +
*WSDL to pascal compiler
 
*Enhance the parser
 
*Enhance the parser
 
** To allow comments in the input file of ws_helper
 
** To allow comments in the input file of ws_helper
  
==TODO==
+
==TODO Server-Side==
 
Extend the toolkit to Server side for :
 
Extend the toolkit to Server side for :
*XML serialization mostly for SOAP over HTTP and XML-RPC
+
*<strike>SOAP</strike>
*Binary serialization
+
*<strike>Binary serialization</strike>
*TCP transport implementation.  
+
*XML-RPC
 +
*<strike>TCP transport</strike> ( first implementation).  
 +
*WSDL generation
 +
*More documentation and samples !
  
 
==Download==
 
==Download==
http://inoussa12.googlepages.com/webservicetoolkitforfpc%26lazarus
+
The lastest version can be found at http://inoussa12.googlepages.com/webservicetoolkitforfpc%26lazarus
  
 
==Author==
 
==Author==
 
Inoussa OUEDRAOGO, http://inoussa12.googlepages.com/
 
Inoussa OUEDRAOGO, http://inoussa12.googlepages.com/

Revision as of 20:02, 21 May 2006

"Web Service Toolkit” is a web services package for FPC and Lazarus; “Web Service Toolkit” is meant to ease web services consumption and creation by FPC and Lazarus users.

Client Side ( service consumption )

Overview

“Web Service Toolkit” is made of two parts, a command line tool “ws_helper” and a collection of support units. Given an interface definition file( describing à web service ), “ws_helper” will create a FPC unit containing a proxy implementing that interface. At runtime when a call targeting the web service is issued, the proxy's role is to :

  • marshall the call paramaters,
  • make the call to the target web service,
  • receive the call return and unmarshall output parameters to the caller.

Behind the scene, the proxy will take care of the SOAP plumbing details.

Example

We will use the “google web api”, freely available for personal use at this adress “http://www.google.com/apis/”.In order to use this service, we have to translate its exposed WSDL interface to Pascal langage. By now “Web Service Toolkit” does not contain a WSDL to pascal compiler, so we will assume this translation available as below ( this is an incomplete translation, but it's enough for the sample ).

 Unit googlewebapi;
 {$mode objfpc}{$H+}
 interface
 uses SysUtils, Classes;
 Type
   IGoogleSearch = Interface
     function doSpellingSuggestion(
       const key:string;
       const phrase:string
     ):string;
   End;
 Implementation
 End.

Invoking “ws_helper” at the prompt with the file “googlewebapi.pas” as argument will produce a file “googlewebapi_proxy.pas” as below.

 Unit googlewebapiimpunit;
 {$mode objfpc}{$H+}
 Interface
 Uses SysUtils, Classes, service_intf, googlewebapi;
 Type
   TGoogleSearch_Proxy=class(TBaseProxy,IGoogleSearch)
     Protected
       function doSpellingSuggestion(
         Const key : string;
         Const phrase : string
       ):string;
   End;
 Implementation
 uses TypInfo;
 { TGoogleSearch_Proxy implementation }
 function TGoogleSearch_Proxy.doSpellingSuggestion(
   Const key : string;
   Const phrase : string
 ):string;
 Var
   locSerializer : IFormatterClient;
   strPrmName : string;
   resTypeInfo : PTypeInfo;
 Begin
   locSerializer := GetSerializer();
   Try
     locSerializer.BeginCall('doSpellingSuggestion', GetTarget());
       locSerializer.Put('key', TypeInfo(string), key);
       locSerializer.Put('phrase', TypeInfo(string), phrase);
     locSerializer.EndCall();
     MakeCall();
     locSerializer.BeginCallRead();
       resTypeInfo := TypeInfo(string);
       If ( resTypeInfo^.Kind in [tkClass,tkObject,tkInterface] ) Then
         Pointer(Result) := Nil;
       strPrmName := 'return';
       locSerializer.Get(TypeInfo(string), strPrmName, result);
   Finally
     locSerializer.Clear();
   End;
 End;
 End.

Then we can build a sample program for the “IGoogleSearch” service(see below). In order to compile this program you must have Indy ( tested with Indy10 ) which can be found at this location “http://www.indyproject.org/Sockets/index.en.iwp” or ICS( tested with the latest ICS-V5 Distribution ) at that adress “http://www.overbyte.be/frame_index.html” as it is used for the HTTP protocol.

 program test_google_api;
 {$mode objfpc}{$H+}
 uses
   Classes, SysUtils,
   base_service_intf, service_intf, soap_formatter,
   //indy_http_protocol,
   ics_http_protocol,
   googlewebapi, googlewebapi_proxy;
 Const
   sADRESS = 'http:Adress=http://api.google.com/search/beta2';
   sTARGET = 'urn:GoogleSearch';
   sKEY    = '<your google key here>';
   sSERVICE_PROTOCOL = 'SOAP';
 Var
   tmpObj : IGoogleSearch;
   strBuffer : string;
 begin
   ICS_RegisterHTTP_Transport();
   WriteLn();
   WriteLn('Enter phrase to spell :');
   ReadLn(strBuffer);
   tmpObj := TGoogleSearch_Proxy.Create(sTARGET,sSERVICE_PROTOCOL,sADRESS);
   Try
     strBuffer := tmpObj.doSpellingSuggestion(sKEY,strBuffer);
     WriteLn('google spell >>> ',strBuffer);
   Except
     On E : Exception Do
       WriteLn(E.Message);
   End;
   ReadLn();
 end.

The units base_service_intf, service_intf, soap_formatter, indy_http_protocol, ics_http_protocol as provided with this toolkit; Below is the result of a execution session spelling for “freepscal lzarus” written with a missing letter 'a' beetwen the letter 'p' and the the letter 's' and a missing letter 'a' beetwen the letter 'L' and the the letter 'z' .

 > .\tests\google_api\test_google_api.exe
 Enter phrase to spell :
 freepscal lzarus
 google spell >>> freepascal lazarus

Google spells it correctly : “freepascal lazarus”!

Server Side ( service creation )

Overview.

Web Service Toolkit contains a server side framework for service creation. Key features are:

  • Service definition( interface ) is separated from implementation,
  • Interface and implementations are not bound to message protocol,
  • Support for SOAP 1.1 and a binary protocol,
  • The framework is not bound to a transport protocol.
  • Easy to add support for application servers

Example

In order to create a service, we have to :

  • define its interface,
  • provide an implementation and register that one for the service,
  • provide a binder that will route calls targeting the service to the implementation and register that one,
  • host the service into an application server( TCP server, HTTP Server, ... ).

Defining the service Interface

We will use the interface defined above for our sample. The complete projects of the example is located in the folder “\tests\tcp_server\calculator”.

 unit calculator;
 {$mode objfpc}{$H+}
 interface
 uses SysUtils, base_service_intf;
 Type
   TBinaryArgsResult = class(TBaseComplexRemotable)
     private
     FArg_A: Integer;
     FArg_B: Integer;
     FArg_OP: string;
     FArg_R: Integer;
   Published
     Property Arg_A : Integer Read FArg_A Write FArg_A;
     Property Arg_B : Integer Read FArg_B Write FArg_B;
     Property Arg_R : Integer Read FArg_R Write FArg_R;
     Property Arg_OP : string Read FArg_OP Write FArg_OP;
   End;
   ICalculator = Interface
     function AddInt(Const A:Integer;Const B:Integer):TBinaryArgsResult;
     function DivInt(Const A:Integer;Const B:Integer):Integer;
   End;
 implementation
 uses base_soap_formatter;
 Initialization
   GetTypeRegistry().Register(sXSD_NS,TypeInfo(TBinaryArgsResult),'TBinaryArgsResult');
 end.

Providing an implementation for the service

“ws_helper” has options to generate proxy file, basic implementation skeleton file and a binder file ( see the listing above).

 ws_helper [-p] [-b] [-i] [-oPATH] inputFilename
   -p  Generate service proxy
   -b  Generate service binder
   -i  Generate service minimal implementation
   -o  PATH  Output directory

Executing “ws_helper” with the -i and -b options as above will produce two files : calculator_imp.pas and calculator_binder.pas.

 ws_helper\ws_helper.exe -i -b -osrv tests\tcp_server\calculator\calculator.pas
 ws_helper Copyright (c) 2006 by Inoussa OUEDRAOGO
 File "tests\tcp_server\calculator\calculator.pas" parsed succesfully.

The calculator_imp.pas unit contains a skeleton implementation class for the interface. It defines a procedure named RegisterCalculatorImplementationFactory. The procedure registers the class as the service implementation provider in the implementation registry.

 Unit calculator_imp;
 {$mode objfpc}{$H+}
 Interface
 Uses SysUtils, Classes, 
      base_service_intf, server_service_intf, server_service_imputils, calculator;
 Type
   TCalculator_ServiceImp=class(TSimpleFactoryItem,ICalculator)
   Protected
     function AddInt(Const A : Integer;Const B : Integer):TBinaryArgsResult;
     function DivInt(Const A : Integer;Const B : Integer):Integer;
   End;
   procedure RegisterCalculatorImplementationFactory();
 Implementation
 { TCalculator_ServiceImp implementation }
 function TCalculator_ServiceImp.AddInt( Const A : Integer; Const B : Integer):TBinaryArgsResult;
 Begin
   Result := TBinaryArgsResult.Create();
   Try
     Result.Arg_OP := '+';
     Result.Arg_A := A;
     Result.Arg_B := B;
     Result.Arg_R := A + B;
   Except
     FreeAndNil(Result);
     Raise;
   End;
 End;
 function TCalculator_ServiceImp.DivInt( Const A : Integer; Const B : Integer):Integer;
 Begin
   Result := A div B;
 End;
 procedure RegisterCalculatorImplementationFactory();
 Begin
   GetServiceImplementationRegistry().Register(
     'Calculator',
     TSimpleItemFactory.Create(TCalculator_ServiceImp) as IItemFactory
   );
 End;
 End.       

Providing a binder for the service.

The binder's role is to:

  • unpack the incoming message,
  • set up the call stack,
  • make the call against the registered implementation,
  • serialize the execution stack to create the return message.

The calculator_binder.pas unit generated by ws_helper, contains :

  • TCalculator_ServiceBinder : the actual binder class,
  • TCalculator_ServiceBinderFactory a factory class for the binder and
  • Server_service_RegisterCalculatorService : the binder factory registration procedure.

The following code extract shows the unit interface part and a method handler of the binder.

 Unit calculator_binder;
 {$mode objfpc}{$H+}
 Interface
 Uses SysUtils, Classes, base_service_intf, server_service_intf, calculator;
 Type
   TCalculator_ServiceBinder=class(TBaseServiceBinder)
   Protected
     procedure AddIntHandler(AFormatter:IFormatterResponse);
     procedure DivIntHandler(AFormatter:IFormatterResponse);
   Public
     constructor Create();
   End;
   TCalculator_ServiceBinderFactory = class(TInterfacedObject,IItemFactory)
   protected
     function CreateInstance():IInterface;
   End;
   procedure Server_service_RegisterCalculatorService();
 Implementation
 uses TypInfo;
 procedure TCalculator_ServiceBinder.AddIntHandler(AFormatter:IFormatterResponse);
 Var
   tmpObj : ICalculator;
   callCtx : ICallContext;
   strPrmName : string;
   procName,trgName : string;
   A : Integer;
   B : Integer;
   returnVal : TBinaryArgsResult;
   locTypeInfo : PTypeInfo;
 Begin
   callCtx := CreateCallContext();
   locTypeInfo := TypeInfo(TBinaryArgsResult);
   If ( locTypeInfo^.Kind in [tkClass,tkInterface] ) Then
     Pointer(returnVal) := Nil;
   strPrmName := 'A';  AFormatter.Get(TypeInfo(Integer),strPrmName,A);
   strPrmName := 'B';  AFormatter.Get(TypeInfo(Integer),strPrmName,B);
   tmpObj := Self.GetFactory().CreateInstance() as ICalculator;
   returnVal := tmpObj.AddInt(A,B);
   locTypeInfo := TypeInfo(TBinaryArgsResult);
   If ( locTypeInfo^.Kind = tkClass ) And Assigned(Pointer(returnVal)) Then
     callCtx.AddObject(TObject(returnVal));
   procName := AFormatter.GetCallProcedureName();
   trgName := AFormatter.GetCallTarget();
   AFormatter.Clear();
   AFormatter.BeginCallResponse(procName,trgName);
     AFormatter.Put('return',TypeInfo(TBinaryArgsResult),returnVal);
   AFormatter.EndCallResponse();
   callCtx := Nil;
 End;

Host the service into an application server.

The application server's role is to route incoming service requests to the Web Service Toolkit runtime. For the runtime to process service requests :

  • The services and their implementations have to be registered ,
  • The message protocol (SOAP, binary,...) have to be registered.

The runtime interface is defined in the server_service_intf unit. This unit contains :

  • GetServerServiceRegistry, which returns the service registry,
  • GetServiceImplementationRegistry which returns the service implementation registry,
  • GetFormatterRegistry which returns the message format registry and
  • HandleServiceRequest which is the unique entry point for request processing.

The toolkit is provided with a simple TCP server hosting the sample Calculator service defined early in this document located in the "\tests\tcp_server folder". The complete source files of the sample is in that directory and the client application in the "\client" sub folder. The registrations are done in the application's main form OnCreate event as printed above:

 procedure TfMain.FormCreate(Sender: TObject);
 begin
   Server_service_RegisterCalculatorService();
   RegisterCalculatorImplementationFactory();
   Server_service_RegisterSoapFormat();
   Server_service_RegisterBinaryFormat();
 end; 

Server_service_RegisterCalculatorService located in the calculator_binder unit ( generated by ws_helper ) registers the Calculator service by calling in turn GetServerServiceRegistry:

 procedure Server_service_RegisterCalculatorService();
 Begin
   GetServerServiceRegistry().Register(
     'Calculator',
     TCalculator_ServiceBinderFactory.Create() as IItemFactory
   );
 End;

RegisterCalculatorImplementationFactory located in the calculator_imp unit ( generated by ws_helper ) registers the Calculator implementation by calling in turn GetServiceImplementationRegistry:

 procedure RegisterCalculatorImplementationFactory();
 Begin
   GetServiceImplementationRegistry().Register(
     'Calculator',
     TSimpleItemFactory.Create(TCalculator_ServiceImp) as IitemFactory
   );
 End;

Server_service_RegisterSoapFormat located in the server_service_soap unit ( provided by the toolkit ) registers the SOAP implementation by calling in turn GetFormatterRegistry:

 procedure Server_service_RegisterSoapFormat();
 begin
   GetFormatterRegistry().Register(
     sSOAP_CONTENT_TYPE,
     TSimpleItemFactory.Create(TSOAPFormatter) as IitemFactory
   );
   RegisterStdTypes();
 end;

Server_service_RegisterBinaryFormat located in the server_binary_formatter unit ( provided by the toolkit ) registers the Binary message implementation by calling in turn GetFormatterRegistry:

 procedure Server_service_RegisterBinaryFormat();
 begin
   GetFormatterRegistry().Register(
     sCONTENT_TYPE,
     TBinaryFormatterFactory.Create() as IitemFactory
   );
 end;

HandleServiceRequest is invoked to process incoming request buffer in the socket client processing procedure ProcessData located in the server_unit unit.

 procedure TTcpSrvApp.ProcessData(Client : TTcpSrvClient);
 Var
   buff, trgt,ctntyp : string;
   rqst : IRequestBuffer;
   wrtr : IDataStore;
   rdr : IDataStoreReader;
   inStream, outStream, bufStream : TMemoryStream;
   i : Integer;
 begin
   inStream := Nil;
   outStream := Nil;
   bufStream := Nil;
   Try
     Client.RequestStream.Position := 0;
     Try
       inStream := TMemoryStream.Create();
       outStream := TMemoryStream.Create();
       bufStream := TMemoryStream.Create();
       rdr := CreateBinaryReader(Client.RequestStream);
       trgt := rdr.ReadStr();
       ctntyp := rdr.ReadStr();
       buff := rdr.ReadStr();
       inStream.Write(buff[1],Length(buff));
       inStream.Position := 0;
       rqst := TRequestBuffer.Create(trgt,ctntyp,inStream,bufStream);
       HandleServiceRequest(rqst);
       i := bufStream.Size;
       SetLength(buff,i);
       bufStream.Position := 0;
       bufStream.Read(buff[1],i);
       wrtr := CreateBinaryWriter(outStream);
       wrtr.WriteStr(buff);
       Client.Send(outStream.Memory,outStream.Size);
     Finally
       bufStream.Free();
       outStream.Free();
       inStream.Free();
       Client.FDataLentgh := -1;
       Client.RequestStream.Size := 0;
     End;
   Except
     On e : Exception Do
       Display('ProcessData()>> Exception = '+e.Message);
   End;
 end;

In order to give it a try one have to :

  • compile the server,
  • compile the client application,
  • execute the server and start listening,
  • execute the client.

Go further

The complete Google example is located in the “tests\google_api” folder. It demonstrate use of class and array data types by google searching. The server sample is located in \tests\tcp_server folder. The toolkit is provide with a SOAP 1.1 message format implementation ( based on the FCL XML units) and a binary one.

Status

The toolkit is usable for simple types and for class types. The serialization is designed to allow customization of basic types and class types by implementing classes derived from “TBaseRemotable”. This classes have to be registered in the type registry.

TODO Client-Side

  • Basic array support for SOAP
  • Basic array support for Binary format
  • XML-RPC formatter
  • More documentation and samples !
  • WSDL to pascal compiler
  • Enhance the parser
    • To allow comments in the input file of ws_helper

TODO Server-Side

Extend the toolkit to Server side for :

  • SOAP
  • Binary serialization
  • XML-RPC
  • TCP transport ( first implementation).
  • WSDL generation
  • More documentation and samples !

Download

The lastest version can be found at http://inoussa12.googlepages.com/webservicetoolkitforfpc%26lazarus

Author

Inoussa OUEDRAOGO, http://inoussa12.googlepages.com/