Você está na página 1de 14

Desvendando Web Services (Soap/XML)

Resumo: Este artigo tem por objetivo dar uma visão geral e completa sobre uma das
melhores novidades do Delphi 6, o suporte a Web Services.

Visão geral

Uma nova geração de tecnologia de desenvolvimento nos é apresentada com o Web


Services. Com ela podemos criar aplicações modulares e independentes que são
distribuídas facilmente em qualquer estrutura de redes TCP/IP, pois esse foi um dos
princípios fundamentais de sua implementação.

Um grande ponto positivo desta tecnologia é que a criação de servidores e clientes


independem da linguagem de programação e do sistema operacional que são
implementados. Atualmente o suporte da Borland se restringe ao Windows, mas já foi
anunciado para um futuro próximo o suporte no Kylix, o que abriria um grande leque
de possibilidades no que diz respeito à implementação de aplicações distribuídas multi-
plataformas.

Os servidores podem descrever seus próprios serviços através da WSDL (Web Service
Definition Language). Dessa forma, os clientes podem facilmente obter informações
sobre os servidores que usarão, tais como: estrutura, métodos e parâmetros exigidos.
Isso se torna essencialmente útil quando se está codificando servidores que serão
usados por terceiros ou implementando clientes que usam serviços de outras
empresas.

No caso particular do Delphi podemos usar um wizard que importa essas informações e
cria automaticamente as units com as definições dos serviços ofertados pelo servidor.

SOAP/XML

A comunicação entre clientes e servidores é feita através do SOAP (Simple Object


Access Protocol). Esse protocolo é definido em XML, sendo assim, as chamadas a
procedures remotas (RPC) são codificadas em XML. Para transporte das mensagens é
usado o HTTP, que além de tornar o SOAP um protocolo leve, elimina inúmeros
problemas de outras tecnologias com proxys, como CORBA, DCOM e etc...

Como foi citado acima, não temos a preocupação de contornar o esquema de


segurança para realizar a comunicação entre clientes e servidores. Como o SOAP usa o
HTTP como camada de transporte ele opera na porta 80, que na extrema maioria dos
casos está liberada pelo proxy/firewall. Além do mais, conceitualmente não existe
diferença entre uma requisição a um método e a uma página HTML.

Dentre as vantagens dessa tecnologia, ainda podemos ressaltar a não necessidade de


instalação de software adicional para o suporte à tecnologia como acontece com o
CORBA e com o DCOM (no windows95).

Mais informações sobre SOAP podem ser encontradas no site da especificação oficial
em http://www.w3.org/TR/SOAP/.

Página 1 de 14
Desvendando Web Services (Soap/XML)

Implementando servidores com suporte a Web Services

Daremos início , agora, à parte prática deste artigo, então para começar devemos criar
um novo projeto de Web Services. No meu caso escolhi CGI stand-alone como tipo da
aplicação e ela rodará diretamente no IIS5, mas estejam livres para escolherem o que
mais lhe convierem. Feito isso seremos apresentados a seguinte tela:

Ao examinarmos o projeto gerado, percebemos facilmente que se trata de uma Web


Server Application. No Web Module criado, encontramos os componentes fundamentais
para implementação do servidor, são eles:

— THTTPSoapDispatcher: Este componente atua como despachante recebendo as


mensagens entrantes e as encaminha para o objeto especificado em sua propriedade
Dispatcher para que seja decodificada. Ele registra-se automaticamente junto ao Web
Module como um auto-dispatching object, fazendo assim com que não seja necessário
a criação de Actions para direcionar as requisições para o THTTPSoapDispatcher, este
passa então a receber todas as solicitações automaticamente.

— THTTPSoapPascalInvoker: Este componente recebe a mensagem vinda do


THTTPSoapDispatcher e a interpreta cuidando que seja disparado o método
correspondente a solicitação. Ele ainda codifica o retorno do método para o padrão
SOAP/XML.

— TWSDLHTMLPublish: Este componente é responsável por publicar todas


informações registradas pelo Web Service em WSDL que faz com que qualquer pessoa
possa adquiri-las para implementar o cliente desse serviço, mesmo que seja em outra
ferramenta que não o Delphi.

O próximo passo na implementação de um Web Services é a definição das interfaces


que serão publicadas para uso por seus clientes. Aquelas devem herdar de IInvokable.

Interfaces Invokable

Antes de passarmos a definição, vamos ver alguma coisa a respeito delas. Por que
precisamos derivar nossas interfaces de IInvokable? A resposta é simples, a
arquitetura trazida pelo Delphi requer que as interfaces de definição sejam compiladas
com informação de run-time, e é isso que a que a IInvokable garante. A única
diferença entre IUnknow e IInvokable é que esta é compilada com a diretiva {$M},
fazendo gerar RTTI para ela e todas suas descendentes.

Página 2 de 14
Desvendando Web Services (Soap/XML)

Dessa forma, tecnicamente chegamos a conclusão de que podemos derivar nossas


interfaces diretamente de IUnknow, desde que incluamos a diretiva {$M} na unit de
declaração da mesma. Embora isso seja possível e funcione, não é recomendável tal
conduta, porque podemos gerar problemas futuros com novas versões da arquitetura.
Se numa versão posterior for definido métodos essenciais na IInvokable teremos
problemas de compatibilidade ou anomalias no funcionamento do código.

Agora que vimos o conceito vamos passar para a parte prática. Vamos começar
definindo uma interface para as operações aritméticas básicas:

unit IMathIntf;

interface

type
IMath = interface(IInvokable)
['{E4F918D6-429C-45D4-9D28-D3E64DDB65E3}']
function Soma(X, Y : Double): Double; stdcall;
function Dife(X, Y : Double): Double; stdcall;
function Mult(X, Y : Double): Double; stdcall;
function Divi(X, Y : Double): Double; stdcall;
end;

implementation

uses InvokeRegistry;

initialization
InvRegistry.RegisterInterface(TypeInfo(IMath));

end.

Nessa unit encontramos uma declaração de interface normal, e esta será usada pelos
clientes para solicitar serviços junto ao servidor. Uma importante observação a ser
feita sobre a unit acima é o código localizado na seção initialization:

InvRegistry.RegisterInterface(TypeInfo(IMath));

Essa é mais uma característica da programação de Web Services. Sempre que


declararmos interfaces, classes de implementação ou classes de exceções (vistas mais
adiante) teremos que registrá-las. Isso é feito através de um objeto global
disponibilizado pela unit InvokeRegistry, que deve ser declarada na seção uses da unit.
Através desse objeto, chamamos métodos específicos para cada tipo de declaração, no
caso de interface usamos o RegisterInterface. Isso é necessário para o mecanismo de
vinculação, feito pelo componente THTTPSOAPPascalInvoker, entre solicitações SOAP e
a chamada ao método correto.

Pronto, agora que já temos a definição do serviço que desejamos prover vamos agora
implementá-la.

Implementando Interfaces Invokable (TInvokableClass)

Página 3 de 14
Desvendando Web Services (Soap/XML)

Quando vamos codificar as interfaces de nossos Web Services devemos criar classes
descendentes da TInvokable. Mas por que isso? São basicamente três os motivos que
nos levam a seguir esta convenção:

1 - Embora na implementação atual não tenha nada que realmente empeça que
derivemos nossa classes de uma outra qualquer como TObject ou TInterfacedObject,
assim como no caso da interface IUnknow/IInvokable, pode ser que em futuras
distribuições sejam criados métodos essenciais para o funcionamento da arquitetura na
Tinvokable, o que levaria nosso código a ter sérios problemas de compatibilidade e
funcionamento.

2 - Dispensa a necessidade de criar uma factory procedure, pois o invocation registry


sabe como instanciar essas classes e suas descendentes, do contrário teríamos que
registrá-las juntamente com uma factory procedure para assegurar seu
funcionamento.

3 - Dispensa a necessidade de implementar um esquema de liberação de memória. A


classe TInvokable implementa uma contagem de referências, e quando esta chega a
zero ela se libera automaticamente.

Mas se depois de tudo isso ainda quisermos usar outra ancestral para nossas classes
de implementação nós podemos. Vejamos abaixo o código da unit de implementação.

unit IMathImpl;

interface

Uses InvokeRegistry, IMathIntf;

type
//TMath = class(TInterfacedObject, IMath)
TMath = class(TInvokableclass, IMath)
public
function Soma(X: Double; Y: Double): Double; stdcall;
function Dife(X: Double; Y: Double): Double; stdcall;
function Mult(X: Double; Y: Double): Double; stdcall;
function Divi(X: Double; Y: Double): Double; stdcall;
end;

implementation

{ TMath }

function TMath.Soma(X, Y: Double): Double;


begin
Result := X + Y;
end;

function TMath.Dife(X, Y: Double): Double;


begin

Página 4 de 14
Desvendando Web Services (Soap/XML)

Result := X - Y;
end;

function TMath.Mult(X, Y: Double): Double;


begin
Result := X * Y;
end;

function TMath.Divi(X, Y: Double): Double;


begin
Result := X / Y;
end;

{// Factory Procedure


procedure CreateMath(out Obj : TObject);
begin
//Pode ser implementado aqui o conceito de singleton.

Result := TMath.Create;
end;
}

initialization
InvRegistry.RegisterInvokableClass(TMath);
//InvRegistry.RegisterInvokableClass(TMath, CreateMath);

end.

Repare nas linhas de código comentadas, visto que elas representam o básico
necessário para o funcionamento da arquitetura, se optarmos por não ter a Tinvokable
como ancestral de nossas classes. Ressalto que essa conduta deve ser evitada.
Novamente percebemos a necessidade de um código de registro, dessa vez o da
classe, que é feito como descrito abaixo.

InvRegistry.RegisterInvokableClass(TypeInfo(IMath));

Feito tudo isso até aqui, já podemos considerar que temos um servidor com suporte a
Web Services completo e funcional. Já até poderíamos passar para a implementação
de um cliente para usá-lo, mas ao invés disso vamos continuar a incrementá-lo com
mais alguns conceitos interessantes antes de passarmos ao desenvolvimento do
cliente.

Até agora só usamos um tipo de dado em nossa interface, o double. Embora essa
escolha tenha se dado devido ao serviço escolhido para o exemplo, o trabalho com
todos os outros tipos básicos(primitivos) funcionam da mesma maneira. Mas e se nós
precisarmos retornar para o cliente um tipo complexo como uma classe? Isso é o que
vamos ver a seguir.

Tipos complexos em Interfaces Invokable (TRemotable)

Página 5 de 14
Desvendando Web Services (Soap/XML)

Sempre que precisarmos retornar ou receber tipos complexos tais como records, sets
ou classes em nossos Web Services, devemos mapeá-los para classes descendentes de
TRemotable. Na compilação destas, são incluídas informações de RTTI, usadas para
converter os dados em SOAP stream.

Então para exemplificarmos o uso desse recurso vamos definir uma outra interface
para o nosso Web Service, que usará e retornará um tipo complexo por nós definido.
Vamos analisar o código abaixo:

unit IGeometryIntf;

interface

uses InvokeRegistry;

type
TLosango = class(TRemotable)
private
FX1, FX2, FY1, FY2 : Integer;
published
property X1 : Integer read FX1 write FX1;
property X2 : Integer read FX2 write FX2;
property Y1 : Integer read FY1 write FY1;
property Y2 : Integer read FY2 write FY2;
end;

TPoint = class(TRemotable)
private
FX, FY : Integer;
published
property X : Integer read FX write FX;
property Y : Integer read FY write FY;
end;

IGeometry = interface(IInvokable)
['{926590CF-4B48-4AB2-9079-24183DD8D34F}']
function DiagonalMaior(const Los : TLosango) : Integer; stdcall;
function Losango(const Centro : TPoint; DiaU, DiaL : Integer) : TLosango; stdcall;
end;

implementation

initialization
InvRegistry.RegisterInterface(TypeInfo(IGeometry));
RemTypeRegistry.RegisterXSClass(TPoint);
RemTypeRegistry.RegisterXSClass(TLosango);

end.

Declaramos duas remotables classes TLosango e TPoint que como seus nomes

Página 6 de 14
Desvendando Web Services (Soap/XML)

sugerem, representam respectivamente um losango e um ponto nos eixos de


coordenadas. Também declaramos uma interface IGeometry contendo duas funções:

- A primeira recebe um parâmetro TLosango e calcula a diagonal maior de um losango,


demonstrando a passagem de um tipo complexo como parâmetro de uma função.
- A segunda recebe os valores do centro, diagonal maior e menor de um losango, com
isso ela retorna um objeto TLosango contendo as coordenadas calculadas de acordo
com os parâmetros passados.

Devemos observar na seção initialization dessa unit uma diferença entre o registro de
uma interface e de uma remotable classe.

initialization
InvRegistry.RegisterInterface(TypeInfo(IGeometry));
RemTypeRegistry.RegisterXSClass(TPoint);
RemTypeRegistry.RegisterXSClass(TLosango);

end.

Como podemos observar até aqui, no caso das interfaces e invokables classes nós
usamos os métodos RegisterInterface e RegisterInvokableClass do objeto InvRegistry,
e no caso das remotables classes e exceções personalizadas usamos o método
RegisterXSClass do objeto RemTypeRegistry, todos eles definidos na unit
InvokeRegistry.

Então vamos implementar agora a definição analisada acima:

unit IGeometryImpl;

interface

uses InvokeRegistry, IgeometryIntf, Math;

type
TGeometry = class(TInvokableClass, IGeometry)
public
function DiagonalMaior(const Los: TLosango): Integer; stdcall;
function Losango(const Centro: TPoint; DiaU: Integer; DiaL: Integer): TLosango;
stdcall;
end;

implementation

{ TGeometry }

function TGeometry.DiagonalMaior(const Los: TLosango): Integer;


begin
Result := Max(Los.X2 - Los.X1, Los.Y2 - Los.Y1);
end;

Página 7 de 14
Desvendando Web Services (Soap/XML)

function TGeometry.Losango(const Centro: TPoint; DiaU,


DiaL: Integer): TLosango;
begin
Result := TLosango.Create;
Result.X1 := Centro.X - (DiaU div 2);
Result.X2 := Centro.X + (DiaU div 2);
Result.Y1 := Centro.Y - (DiaL div 2);
Result.Y2 := Centro.Y + (DiaL div 2);
end;

initialization
InvRegistry.RegisterInvokableClass(TGeometry);

end.

Analisando essa última implementação, os mais atentos poderiam fazer a seguinte


pergunta: E essa instância de TLosango criada no método Losango, não é preciso
desalocar a memória associada a ela? Não, todas as descendentes de TRemotable são
liberadas automaticamente logo depois que a codificação do pacote de retorno é
concluída, portanto não precisamos nos preocupar com isso.

Bom, antes de passarmos a implementação do cliente vamos dar uma rápida olhada
no manuseio de exceções.

Exceções personalizadas em Web Services

Todo programa bem implementado deve ter uma boa estrutura de exceções para
poder assegurar uma robustez desejável. No caso dos Web Services isso não deve ser
diferente.

Quando uma exceção ocorre no escopo da chamada de um método, o servidor


automaticamente codifica as informações sobre ela num SOAP fault packet e as envia
como retorno do método solicitado. Dessa forma, a aplicação cliente gera a exceção.

Se não definirmos um tratamento de exceções personalizadas, a aplicação cliente gera


uma exceção comum(Exception) com a mensagem trazida no SOAP fault packet.
Embora em alguns casos isso possa ser suficiente, nós temos como transmitir qualquer
informação desejada de uma exceção. Para isso se tornar possível, basta que a gente
defina nossas classes de exceção descendentes de ERemotableException.

Dessa forma podemos enviar ao cliente o valor de todas as propriedades published


definidas na classe, assim ele pode levantar uma exceção equivalente à ocorrida no
servidor. E o melhor é que se o cliente e o servidor compartilharem da mesma unit que
define, implementa e registra suas classes de exceção, automaticamente o cliente
levanta a exceção correta com todos seus valores de propriedades preenchidos quando
receber um SOAP fault packet.

Registrar as classes de exceção? Isso mesmo, como já podemos perceber tudo com
que trabalhamos em Web Services deve ser registrado. O registro das classes de
exceção se dá de forma idêntica a das descendentes de TRemotable que foi vista
anteriormente.

Página 8 de 14
Desvendando Web Services (Soap/XML)

Para exemplificar vamos criar agora uma exceção que será levantada quando o
método IGeometry.Losango receber no parâmetro Centro uma coordenada não
compreendida no primeiro quadrante. Então depois que definirmos a exceção vamos
ter de alterar esse método para implementar tal comportamento. Vejamos a seguir o
código de definição da exceção:

unit EInvalidCentroU;

interface

uses InvokeRegistry;

type
EInvalidCentro = class(ERemotableException)
private
FX, FY : Integer;
public
constructor Create(const X, Y : Integer);
published
property X : Integer read FX write FX;
property Y : Integer read FY write FY;
end;

implementation

{ EInvalidCentro }

constructor EInvalidCentro.Create(const X, Y: Integer);


begin
inherited Create('O centro deve estar no primeiro quadrante.');
FX := X;
FY := Y;
end;

initialization
RemTypeRegistry.RegisterXSClass(EInvalidCentro);

end.

Ufa! O caminho foi longo, mas chegamos lá. Com isso concluímos a codificação do
nosso servidor, vamos passar agora para a implementação do cliente.

Implementando clientes para Web Services

Para iniciar vamos criar uma nova aplicação comum e deixá-la com a interface como
abaixo:

Página 9 de 14
Desvendando Web Services (Soap/XML)

Agora que já construímos a interface, vamos ver como requisitar os serviços remotos
do Web Services. Como já destaquei anteriormente, um cliente independe da
implementação do servidor, dessa forma, veremos como obter informações sobre um
servidor que já está rodando e disponível.

Todo servidor Web Services deve publicar informações sobre si mesmo no padrão
WSDL, no Delphi isso é feito automaticamente pela simples inclusão e configuração do
componente TWSDLHTMLPublish no projeto do servidor. Essas informações estão
acessíveis para nós, normalmente, através de uma action com path info "/wsdl". Nesse
caso estamos rodando o servidor localmente sobre o IIS5.0, então podemos acessar
essas informações solicitando a seguinte URL :

http://localhost/cgi-bin/WebServices.exe/wsdl

Isso nos trará a seguinte tela:

Página 10 de 14
Desvendando Web Services (Soap/XML)

Podemos encontrar então todas as interfaces implementadas pelo servidor bem como
um link para a descrição WSDL de cada interface. Para vermos essa descrição vamos
clicar no link de IMath. Fazendo isso seremos apresentados a seguinte tela:

OBS: Este comportamento acima descrito pode variar de acordo com a implementação
do servidor.

Através dessas informações obtidas já podemos então analisá-las para começar a


codificação das units de interface com o servidor. Quer dizer que temos que ler estas
informações e codificá-las manualmente? Depende, se você estiver implementando o
cliente em Delphi está livre dessa tarefa, podemos usar o Web Services Importer para
fazer esse trabalho para nós:

Página 11 de 14
Desvendando Web Services (Soap/XML)

Devemos repetir esse procedimento para cada interface que queremos importar, no
caso da IMath devemos preencher o campo acima com a seguinte URL:

http://localhost/cgi-bin/WebServices.exe/wsdl/IMath

Página 12 de 14
Desvendando Web Services (Soap/XML)

Depois de clicarmos em Generate veremos que ele transformou aquela descrição


WSDL em units do Delphi muito semelhantes (senão igual) àquelas que foram
definidas no servidor. Isso torna claro que se o servidor e o cliente forem ser
implementados em Delphi podemos ignorar esse procedimento e simplesmente
compartilhar as units de definição entre os dois projetos.

Nesse ponto, já temos um projeto com a interface gráfica e todas as units de


importação prontas, passemos agora então a requisição dos métodos remotos.

THTTPRIO

O componente THTTPRIO é o que usamos para obter uma referência válida de uma
interface registrada. Quando fazemos um type cast desse objeto para uma interface
específica ele gera uma tabela de métodos em memória que é usada quando é feita
uma chamada a um método específico.

Antes de começarmos a usar esse componente, devemos configurá-lo, isso pode ser
feito de duas maneiras diferentes:

1 - Se o servidor foi escrito em Delphi precisamos apenas configurar a propriedade


URL, cujo valor é gerado no registro da invokable interface.
2 - Independentemente da linguagem usada na implementação do servidor, podemos
configurar as propriedades WSDLLocation, Service e Port. Quando configuramos
WSDLLocation, deve ser usado o mesmo valor passado para o Web Services Importer
(http://localhost/cgi-bin/WebServices.exe/wsdl/IMath), fica disponível valores para as
propriedades Service e Port que devem ser selecionados no object inspector.

Eu particularmente prefiro esse método 2, por ser um método mais genérico.

Depois do componente configurado vamos passar a implementação do botão de somar


para ilustrar o uso dele. Vejamos o código do botão somar:

procedure TfrmMain.SpeedButton1Click(Sender: TObject);


Var

Página 13 de 14
Desvendando Web Services (Soap/XML)

Im : IMath;
A, B : Double;
begin
Im := HRMath as IMath;
A := StrToFloat(EdtX.Text);
B := StrToFloat(EdtY.Text);
LblResult.Caption := FloatToStr(Im.Soma(A, B));
end;

Podemos perceber que o uso dele é bem simples. Basta declararmos uma variável do
tipo da interface desejada e atribuirmos a ela o type cast do componente. Depois de
feito isso, podemos usá-la normalmente como se fosse um objeto. Lembre-se também
que não é necessária a liberação de memória correspondente a referência da interface,
ela é liberada automaticamente quando a variável sai de escopo. Esse procedimento
deve ser repetido para todos os métodos que desejamos requisitar.

Para uma melhor compreensão do assunto abordado, recomendo que seja analisado os
fontes dos projetos criados ao longo deste artigo. Fontes disponíveis em xxxxxxxxxx.

TSoapConnection

O componente TSoapConnection pode ser usado por uma aplicação cliente que deseja
conectar-se a uma mult-tiered database application, implementada como um Web
Services. Ele nos dá a possibilidade de estabelecer a conexão entre servidor e cliente e
obter a IAppServer do servidor, implementado num RemoteDataModule, a partir da
qual temos acesso aos providers existentes nele.

Esse componente traz algumas vantagens em relação aos outros existentes que
desempenham funções parecidas. Dentre essas vantagens, destacamos o uso do HTTP
para transporte das informações, bem como o suporte a SSL. Podemos ter segurança
na transmissão de dados e ainda evitamos problemas com proxys.

Consulte o help online para informações sobre pré-requisitos para o uso deste
componente. O componente TSoapConnection desempenha mais ou menos o mesmo
papel dos componentes TDCOMConnection, TSocketConnection, TWebConnection,
TCORBAConnection.

Conclusão

Como podemos perceber a discussão sobre Web Services é extensa e muito


interessante. Uma das coisas que mais impulsiona a disseminação dessa tecnologia é a
simplicidade notada para implementar aplicações distribuídas quando comparada a
outras tecnologias existentes no mercado.

Termino este artigo desejando que as todas as expectativas daqueles que iniciaram a
sua leitura tenham sido satisfeitas. Agradecerei a todos que venham a opiná-lo ou
comentá-lo.

Página 14 de 14

Você também pode gostar