Cópia da wikiespelho

Este é um espelho da wiki em pascal.chat. A edição, o histórico e a entrada ficam lá.

Web Service Toolkit/pt

English · Français · Português · Русский

"Web Service Toolkit” é um pacote de serviços Web para FPC e Lazarus. “Web Service Toolkit” significa facilidade de criação e uso de serviços Web pelos usuários do FPC e do Lazarus.

A Aplicação-Cliente ( uso do serviço )

Visão Geral

O “Web Service Toolkit” é feito de duas partes, uma ferramenta de linha de comando “ws_helper” e uma coleção de unidades de suporte. Dado um arquivo de definição de interface (um arquivo WSDL ou um arquivo Pascal descrevendo o serviço Web), “ws_helper” vai criar uma unidade FPC contendo um proxy implementando esta inteface. Em tempo de execução, quando uma chamada ao serviço Web for executada, a ação do proxy é para

Atrás da cena, o proxy cuidará dos detalhes do SOAP plumbing.

Exemplo

Nós vamos usar o “Amazon E-Commerce Service” disponível livremente (você vai precisar criar uma conta, é free) para uso pessoal neste endereço 1(http://www.amazon.com/gp/browse.html?node=3435361). Para usar este serviço, temos que baixar a interface WSDL (Web Services Description Language) e traduzi-la para a linguagem Pascal. O programa ws_helper manejará isto para nós. Para apresentar suas capacidades, estão abaixo os argumentos de linha de comando que ele suporta.

pascal
ws_helper [-uMODE] [-p] [-b] [-i] [-oPATH] inputFilename
        -u MODE Generate the pascal translation of the WSDL input file
           MODE value may be U for used types or A for all types
        -p  Generate service proxy
        -b  Generate service binder
        -i  Generate service minimal implementation
        -o  PATH  Relative output directory
        -a  PATH  Absolute output directory

Para traduzir o arquivoo WSDL baixado do website do “Amazon E-Commerce Service”, execute o seguinte da linha de comando:

pascal
..\..\ws_helper\ws_helper.exe -uA -p -o. AWSECommerceService.wsdl
      ws_helper, Web Service Toolkit 0.4 Copyright (c) 2006, 2007 by Inoussa OUEDRAOGO
    
      Parsing the file??: AWSECommerceService.wsdl
      Interface file generation...
      Proxy file generation...
      Metadata file generation...
      File "AWSECommerceService.wsdl" parsed succesfully.

Vão-se produzir dois arquivos:

Abaixo está um extrato dos dois arquivos:

pascal
unit AWSECommerceService;
    {$IFDEF FPC} {$mode objfpc}{$H+} {$ENDIF}
    interface
    
    uses SysUtils, Classes, TypInfo, base_service_intf, service_intf;
    
    const
      sNAME_SPACE = 'http://webservices.amazon.com/AWSECommerceService/2007-04-04';
      sUNIT_NAME = 'AWSECommerceService';
    
    type
    
      HelpRequest = class;
      Help_RequestArray = class;
    
    (...)
    
      AWSECommerceServicePortType = interface(IInvokable)
        ['{305A7E48-DD92-4C20-B699-4F2B47C93342}']
        function Help(
          Const HelpParam : Help_Type
        ):HelpResponse_Type;
        function ItemSearch(
          Const ItemSearchParam : ItemSearch_Type
        ):ItemSearchResponse_Type;
        function ItemLookup(
          Const ItemLookupParam : ItemLookup_Type
        ):ItemLookupResponse_Type;
        (...)
      end;
    
      procedure Register_AWSECommerceService_ServiceMetadata();
pascal
Unit AWSECommerceService_proxy;
    {$IFDEF FPC} {$mode objfpc}{$H+} {$ENDIF}
    Interface
    
    Uses SysUtils, Classes, TypInfo, base_service_intf, service_intf, AWSECommerceService;
    
    Type
    
      TAWSECommerceServicePortType_Proxy=class(TBaseProxy,AWSECommerceServicePortType)
      Protected
        class function GetServiceType() : PTypeInfo;override;
        function Help(
          Const HelpParam : Help_Type
        ):HelpResponse_Type;
        function ItemSearch(
          Const ItemSearchParam : ItemSearch_Type
        ):ItemSearchResponse_Type;
        function ItemLookup(
          Const ItemLookupParam : ItemLookup_Type
        ):ItemLookupResponse_Type;   
        (...)
      End;
    
      Function wst_CreateInstance_AWSECommerceServicePortType(
        const AFormat : string = 'SOAP:'; 
        const ATransport : string = 'HTTP:'
      ):AWSECommerceServicePortType;
    
    Implementation
    uses wst_resources_imp, metadata_repository;
    
    
    Function wst_CreateInstance_AWSECommerceServicePortType(
      const AFormat : string; 
      const ATransport : string
    ):AWSECommerceServicePortType;
    Begin
      Result := TAWSECommerceServicePortType_Proxy.Create(
        'AWSECommerceServicePortType',
         AFormat + 
           GetServiceDefaultFormatProperties(TypeInfo(AWSECommerceServicePortType)),
         ATransport + 'address=' + GetServiceDefaultAddress(TypeInfo(AWSECommerceServicePortType))
      );
    End;
    
    function TAWSECommerceServicePortType_Proxy.ItemSearch(
      Const ItemSearchParam : ItemSearch_Type
    ):ItemSearchResponse_Type;
    Var
      locSerializer : IFormatterClient;
      strPrmName : string;
    Begin
      locSerializer := GetSerializer();
      Try
        locSerializer.BeginCall('ItemSearch', GetTarget(),(Self as ICallContext));
          locSerializer.Put('ItemSearch', TypeInfo(ItemSearch_Type), ItemSearchParam);
        locSerializer.EndCall();
    
        MakeCall();
    
        locSerializer.BeginCallRead((Self as ICallContext));
          TObject(Result) := Nil;
          strPrmName := 'ItemSearchResponse';
          locSerializer.Get(TypeInfo(ItemSearchResponse_Type), strPrmName, Result);
    
      Finally
        locSerializer.Clear();
      End;
    End;
    (...)

Nós agora podemos construir um programa simples para o serviço. A biblioteca Synapse é requerida para compilar o programa como ele é usado para a comunicação HTTP. Esta biblioteca pode ser baixada livre de taxa neste endereço: 2(http://synapse.ararat.cz/). A biblioteca Indy 3(http://www.indyproject.org/Sockets/fpc/index.en.html) ou a biblioteca ICS 4(http://www.overbyte.be/frame_index.html) também podem ser usadas.

pascal
program amazon_sample;
    {$mode objfpc}{$H+}
    uses
      Classes, SysUtils, soap_formatter, synapse_http_protocol,
      metadata_repository, AWSECommerceService, AWSECommerceService_proxy;
    
    const sACCES_ID = <Your AccesID here>;
    
    function ReadEntry(const APromp : string):string ;
    begin
      Result := '';
      Write(APromp);
      while True do begin
        ReadLn(Result);
        Result := Trim(Result);
        if ( Length(Result) > 0 ) then
          Break;
      end;
    end;
    
    var
      locService : AWSECommerceServicePortType;
      rqst : ItemSearch_Type;
      rsps : ItemSearchResponse_Type;
      rspsItem : Items_Type;
      i, j, k : Integer;
      itm : Item_Type;
    begin
      SYNAPSE_RegisterHTTP_Transport();
      WriteLn('Web Services Toolkit Amazon sample');
      WriteLn('This sample demonstrates the "ItemSearch" method of the Amazon web service');
      WriteLn();
      rqst := ItemSearch_Type.Create();
      try
        locService := wst_CreateInstance_AWSECommerceServicePortType();
        rqst.AWSAccessKeyId := sACCES_ID;
        while True do begin
          rqst.Request.SetLength(1);
          rqst.Request[0].SearchIndex := ReadEntry('Enter the Search Index : ');
          rqst.Request[0].Availability := Available;
          rqst.Request[0].Count := 10;
          rqst.Request[0].MerchantId := 'Amazon';
          rqst.Request[0].ItemPage := 1;
          rqst.Request[0].Keywords := ReadEntry('Enter the Keywords : ');
          rsps := locService.ItemSearch(rqst);
          if ( rsps.OperationRequest.Errors.Length > 0 ) then begin
            WriteLn(Format('Errors ( %d ) : ',[rsps.OperationRequest.Errors.Length]));
            for i := 0 to Pred(rsps.OperationRequest.Errors.Length) do begin
              WriteLn(Format('  Error[%d] :',[i]));
              WriteLn('    ' + rsps.OperationRequest.Errors[i].Code);
              WriteLn('    ' + rsps.OperationRequest.Errors[i].Message);
            end;
          end else begin
            WriteLn(Format('Response ( %d ) : ',[rsps.Items.Length]));
            if Assigned(rsps) then begin
              for i := 0 to Pred(rsps.Items.Length) do begin
                rspsItem := rsps.Items[i];
                WriteLn('    TotalPages :' + IntToStr(rspsItem.TotalPages));
                WriteLn('    TotalResults :' + IntToStr(rspsItem.TotalResults));
                WriteLn('    Items :' + IntToStr(rspsItem._Item.Length));
                WriteLn('');
                for j := 0 to Pred(rspsItem._Item.Length) do begin
                  itm := rspsItem._Item[j];;
                  WriteLn('        ASIN :' + itm.ASIN);
                  WriteLn('        DetailPageURL :' + itm.DetailPageURL);
                  if Assigned(itm.ItemAttributes) then begin
                    WriteLn('               Title :' + itm.ItemAttributes.Title);
                    for k := 0 to Pred(itm.ItemAttributes.Author.Length) do begin
                      WriteLn('               Author[ ' + IntToStr(k) + ' ] ' + itm.ItemAttributes.Author.Item[k]);
                    end;
                    WriteLn('               Manufacturer :' + itm.ItemAttributes.Manufacturer);
                    WriteLn('               ProductGroup :' + itm.ItemAttributes.ProductGroup);
                  end;
                  WriteLn('');
                end;
              end;
            end else begin
              WriteLn('Unexpected service response : Invalid response');
            end;
          end;
          WriteLn();
          WriteLn();
          if ( UpperCase(ReadEntry('Continue ( Y/N ) :'))[1] <> 'Y' ) then
            Break;
        end;
      finally
        FreeAndNil(rqst);
        FreeAndNil(rsps);
      end;
      ReadLn;
    end.

As unidades base_service_intf, service_intf, soap_formatter, synapse_http_protocol, wst_resources_imp e metadata_repository são fornecidas com o toolkit. Abaixo está o resultado da execução da sessão de busca de “Freepascal” no índice de busca “All” (o serviço diferencia maiúscula/minúscula).

pascal
Web Services Toolkit Amazon sample
    This sample demonstrates the "ItemSearch" method of the Amazon web service
    
    Enter the Search Index??: All
    Enter the Keywords??: Freepascal
    Response ( 1 )??:
        TotalPages :1
        TotalResults :9
        Items :9
    
            ASIN :0470088702
            DetailPageURL??: http://www.amazon.com/gp/redirect.html%3FASIN=0470088702%26tag=ws%26lcode=sp1%26cID=2025%26ccmID=165953%26location=/o/ASIN/0470088702%253FSubscriptionId=0W3H25JMMGBNBXSTQN82
                   Title :Beginning Programming For Dummies (Beginning Programming f
    or Dummies)
                   Author[ 0 ] Wallace Wang
                   Manufacturer :For Dummies
                   ProductGroup :Book
    
            ASIN :0471375233
            DetailPageURL??: http://www.amazon.com/gp/redirect.html%3FASIN=0471375233%26tag=ws%26lcode=sp1%26cID=2025%26ccmID=165953%26location=/o/ASIN/0471375233%253FSubscriptionId=0W3H25JMMGBNBXSTQN82
                   Title :Assembly Language Step-by-step: Programming with DOS and Linux (with CD-ROM)
                   Author[ 0 ] Jeff Duntemann
                   Manufacturer :Wiley
                   ProductGroup :Book
    (...)

Isto encontra uma página contendo nove ítens. A função wst_CreateInstance_AWSECommerceServicePortType(), localizada no arquivo AWSECommerceService.pas cria uma instância do proxy baseada na informação do serviço contida no arquivo WSDL.

pascal
Function wst_CreateInstance_AWSECommerceServicePortType(
      const AFormat    : string; 
      const ATransport : string
    ):AWSECommerceServicePortType;
    Begin
      Result := TAWSECommerceServicePortType_Proxy.Create(
                  'AWSECommerceServicePortType',
                  Aformat + GetServiceDefaultFormatProperties(TypeInfo(AWSECommerceServicePortType)),
                  ATransport + 'address=' + GetServiceDefaultAddress(TypeInfo(AWSECommerceServicePortType))
                );
    End;
pascal
Os fontes completos deste exemplo s??o entregues com o toolkit.

Parâmetros de Connexão

O formato geral é:

pascal
protocol:paramName=paramValue(;paramName=paramValue)*

Parâmetros do Proxy HTTP

Para HTTP os parâmetros suportados são:

HTTP Connection through proxy are supported. Below is an example address string.

pascal
const
        sADDRESS = 'http:address=http://webservices.amazon.com/AWSECommerceService/2007-04-04'+
                     ';ProxyServer=197.150.10.10;ProxyPort=9881'+
                     ';ProxyUsername=inoussa;ProxyPassword=wst';

The toolkit has two TCP implementations based on Synapse ( synapse_tcp_protocol.pas ) and ICS ( ics_tcp_protocol.pas ).

Parâmetros da Conexão TCP

Os parâmetros de TCP suportados são:

Abaixo está um exemplo de uma string de endereço.

pascal
Const
        sADDRESS = 'TCP:Address=10.0.0.3;Port=1234;target=UserService';

The toolkit has two TCP implementations based on Synapse ( synapse_tcp_protocol.pas ) and ICS ( ics_tcp_protocol.pas ).

Parâmetros de Conexão LIBRARY ( LIB )

A idéia por trás deste protocolo é poder hospedar serviços em bibliotecas dinâmicas (DLL/DSO). Isto pode ser visto como um framework de plug-in em que plug-ins (serviços) são fornecidos por bibliotecas dinâmicas. Os parâmetros para LIB suportados são:

Abaixo um exemplo de uma string de endereço.

pascal
Const
        sADDRESS = 'LIB:FileName=..\library_server\lib_server.dll;target=UserService';

The toolkit has one LIB implementation ( library_protocol.pas ).

As pastas sample contêm 3 projetos, user_client_console, tcp_server e library_server que demonstra os protocolos TCP e LIBRARY.

Serviço Multi-Endereço ( Endereço por operação )

Certos serviços (como os serviços do eBay SOAP) usam um endereço por operação. O toolkit usa meta-dados estendidos (veja o capítulo sobre serviços de meta-dados abaixo) para configurar endereço de operação. O sample SOAP do eBay localizado na pasta test\eBay demonstra a configuração do endereço da operação.

Server Side ( service creation )

Overview

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

Example

In order to create a service, we have to :

Defining the service Interface

We will use the interface defined below for our sample. The complete projects of the example is located in the folder “samples”.

pascal
unit user_service_intf;
    {$IFDEF FPC} {$mode objfpc}{$H+} {$ENDIF}
    interface
    
    uses SysUtils, Classes, TypInfo, base_service_intf, service_intf;
    
    const
      sNAME_SPACE = 'urn:UserService';
      sUNIT_NAME = 'user_service_intf';
    
    type
    
      TUser = class;
      TUserArray = class;
    
      TUserCategory = ( 
        Normal
        ,Admin
      );
    
      TUser = class(TBaseComplexRemotable)
      private
        FCategory : TUserCategory;
        FUserName : string;
        FeMail : string;
        FPreferences : string;
      published
        property Category : TUserCategory read FCategory write FCategory;
        property UserName : string read FUserName write FUserName;
        property eMail : string read FeMail write FeMail;
        property Preferences : string read FPreferences write FPreferences;
      end;
    
      TUserArray = class(TBaseObjectArrayRemotable)
      private
        function GetItem(AIndex: Integer): TUser;
      public
        class function GetItemClass():TBaseRemotableClass;override;
        property Item[AIndex:Integer] : TUser Read GetItem;Default;
      end;
    
      UserService = interface(IInvokable)
        ['{CA6F6192-C3DE-4D9C-B3DF-E616376A0DC9}']
        function GetList():TUserArray;
        procedure Add(
          Const AUser : TUser
        );
        procedure Update(
          Const AUser : TUser
        );
        function Find(
          Const AName : string
        ):TUser;
        function Delete(
          Const AName : string
        ):boolean;
      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 following listing).

pascal
ws_helper, Web Service Toolkit 0.4 Copyright (c) 2006, 2007 by Inoussa OUEDRAOGO
    ws_helper [-uMODE] [-p] [-b] [-i] [-oPATH] inputFilename
      -u MODE Generate the pascal translation of the WSDL input file
           MODE value may be U for used types or A for all types
      -p  Generate service proxy
      -b  Generate service binder
      -i  Generate service minimal implementation
      -o  PATH  Relative output directory
      -a  PATH  Absolute output directory

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

pascal
ws_helper\ws_helper.exe -i -b -o. user_service_intf.pas
    ws_helper, Web Service Toolkit 0.4 Copyright (c) 2006, 2007 by Inoussa OUEDRAOGO
    Parsing the file??: user_service_intf.pas
    Proxy file generation...
    Binder file generation...
    Implementation file generation...
    Metadata file generation...
    File "user_service_intf.pas" parsed succesfully..

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

pascal
Unit user_service_intf_imp;
    {$IFDEF FPC} {$mode objfpc}{$H+} {$ENDIF}
    Interface
    Uses SysUtils, Classes, 
         base_service_intf, server_service_intf, server_service_imputils,
         user_service_intf, cursor_intf;
    
    Type
      { TUserService_ServiceImp }
    
      TUserService_ServiceImp=class(TBaseServiceImplementation,UserService)
      Protected
        function GetList():TUserArray;
        procedure Add(
          Const AUser : TUser
        );
        procedure Update(
          Const AUser : TUser
        );
        function Find(
          Const AName : string
        ):TUser;
        function Delete(
          Const AName : string
        ):boolean;
      End;
    
      procedure RegisterUserServiceImplementationFactory();
    
    Implementation
    (...)
    
    procedure TUserService_ServiceImp.Add(Const AUser : TUser);
    var
      locObj : TUser;
    Begin
      locObj := Find(AUser.UserName);
      if ( locObj <> nil ) then
        raise Exception.CreateFmt('Duplicated user : "%s"',[AUser.UserName]);
      locObj := TUser.Create();
      locObj.Assign(AUser);
      FUserList.Add(locObj);
    End; 
    
    procedure RegisterUserServiceImplementationFactory();
    Begin
      GetServiceImplementationRegistry().Register(
        'UserService',
    TImplementationFactory.Create(TUserService_ServiceImp) as IServiceImplementationFactory);
    End;
    
    (...)

Providing a binder for the service.

The binder's role is to:

The user_service_intf_binder.pas unit generated by ws_helper, contains :

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

pascal
unit user_service_intf_binder;
    {$IFDEF FPC} {$mode objfpc}{$H+} {$ENDIF}
    interface
    uses SysUtils, Classes, base_service_intf, server_service_intf, user_service_intf;
    
    type
      TUserService_ServiceBinder=class(TBaseServiceBinder)
      Protected
        procedure GetListHandler(AFormatter:IFormatterResponse);
        procedure AddHandler(AFormatter:IFormatterResponse);
        procedure UpdateHandler(AFormatter:IFormatterResponse);
        procedure FindHandler(AFormatter:IFormatterResponse);
        procedure DeleteHandler(AFormatter:IFormatterResponse);
      Public
        constructor Create();
      End;
    
      TUserService_ServiceBinderFactory = class(TInterfacedObject,IItemFactory)
      protected
        function CreateInstance():IInterface;
      End;
    
      procedure Server_service_RegisterUserServiceService();
    
    (...)
    
    procedure TUserService_ServiceBinder.AddHandler(AFormatter:IFormatterResponse);
    Var
      cllCntrl : ICallControl;
      tmpObj : UserService;
      callCtx : ICallContext;
      strPrmName : string;
      procName,trgName : string;
      AUser : TUser;
    Begin
      callCtx := GetCallContext();
      TObject(AUser) := Nil;
      
      strPrmName := 'AUser';  AFormatter.Get(TypeInfo(TUser),strPrmName,AUser);
      If Assigned(Pointer(AUser)) Then
        callCtx.AddObjectToFree(TObject(AUser));
      
      tmpObj := Self.GetFactory().CreateInstance() as UserService;
      if Supports(tmpObj,ICallControl,cllCntrl) then
        cllCntrl.SetCallContext(GetCallContext());
      
      tmpObj.Add(AUser);
      
      procName := AFormatter.GetCallProcedureName();
      trgName := AFormatter.GetCallTarget();
      AFormatter.Clear();
      AFormatter.BeginCallResponse(procName,trgName);
      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 runtime interface is defined in the server_service_intf unit. This unit contains :

The toolkit is provided with a simple TCP server ( using Synapse components ) hosting the sample UserService service defined early in this document and located in the \samples\tcp_server folder. The complete source files of the sample is in that directory and the client application in the \samples\user_client_console sub folder. The registrations are done in the application main routine as printed above:

pascal
program tcp_server;
    {$INCLUDE wst.inc}
    uses
      Classes, SysUtils,
      base_service_intf, server_service_soap,
      base_binary_formatter, server_binary_formatter,
      metadata_service, metadata_service_imp, metadata_service_binder,
      synapse_tcp_server,
      user_service_intf, user_service_intf_binder, user_service_intf_imp , imp_helper;
    var
      listnerThread : TServerListnerThread;
    begin
      SetLogger(TConsoleLogger.Create());
      
      Server_service_RegisterBinaryFormat();
      Server_service_RegisterSoapFormat();
      Server_service_RegisterWSTMetadataServiceService();
      RegisterWSTMetadataServiceImplementationFactory();
      
      Server_service_RegisterUserServiceService();
      RegisterUserServiceImplementationFactory();
      
      Logger().Log('WST sample TCP Server listning on "%s"',[sSERVER_PORT]);
      Logger().Log('Hit <enter> to stop.');
      listnerThread := TServerListnerThread.Create();
      ReadLn;
    end.

Server_service_RegisterUserServiceService located in the user_service_intf_binder unit ( generated by ws_helper ) registers the UserService service by calling in turn GetServerServiceRegistry :

pascal
procedure Server_service_RegisterUserServiceService();
    Begin
      GetServerServiceRegistry().Register(
        'UserService',TUserService_ServiceBinderFactory.Create() 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 :

pascal
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 :

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

Incoming requests are processed in the client thread TClientHandlerThread.Execute() method (located in the synapse_tcp_server.pas file) invoking the HandleServiceRequest() method.

pascal
procedure TClientHandlerThread.Execute();
    var
      wrtr : IDataStore;
      rdr : IDataStoreReader;
      buff, trgt,ctntyp : string;
      rqst : IRequestBuffer;
      i : PtrUInt;
    begin
      FInputStream := TMemoryStream.Create();
      FOutputStream := TMemoryStream.Create();
      FSocketObject := TTCPBlockSocket.Create();
      try
        FSocketObject.RaiseExcept := True;
        try
          FSocketObject.Socket := FSocketHandle;
          FSocketObject.GetSins();
          while not Terminated do begin
            FOutputStream.Size := 0;
            if ( ReadInputBuffer() >= SizeOf(LongInt) ) then begin
              rdr := CreateBinaryReader(FInputStream);
              trgt := rdr.ReadStr();
              ctntyp := rdr.ReadStr();
              buff := rdr.ReadStr();
              rdr := nil;
              FInputStream.Size := 0;
              FInputStream.Write(buff[1],Length(buff));
              FInputStream.Position := 0;
              rqst := TRequestBuffer.Create(trgt,ctntyp,FInputStream,FOutputStream);
              HandleServiceRequest(rqst);
              i := FOutputStream.Size;
              SetLength(buff,i);
              FOutputStream.Position := 0;
              FOutputStream.Read(buff[1],i);
              FOutputStream.Size := 0;
              wrtr := CreateBinaryWriter(FOutputStream);
              wrtr.WriteStr(buff);
              SendOutputBuffer();
              ClearBuffers();
            end;
          end;
        except
          on e : Exception do begin
            Logger().Log('Error : ThreadID = %d; Message = %s',[Self.ThreadID,e.Message]);
          end;
        end;
      finally
        FreeAndNil(FSocketObject);
      end;
    end;

In order to give it a try one have to :

WSDL generation

Services in the toolkit are organized into meta data repositories. Conceptually a repository corresponds :

The repository is the toolkit WSDL generation unit.

The Metadata Service

The toolkit is provided with an easy to use metadata service implementation which in turn uses the raw interface defined in the metadata_repository unit (see above). A Lazarus GUI client application is located in the tests\metadata_browser folder.

WSDL generation API

The metadata_wsdl pascal unit contains the GenerateWSDL function for WSDL generation from a repository (see the signature below).

pascal
PServiceRepository = ^TServiceRepository;
      TServiceRepository = record
        NameSpace        : ShortString;
        Name             : ShortString;
        RootAddress      : ShortString;
        ServicesCount    : Byte;
        Services         : PService;
      end;
    
      procedure GenerateWSDL(AMdtdRep : PServiceRepository; ADoc : TDOMDocument);
WSDL Customization

The WSDL generation is based on the IWsdlTypeHandler and the IWsdlTypeHandlerRegistry interfaces located in the metadata_wsdl unit. In order to customize the generated WSDL, one has to provide a class implementing the IWsdlTypeHandler interface. Then that class has to be registered in the registry. The metadata_wsdl unit contains implementations for pascal enumerations, TBaseComplexRemotable descendants, and TBaseArrayRemotable descendants.

Sample

A functional sample project is located under \samples\http_server . It is an Indy base http server.

Services Extensions

Services extensions provide a mean to hook into all the services request processing stages. Services extensions may be used, for example, to implement authentication, request logging, data compression, etc. The IServiceExtension bellow is the interface used by the toolkit runtime to call services extensions.

pascal
TMessageStage = (
        msAfterDeserialize, msAfterSerialize, msBeforeDeserialize, msBeforeSerialize
      );
    
      IServiceExtension = interface
        ['{E192E6B3-7932-4D44-A8AC-135D7A0B8C93}']
        procedure ProcessMessage(
          const AMessageStage     : TMessageStage;
                   ACallContext   : ICallContext;
                   AMsgData       : IInterface
        );
      end;

The AMsgData parameter actual type depends on the message processing state and corresponds to :

These types are located in the server_service_intf unit. Extensions have to be registered in the extensions registry ( located in the server_service_intf unit ) printed bellow

pascal
IServiceExtensionRegistry = Interface
        ['{68DC78F1-E6CF-4D6B-8473-75288794769C}']
        function Find(const AName : string):IServiceExtension;
        procedure Register(
          const AName    : string;
                AFactory : IItemFactory
        );
      end;

In order for an service implementation to use a service extension, it has to register himself to that extension. To that end, the IServiceImplementationFactory interface provides the RegisterExtension method. A complete sample is included in the \samples\http_server sample ( implemented in \samples\logger_extension.pas ).

Services meta data

Services in the toolkit are organized into meta data repositories( see the “services's meta data” below ). Conceptually a repository corresponds :

The ws_helper tool, when parsing the interface definition file, records the meta data of the services contained in the file to a Lazarus resource file. The resource file is then embedded into the generated binder's unit file( see the unit “initialization” part ). At runtime the service's recorded meta data are accessible through the interface IModuleMetadataMngr defined in the metadata_repository unit ( see below ). The GetModuleMetadataMngr function defined in the same unit returns an instance of an object supporting that interface.

pascal
IModuleMetadataMngr = interface
        ['{B10ACF6A-A599-45A3-B083-BEEFB810C889}']
        function IndexOfName(const ARepName : shortstring):Integer;
        function GetCount():Integer;
        function GetRepositoryName(const AIndex : Integer):shortstring;
        procedure SetRepositoryNameSpace(const ARepName,ANameSpace : shortstring);
        function LoadRepositoryName(
          const ARepName,ARootAddress  : shortstring;
          out   ARepository  : PServiceRepository
        ):Integer;
        procedure ClearRepository(var ARepository : PServiceRepository);
        procedure SetServiceCustomData(
          const ARepName       : shortstring;
          const AServiceName   : shortstring;
          const ADataName,
                AData          : string
        );
        procedure SetOperationCustomData(
          const ARepName       : shortstring;
          const AServiceName   : shortstring;
          const AOperationName : shortstring;
          const ADataName,
                AData          : string
        );
        function GetServiceMetadata(const ARepName,AServiceName : shortstring) : PService;
        procedure ClearServiceMetadata(var AService : PService);
      end;

Extended Meta data

The meta data interface provides a way to add custom data to recorded ones. Services's metadata can be set through SetServiceCustomData , operation's metadata be set through the SetOperationCustomData method. A repository's extended meta data has to be registered after the service meta data recorded in the resource file have been registered. So for client application the generated proxy unit contains a conditional code fragment to call a registration procedure like showed below for the eBay sample located in the tests\ebay folder. The procedure name is obtained from the interface unit name ( the repository's name ) : Register_%UNIT_NAME%_ServiceMetadata .

pascal
initialization
        {$i ebay.lrs}
    
        {$IF DECLARED(Register_ebay_ServiceMetadata)}
        Register_ebay_ServiceMetadata();
        {$ENDIF}
      End.

Headers support

The THeaderBlock class

pascal
THeaderBlock = class(TBaseComplexRemotable)
      public
        property Direction : THeaderDirection read FDirection write FDirection;
        property Understood : Boolean read FUnderstood write FUnderstood;
      published
        property mustUnderstand : Integer read FmustUnderstand write SetmustUnderstand 
          stored HasmustUnderstand;
      end;

The THeaderBlock showed above ( the private part has been omitted for brevity), located in the base_service_intf unit, is the root class all header classes are derived from.. The Direction property indicate whether it is an incoming header or an outgoing one. The mustUnderstand property define whether the header is a mandatory one.

Defining header class

Soap headers are derived from the THeaderBlock base class located in the base_service_intf unit. They have to be registered in the type registry. Below is reproduced an header example extracted from the “calculator” sample project.

pascal
TCalcHeader = class(THeaderBlock)
      published
        property Login : string read FLogin write FLogin;
        property Password : string read FPassword write FPassword;
        property WantedPrecision : Integer read FWantedPrecision write FWantedPrecision;
      end;

The ICallContext interface

pascal
ICallContext = Interface
        ['{855EB8E2-0700-45B1-B852-2101023200E0}']
        procedure AddObjectToFree(const AObject : TObject);
        procedure Clear();
        function AddHeader(
          const AHeader        : THeaderBlock;
          const AKeepOwnership : Boolean
        ):Integer;
        function GetHeaderCount(const ADirections : THeaderDirections):Integer;
        function GetHeader(const AIndex : Integer) : THeaderBlock;
        procedure ClearHeaders(const ADirection : THeaderDirection);
      End;

The ICallContext interface defined in the base_service_intf unit represents the service call context. The AddHeader method allows headers sending while the GetHeader method retrieves header in the call context.

Client side headers

An ICallContext reference may be obtained from the current service proxy instance simply by querying it for that interface as showed in the code fragment below extracted from the “calculator” client example project.

pascal
var
        ch : TCalcHeader;
        hdrs : ICallContext;
      begin
        FObj := TCalculator_Proxy.Create('Calculator', edtFormat.Text, edtAddress.Text);
    
        ch := TCalcHeader.Create();
        ch.mustUnderstand := 1;
        ch.Login := 'azerty';
        ch.Password := 'qwerty';
        ch.WantedPrecision := 1210;
        hdrs := FObj as ICallContext;
        hdrs.AddHeader(ch,true);

A header may be made mandatory by setting its mustUnderstand property to 1 as in the code above.

Server side headers

The ICallControl interface

pascal
ICallControl = interface
        ['{7B4B7192-EE96-4B52-92C7-AE855FBC31E7}']
        procedure SetCallContext(ACallContext : ICallContext);
        function GetCallContext():ICallContext;
      end;

The ICallControl interface, located in the server_service_intf unit, is used by the toolkit runtime to share the executing call environment with service implementation classes. When the runtime is about to issue a call against a implementation class instance, it queries that instance for ICallControl interface support; If the implementation has ICallControl interface support then the obtained reference is used to set the call context through the SetCallContext method. The implementation instance can then access the call context by calling the GetCallContex method. The toolkit provides the TBaseServiceImplementation class which has support for the ICallControl interface and can be used as a base implementation class. It is the base class used by the ws_helper generated skeleton implementation class when invoked with the -i command line option. The method printed bellow, extracted from the calculator sample service demonstrates the access to headers for read and write.

pascal
function TCalculator_ServiceImp.AddInt(
        Const A : Integer;
        Const B : Integer
      ):TBinaryArgsResult;
      var
        hdr : TCalcResultHeader;
        h : TCalcHeader;
        cc : ICallContext;
      Begin
        hdr := TCalcResultHeader.Create();
        cc := GetCallContext();
        if Assigned(cc) and ( cc.GetHeaderCount([hdIn]) > 0 ) and ( cc.GetHeader(0).InheritsFrom(TCalcHeader) ) then begin
          h := cc.GetHeader(0) as TCalcHeader;
          h.Understood := True;
          hdr.Assign(h);
        end;
        hdr.TimeStamp := DateTimeToStr(Now());
        hdr.SessionID := 'testSession';
        cc.AddHeader(hdr,True);
        hdr := nil;
        Result := TBinaryArgsResult.Create();
        Try
          Result.Arg_OP := '+';
          Result.Arg_OpEnum := coAdd;
          Result.Arg_A := A;
          Result.Arg_B := B;
          Result.Arg_R := A + B;
          Result.Comment := 'Doing an + operation';
        Except
          FreeAndNil(Result);
          Raise;
        End;
      End;

SOAP Specific

Binding style

The binding style is used to indicate whether the service is RPC oriented or Document oriented.

Client side

The binding style may be specified in the SOAP protocol string on the creation of a service proxy. The default value for the binding style is RPC. Below is printed a sample code that demonstrates the use of Document style.

pascal
locService := TSampleService_Proxy.Create(
                      'SampleService',
                      'SOAP:Style=Document;EncodingStyle=Litteral',
                      'http:Address=http://127.0.0.1/services/SampleService'
                    );

Server side

Currently services created with the toolkit use the RPC binding style.

Encoding style

The encoding style indicates the rules used to encode types in XML. Supported values are Encoded and Litteral.

Client side

The encoding style may be specified in the SOAP protocol string on the creation of a service proxy. The default value for the encoding style is Encoded. The above sample demonstrates the use of Litteral style.

Server side

Currently services created with the toolkit use the Encoded encoding style.

Provided examples

The samples are located under the “tests” folder.

Client side examples ( tested on Windows XP and Ubuntu )

Server side examples

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.

Serialization

The serialization is based on the IFormatterBase interface located in the base_service_intf unit.

The toolkit has two serializers implementations : the SOAP serializer and a Binary serializer.

SOAP serializer

The SOAP serializer implements SOAP 1.1. It has support for the following pascal types:

Binary serializer

The Binary serializer is more efficient in time and space compared to the SOAP serializer . It uses big endian to stream data. This serializer has been tested with Delphi 7 ( \samples\delphi\user_client_console ) It has support for the following pascal types:

Class type serialization

The toolkit has support for instances of classes derived from TBaseRemotable. TBaseRemotable is the abstract base class used by the formatter interface to allow customization of the serialization. The toolkit provides the TBaseComplexRemotable class which implements serialization for its descendants classes published properties. It also provides TBaseObjectArrayRemotable class for serialization of array of TBaseRemotable descendant classes.

The root “TBaseRemotable” class
pascal
TBaseRemotable = class(TPersistent)
      Public
        constructor Create();virtual;
        class procedure Save(
                AObject   : TBaseRemotable;
                AStore    : IFormatterBase;
          Const AName     : String;
          Const ATypeInfo : PTypeInfo
        );virtual;abstract;
        class procedure Load(
          Var   AObject   : TObject;
                AStore    : IFormatterBase;
          var   AName     : String;
          const ATypeInfo : PTypeInfo
        );virtual;abstract;
      End;

TBaseRemotable is the abstract base class used by the formatter interface to allow customization of the serialization. This class defines a virtual constructor and mainly two(2) virtual abstract class methods :

The “TBaseComplexRemotable” serialization
pascal
TBaseComplexRemotable = class(TAbstractComplexRemotable)
      public
        class procedure Save(
                AObject   : TBaseRemotable;
                AStore    : IFormatterBase;
          const AName     : string;
          const ATypeInfo : PTypeInfo
        );override;
        class procedure Load(
          var   AObject   : TObject;
                AStore    : IFormatterBase;
          var   AName     : string;
          const ATypeInfo : PTypeInfo
        );override;
        class procedure RegisterAttributeProperty(const AProperty : shortstring);virtual;
        class procedure RegisterAttributeProperties(const APropertList : array of shortstring);virtual;
        class function IsAttributeProperty(const AProperty : shortstring):Boolean;
        procedure Assign(Source: TPersistent); override;
      end;

TBaseComplexRemotable implements serialization for its descendants classes published properties. The serialization is based on runtime type information (RTTI) and can be customized to:

The following class shows a the serialization's customization sample.

pascal
TSampleClass = class(TBaseComplexRemotable)
      private
        FProp_Always: Integer;
        FProp_Never: Integer;
        FProp_Optional: Integer;
        function GetStoredProp_Optional: boolean;
      published
        //This property will always be serialized
        property Prop_Always : Integer read FProp_Always write FProp_Always;
        //This property will never be serialized
        property Prop_Never : Integer read FProp_Never write FProp_Never stored False;
        //This property will be serialized if "Self.GetStoredProp_Optional() = True"
        property Prop_Optional : Integer read FProp_Optional write FProp_Optional stored GetStoredProp_Optional;
      End;

TBaseComplexRemotable allows properties serialization as attributes. Theses properties have to be registered as such with the RegisterAttributeProperty class method or RegisterAttributeProperties one.

TBaseComplexRemotable

TBaseComplexSimpleContentRemotable provides implementation for the “XML Schema” complex types which extend simple types with attributes. The following example illustrates this :

pascal
<xs:complexType name="DecimalWithUnits">
        <xs:simpleContent>
          <xs:extension base="xs:decimal">
            <xs:attribute name="Units" type="xs:string"
                          use="required"/>
          </xs:extension>
        </xs:simpleContent>
      </xs:complexType>

This type will be translate by ws_helper to Pascal as

pascal
DecimalWithUnits = class(TComplexFloatExtendedContentRemotable)
      private
        FUnits : string;
      published
        property Units : string read FUnits write FUnits;
      end;

using the predefined types ( in base_service_intf.pas )

pascal
TBaseComplexSimpleContentRemotable =
         class(TAbstractComplexRemotable)
      protected
        class procedure SaveValue(
          AObject : TBaseRemotable; 
          AStore : IformatterBase
        );virtual;abstract;
        class procedure LoadValue(
          var AObject : TObject; 
          AStore : IformatterBase
        );virtual;abstract;
      public
        class procedure Save(
                AObject   : TBaseRemotable;
                AStore    : IFormatterBase;
          const AName     : string;
          const ATypeInfo : PTypeInfo
        );override;
        class procedure Load(
          var   AObject   : TObject;
                AStore    : IFormatterBase;
          var   AName     : string;
          const ATypeInfo : PTypeInfo
        );override;
      end;
    
      TComplexFloatExtendedContentRemotable =
         class(TBaseComplexSimpleContentRemotable)
      private
        FValue: Extended;
      protected
        class procedure SaveValue(
          AObject : TBaseRemotable; 
          AStore : IformatterBase
        );override;
        class procedure LoadValue(
           var AObject : TObject; 
           AStore : IformatterBase
        );override;
      public
        property Value : Extended read FValue write FValue;
      end;

An instance of this type looks like the one below. Every attribute must be registered using the RegisterAttributeProperty() method. The toolkit provides class for Pascal basic types( TComplexInt8UContentRemotable, TComplexInt8SContentRemotable, TComplexInt16SContentRemotable, ...).

pascal
<example Units = "meter">
        12.10
      </example>
Provided array implementations

The toolkit provides array implementation for basic types ( in the base_service_intf unit ) listed bellow. The implementations are based on the serialization's customization.

The toolkit array's implementation support “embedded” array serialization. This type of array occurs typically with types like the following one ( the "ResponseGroup" may be repeated ):

pascal
<xs:complexType name="CustomerContentSearchRequest">
        <xs:sequence>
          <xs:element name="CustomerPage" type="xs:positiveInteger"
             minOccurs="0"/>
          <xs:element name="Email" type="xs:string" minOccurs="0"/>
          <xs:element name="Name" type="xs:string" minOccurs="0"/>
          <xs:element name="ResponseGroup" type="xs:string"
               minOccurs="0" maxOccurs="unbounded"/>
        </xs:sequence>
      </xs:complexType>

which could be instantiated as

pascal
<search>
        <CustomerPage> 1 </CustomerPage>
        <Name>Sample name</Name>
        <ResponseGroup>Group 1</ResponseGroup>
        <ResponseGroup>Group 2</ResponseGroup>
        <ResponseGroup>Group 3</ResponseGroup>
      </search>

This type will be translate to Pascal by ws_helper as (the private and protected parts are omitted to be short)

pascal
(...)
      CustomerContentSearchRequest_ResponseGroupArray =
        class(TBaseSimpleTypeArrayRemotable)
      public
        class function GetItemTypeInfo():PTypeInfo;override;
        procedure SetLength(const ANewSize : Integer);override;
        property Item[AIndex:Integer] : string 
          read GetItem write SetItem; default;
      end;
    
      CustomerContentSearchRequest = class(TBaseComplexRemotable)
      published
        property CustomerPage : positiveInteger 
           read FCustomerPage 
           write FCustomerPage stored HasCustomerPage;
        property Email : string 
           read FEmail 
           write FEmail 
           stored HasEmail;
        property Name : string read FName write FName stored HasName;
        property ResponseGroup :
           CustomerContentSearchRequest_ResponseGroupArray
           read FResponseGroup 
           write FResponseGroup;
      end;
    
    implementation
    (...)
      GetTypeRegistry().ItemByTypeInfo[
        TypeInfo(CustomerContentSearchRequest_ResponseGroupArray)]
        .RegisterExternalPropertyName(sARRAY_STYLE,sEmbedded);
    (...)

The last instruction set the array style to “Embedded” and so the SOAP formatter will serialize the array accordingly.

Test cases

The toolkit uses FPCUnit for test cases. The test project is located in the \tests\test_suite folder.

TODO

TODO Common

TODO Client-Side

TODO Server-Side

Extend the toolkit to Server side for :

Licence

Download & NewsGroup

The lastest version can be found at http://inoussa12.googlepages.com/webservicetoolkitforfpc%26lazarus and from Lazarus-CCR sourceforge site.
The "Web Service Toolkit" now has a newsgroup hosted at news://news.dxmachine.com/public.wst

Changes Log

Version 0.4

Version 0.3.1 ( 21 August 2006 )

Version 0.3 ( 5 August 2006 )

Version 0.2.3.2 ( 5 July 2006 )

Version 0.2.3 ( 4 July 2006 )

Version 0.2.2 ( 7 June 2006 )

Author

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


From the Free Pascal wiki, page "Web Service Toolkit/pt", as the Internet Archive kept it on 2026-02-02, last changed on 2023-02-07. Copyright stays with the author who wrote it, according to the old wiki's Licensing page. If you have concerns about this material, message the admin.

Revisão 3, última alteração por WikiArchive .