Sei sulla pagina 1di 12

Fernando Vasconcelos Página 1 de 12

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/.

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:

http://www.delphibr.com.br/artigos/soap.php 18/1/2004
Fernando Vasconcelos Página 2 de 12

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.

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;

http://www.delphibr.com.br/artigos/soap.php 18/1/2004
Fernando Vasconcelos Página 3 de 12

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)

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;

http://www.delphibr.com.br/artigos/soap.php 18/1/2004
Fernando Vasconcelos Página 4 de 12

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
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)

http://www.delphibr.com.br/artigos/soap.php 18/1/2004
Fernando Vasconcelos Página 5 de 12

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 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.

http://www.delphibr.com.br/artigos/soap.php 18/1/2004
Fernando Vasconcelos Página 6 de 12

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;

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.

http://www.delphibr.com.br/artigos/soap.php 18/1/2004
Fernando Vasconcelos Página 7 de 12

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.

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.

http://www.delphibr.com.br/artigos/soap.php 18/1/2004
Fernando Vasconcelos Página 8 de 12

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:

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:

http://www.delphibr.com.br/artigos/soap.php 18/1/2004
Fernando Vasconcelos Página 9 de 12

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:

http://www.delphibr.com.br/artigos/soap.php 18/1/2004
Fernando Vasconcelos Página 10 de 12

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

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.

http://www.delphibr.com.br/artigos/soap.php 18/1/2004
Fernando Vasconcelos Página 11 de 12

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
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.

http://www.delphibr.com.br/artigos/soap.php 18/1/2004
Fernando Vasconcelos Página 12 de 12

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.

http://www.delphibr.com.br/artigos/soap.php 18/1/2004

Potrebbero piacerti anche