Web Services em AdvPL – Parte 01

Introdução

Quem não conhece, já ouviu falar de Web Services. Traduzindo em pouquíssimas palavras, Web Services é um padrão de comunicação entre sistemas, onde o provedor ( ou “servidor” ) de um Web Service, possui uma camada de publicação de classes e métodos, descritos em um documento baseado em XML, chamado de WSDL ( Web Services Description Language ), através do qual uma linguagem ou plataforma de desenvolvimento pode criar uma classe ou camada de “proxy” para acessar as funcionalidades descritas no WSDL. Normalmente os Web Services utilizam o protocolo HTTP para trocas de mensagens entre a aplicação cliente e o servidor, e são utilizados entre sistemas que podem ou não ser baseados na mesma plataforma ou tecnologia, e as aplicações client e server podem estar na mesma rede ( intranet ) ou na Internet.

WSDL

A especificação da descrição de um Web Service é bem ampla, ela prevê a descrição detalhada das classes e métodos disponíveis, descrição das estruturas de parâmetros e retornos, até mesmo a documentação textual de cada método pode ser especificada dentro do WSDL. Normalmente uma plataforma de desenvolvimento que trabalha com orientação a objetos e é compatível nativamente com Web Services, provê um tipo de classe diferenciada para permitir a publicação de uma classe implementada no formato nativo da linguagem para ser acessada como um Web Service.

SOAP

Soap é um acrônimo para Simple Object Access Protocol, trata-se de uma especificação para troca de informações estruturadas, baseado em XML. O WSDL fornecido descreve como devem ser os pacotes SOAP de envio e retorno de informações de um Web Service. A especificação das mensagens normalmente são simples, porém a especificação permite criar camadas complexas, chegando até mesmo ao nível de declaração de mensagens com herança e utilização de mais de um arquivo de definições, especificados como “includes” ou “imports” dentro dos WSDL(s).

Vantagens

Quando você usa uma plataforma ou ambiente de desenvolvimento que cria uma classe “proxy” para você consumir um Web Service, o mundo é maravilhoso, o céu é azul, tudo é lindo. Você não precisa saber ou entender o que vai dentro do XML, a classe monta tudo para você, basta alimentar os dados nas estruturas de parâmetro e consumir o serviço, e em caso de sucesso a(s) estrutura(s) de retorno estarão preenchidas. Com o detalhamento do WSDL, a classe proxy gerada pode até já vir documentada. A utilização do protocolo HTTP para envio de requisições torna mais fácil a criação de integrações de sistemas baseados em SOA (Service-Oriented Architecture), ou Arquitetura Orientada a Serviços.

Desvantagens

Quando falamos de transações curtas, requisições pequenas, Web Services é ótimo. Mas se você vai enfiar em uma única requisição um caminhão de dados, ou mesmo arquivos, a natureza de parser de XML — que normalmente exige que o documento inteiro seja parseado ou analisado antes do método chamado ser efetivamente executado — ou mesmo qualquer instabilidade no HTTP, ou picos de latência, podem tornar o processamento mais lento, devido ao overhead das camadas utilizadas, e com isso deixar o cliente do serviço esperando por muito tempo, o que não é uma boa prática. Como toda a solução tecnológica, seu uso deve ser mediante a necessidade, quando aplicável ao cenário proposto. Se você precisa transferir arquivos grandes para um processamento, faça-o via FTP, e utilize um WebService apenas para solicitar a requisição de processamento, informando o arquivo a ser utilizado. Lembrando que esta é uma prerrogativa que o desenvolvedor da parte “Server” do Web Service deve ser responsável.

Web Services no AdvPL

Desde 2002, o Protheus permite a criação de Web Services Server no repositório, bastando para isso escrever uma classe do tipo WSSERVER, e um FrameWork da LIB do ERP Microsiga provê a camada WEB de publicação ( WSDL ) e consumo. E, para a geração de uma classe proxy de um WebService, o IDE e o TDS possuem uma ferramenta de geração de código, que gera a classe client AdvPL ( WSCLIENT ), também utilizando um FrameWork da LIB do ERP Microsiga, para consumir o serviço.

A implementação dos Web Services em AdvPL (client-side) não consegue atender a especificação inteira de todas as versões de SOAP, mas atende a maioria das partes mais utilizadas das especificações. Existem algumas dificuldades com o uso de WebServices onde o WSDL “mescla” namespaces distintos nos serviços, e às vezes alguma propriedade específica da especificação é utilizada, e exige um pacote diferenciado. Uma vez que a classe proxy seja gerada em AdvPL, é possível customizá-la para atender estas necessidades, mas o desenvolvedor deve lembrar-se de que: Se ele precisar re-gerar a classe proxy, as alterações feitas por ele no código serão perdidas, portanto quando isso for mesmo necessário, cabe ao desenvolvedor manter um histórico do que ele precisou mexer na classe client, para ele refazer as mesmas alterações caso a classe client venha a ser re-gerada.

No TDN, existe uma árvore de tópicos sobre Web Services, dispinível a partir do Link http://tdn.totvs.com/display/tec/Web+Services+–+23597, onde inclusive é abordado um outro tipo de Web Service, chamado REST, que não usa XML para a troca de dados entre a aplicação cliente e servidora. Este post não substitui a documentação da TDN, apenas procura colocar alguns detalhes a mais, em uma linguagem um pouco mais informal, sobre a tecnologia envolvida.

Nova classe Client no AdvPL

Nas builds do TOTVS|Application Server, a partir da 7.00.121227P, foi publicada uma nova classe client de Web Services na linguagem AdvPL, chamada tWSDLManager. Ela é uma classe proxy dinâmica, onde você fornece para ela o WSDL do serviço, ela identifica na hora todas as classes e métodos disponíveis para serem consumidas, e provê métodos para que você consuma o serviço diretamente pela classe, ou utilize a classe como “core” de WebSErvices, para montar o pacote com a requisição, e o desenvolvedor poder utilizar outro meio de transporte além do HTTP para a transmissão dos dados.

Ela não é tão prática quanto a implementação anterior, onde uma classe proxy em AdvPL era gerada e compilada no repositório de objetos do ERP, porém ela foi construída com um núcleo mais robusto, e compatível com elementos não suportados pela classe proxy WSCLIENT. Então, este tópico vamos ver como usar esta classe para, por exemplo, consumir um Web Service publico na Internet.

Usando um Web Service na Internet

Vamos usar um serviço de conversão de moedas, disponível no endereço http://www.webservicex.net/CurrencyConvertor.asmx. Ao acessar esta página,
vemos que não se trata de um Web Site tradicional, mas uma página de publicação com detalhes do serviço. O provedor deste serviço utilizou o ASP .NET para fazê-lo. O que nos interessa para consumir o serviço é o WSDL, disponivel no link da página onde é mostrado em destaque “Service Description”. Clique neste link, e o WSDL será mostrado no seu Browser. Agora, pegue a URL mostrada no Browse ( http://www.webservicex.net/CurrencyConvertor.asmx?WSDL ) e copie para a área de transferência do Windows ( o famoso Control+C ).

Gerando a classe Client em AdvPL

Pelo método tradicional, usando o TDS, vamos gerar a classe client em AdvPL para consumir este serviço. Antes de mais nada, após abrir o TDS, entre na perspectiva de “Administrador TOTVS”, e na aba “Servidor”, configure ou selecione um servidor do TOTVS Application Server previamente configurado e conecte nele. Sem a conexão com um Application Server, a geração de código AdvPL para WebServices não funciona.

Utilizando o TDS 11.3, na perspectiva “Desenvolvedor Totvs”, localize a janela “Explorador de Projetos”, normalmente do lado esquerdo do vídeo, escolha ou crie uma pasta no projeto para acomodar o fonte gerado, e clicando com o botão direito sobre a pasta, escolha a opção Novo -> Outras, e na tela apresentada, escolha Advpl -> Novo fonte WSDL AdvPL.

Clique em “avançar”, e será mostrado uma tela perguntando os dados pertinentes do serviço. Na primeira informação (local), é mostrada a pasta do projeto onde o fonte será criado. Pode ser mantida a informação lá existente. Na segunda informação (nome do fonte), devemos informar o nome do fonte AdvPL que será criado com a classe client. No nosso exemplo, informe “ConvMoedas”, e no campo “url”, cole a URL do WSDL do serviço, anteriormente copiada para a área de transferência do Windows na etapa anteriormente descrita. Então, clique no botão “Concluir”.

Se não houver nenhum problema com a conexão com a Internet, o fonte “ConvMoedas.prw” deverá ser criado, e adicionado automaticamente ao Projeto aberto no TDS. PAra compilar o fonte, basta estar com o foco na janela do editor de código deste fonte, e pressionar as teclas Control+F9. Vamos ver o fonte gerado no meu ambiente de testes:

#INCLUDE "PROTHEUS.CH"
#INCLUDE "APWEBSRV.CH"
/* ===============================================================================
WSDL Location http://www.webservicex.net/CurrencyConvertor.asmx?WSDL
Gerado em 03/27/15 21:22:35
Observações Código-Fonte gerado por ADVPL WSDL Client 1.120703
 Alterações neste arquivo podem causar funcionamento incorreto
 e serão perdidas caso o código-fonte seja gerado novamente.
=============================================================================== */
User Function _HKRQIVL ; Return // "dummy" function - Internal Use
/* -------------------------------------------------------------------------------
WSDL Service WSCurrencyConvertor
------------------------------------------------------------------------------- */
WSCLIENT WSCurrencyConvertor
WSMETHOD NEW
 WSMETHOD INIT
 WSMETHOD RESET
 WSMETHOD CLONE
 WSMETHOD ConversionRate
WSDATA _URL AS String
 WSDATA _HEADOUT AS Array of String
 WSDATA _COOKIES AS Array of String
 WSDATA oWSFromCurrency AS CurrencyConvertor_Currency
 WSDATA oWSToCurrency AS CurrencyConvertor_Currency
 WSDATA nConversionRateResult AS double
ENDWSCLIENT
WSMETHOD NEW WSCLIENT WSCurrencyConvertor
::Init()
If !FindFunction("XMLCHILDEX")
 UserException("O Código-Fonte Client atual requer os executáveis do Protheus Build [7.00.131227A-20150220] ou superior. Atualize o Protheus ou gere o Código-Fonte novamente utilizando o Build atual.")
EndIf
If val(right(GetWSCVer(),8)) < 1.040504
 UserException("O Código-Fonte Client atual requer a versão de Lib para WebServices igual ou superior a ADVPL WSDL Client 1.040504. Atualize o repositório ou gere o Código-Fonte novamente utilizando o repositório atual.")
EndIf
Return Self
WSMETHOD INIT WSCLIENT WSCurrencyConvertor
 ::oWSFromCurrency := CurrencyConvertor_CURRENCY():New()
 ::oWSToCurrency := CurrencyConvertor_CURRENCY():New()
Return
WSMETHOD RESET WSCLIENT WSCurrencyConvertor
 ::oWSFromCurrency := NIL 
 ::oWSToCurrency := NIL 
 ::nConversionRateResult := NIL 
 ::Init()
Return
WSMETHOD CLONE WSCLIENT WSCurrencyConvertor
Local oClone := WSCurrencyConvertor():New()
 oClone:_URL := ::_URL 
 oClone:oWSFromCurrency := IIF(::oWSFromCurrency = NIL , NIL ,::oWSFromCurrency:Clone() )
 oClone:oWSToCurrency := IIF(::oWSToCurrency = NIL , NIL ,::oWSToCurrency:Clone() )
 oClone:nConversionRateResult := ::nConversionRateResult
Return oClone
// WSDL Method ConversionRate of Service WSCurrencyConvertor
WSMETHOD ConversionRate WSSEND oWSFromCurrency,oWSToCurrency WSRECEIVE nConversionRateResult WSCLIENT WSCurrencyConvertor
Local cSoap := "" , oXmlRet
BEGIN WSMETHOD
cSoap += ''
cSoap += WSSoapValue("FromCurrency", ::oWSFromCurrency, oWSFromCurrency , "Currency", .T. , .F., 0 , NIL, .F.) 
cSoap += WSSoapValue("ToCurrency", ::oWSToCurrency, oWSToCurrency , "Currency", .T. , .F., 0 , NIL, .F.) 
cSoap += ""
oXmlRet := SvcSoapCall( Self,cSoap,; 
 "http://www.webserviceX.NET/ConversionRate",; 
 "DOCUMENT","http://www.webserviceX.NET/",,,; 
 "http://www.webservicex.net/CurrencyConvertor.asmx")
::Init()
::nConversionRateResult := WSAdvValue( oXmlRet,"_CONVERSIONRATERESPONSE:_CONVERSIONRATERESULT:TEXT","double",NIL,NIL,NIL,NIL,NIL,NIL)
END WSMETHOD
oXmlRet := NIL
Return .T.
// WSDL Data Enumeration Currency
WSSTRUCT CurrencyConvertor_Currency
 WSDATA Value AS string
 WSDATA cValueType AS string
 WSDATA aValueList AS Array Of string
 WSMETHOD NEW
 WSMETHOD CLONE
 WSMETHOD SOAPSEND
 WSMETHOD SOAPRECV
ENDWSSTRUCT
WSMETHOD NEW WSCLIENT CurrencyConvertor_Currency
 ::Value := NIL
 ::cValueType := "string"
 ::aValueList := {}
 aadd(::aValueList , "AFA" )
 aadd(::aValueList , "ALL" )
 aadd(::aValueList , "DZD" )
 aadd(::aValueList , "ARS" )
 aadd(::aValueList , "AWG" )
 aadd(::aValueList , "AUD" )
 aadd(::aValueList , "BSD" )
 aadd(::aValueList , "BHD" )
 aadd(::aValueList , "BDT" )
 aadd(::aValueList , "BBD" )
 aadd(::aValueList , "BZD" )
 aadd(::aValueList , "BMD" )
 aadd(::aValueList , "BTN" )
 aadd(::aValueList , "BOB" )
 aadd(::aValueList , "BWP" )
 aadd(::aValueList , "BRL" )
 aadd(::aValueList , "GBP" )
 aadd(::aValueList , "BND" )
 aadd(::aValueList , "BIF" )
 aadd(::aValueList , "XOF" )
 aadd(::aValueList , "XAF" )
 aadd(::aValueList , "KHR" )
 aadd(::aValueList , "CAD" )
 aadd(::aValueList , "CVE" )
 aadd(::aValueList , "KYD" )
 aadd(::aValueList , "CLP" )
 aadd(::aValueList , "CNY" )
 aadd(::aValueList , "COP" )
 aadd(::aValueList , "KMF" )
 aadd(::aValueList , "CRC" )
 aadd(::aValueList , "HRK" )
 aadd(::aValueList , "CUP" )
 aadd(::aValueList , "CYP" )
 aadd(::aValueList , "CZK" )
 aadd(::aValueList , "DKK" )
 aadd(::aValueList , "DJF" )
 aadd(::aValueList , "DOP" )
 aadd(::aValueList , "XCD" )
 aadd(::aValueList , "EGP" )
 aadd(::aValueList , "SVC" )
 aadd(::aValueList , "EEK" )
 aadd(::aValueList , "ETB" )
 aadd(::aValueList , "EUR" )
 aadd(::aValueList , "FKP" )
 aadd(::aValueList , "GMD" )
 aadd(::aValueList , "GHC" )
 aadd(::aValueList , "GIP" )
 aadd(::aValueList , "XAU" )
 aadd(::aValueList , "GTQ" )
 aadd(::aValueList , "GNF" )
 aadd(::aValueList , "GYD" )
 aadd(::aValueList , "HTG" )
 aadd(::aValueList , "HNL" )
 aadd(::aValueList , "HKD" )
 aadd(::aValueList , "HUF" )
 aadd(::aValueList , "ISK" )
 aadd(::aValueList , "INR" )
 aadd(::aValueList , "IDR" )
 aadd(::aValueList , "IQD" )
 aadd(::aValueList , "ILS" )
 aadd(::aValueList , "JMD" )
 aadd(::aValueList , "JPY" )
 aadd(::aValueList , "JOD" )
 aadd(::aValueList , "KZT" )
 aadd(::aValueList , "KES" )
 aadd(::aValueList , "KRW" )
 aadd(::aValueList , "KWD" )
 aadd(::aValueList , "LAK" )
 aadd(::aValueList , "LVL" )
 aadd(::aValueList , "LBP" )
 aadd(::aValueList , "LSL" )
 aadd(::aValueList , "LRD" )
 aadd(::aValueList , "LYD" )
 aadd(::aValueList , "LTL" )
 aadd(::aValueList , "MOP" )
 aadd(::aValueList , "MKD" )
 aadd(::aValueList , "MGF" )
 aadd(::aValueList , "MWK" )
 aadd(::aValueList , "MYR" )
 aadd(::aValueList , "MVR" )
 aadd(::aValueList , "MTL" )
 aadd(::aValueList , "MRO" )
 aadd(::aValueList , "MUR" )
 aadd(::aValueList , "MXN" )
 aadd(::aValueList , "MDL" )
 aadd(::aValueList , "MNT" )
 aadd(::aValueList , "MAD" )
 aadd(::aValueList , "MZM" )
 aadd(::aValueList , "MMK" )
 aadd(::aValueList , "NAD" )
 aadd(::aValueList , "NPR" )
 aadd(::aValueList , "ANG" )
 aadd(::aValueList , "NZD" )
 aadd(::aValueList , "NIO" )
 aadd(::aValueList , "NGN" )
 aadd(::aValueList , "KPW" )
 aadd(::aValueList , "NOK" )
 aadd(::aValueList , "OMR" )
 aadd(::aValueList , "XPF" )
 aadd(::aValueList , "PKR" )
 aadd(::aValueList , "XPD" )
 aadd(::aValueList , "PAB" )
 aadd(::aValueList , "PGK" )
 aadd(::aValueList , "PYG" )
 aadd(::aValueList , "PEN" )
 aadd(::aValueList , "PHP" )
 aadd(::aValueList , "XPT" )
 aadd(::aValueList , "PLN" )
 aadd(::aValueList , "QAR" )
 aadd(::aValueList , "ROL" )
 aadd(::aValueList , "RUB" )
 aadd(::aValueList , "WST" )
 aadd(::aValueList , "STD" )
 aadd(::aValueList , "SAR" )
 aadd(::aValueList , "SCR" )
 aadd(::aValueList , "SLL" )
 aadd(::aValueList , "XAG" )
 aadd(::aValueList , "SGD" )
 aadd(::aValueList , "SKK" )
 aadd(::aValueList , "SIT" )
 aadd(::aValueList , "SBD" )
 aadd(::aValueList , "SOS" )
 aadd(::aValueList , "ZAR" )
 aadd(::aValueList , "LKR" )
 aadd(::aValueList , "SHP" )
 aadd(::aValueList , "SDD" )
 aadd(::aValueList , "SRG" )
 aadd(::aValueList , "SZL" )
 aadd(::aValueList , "SEK" )
 aadd(::aValueList , "CHF" )
 aadd(::aValueList , "SYP" )
 aadd(::aValueList , "TWD" )
 aadd(::aValueList , "TZS" )
 aadd(::aValueList , "THB" )
 aadd(::aValueList , "TOP" )
 aadd(::aValueList , "TTD" )
 aadd(::aValueList , "TND" )
 aadd(::aValueList , "TRL" )
 aadd(::aValueList , "USD" )
 aadd(::aValueList , "AED" )
 aadd(::aValueList , "UGX" )
 aadd(::aValueList , "UAH" )
 aadd(::aValueList , "UYU" )
 aadd(::aValueList , "VUV" )
 aadd(::aValueList , "VEB" )
 aadd(::aValueList , "VND" )
 aadd(::aValueList , "YER" )
 aadd(::aValueList , "YUM" )
 aadd(::aValueList , "ZMK" )
 aadd(::aValueList , "ZWD" )
 aadd(::aValueList , "TRY" )
Return Self
WSMETHOD SOAPSEND WSCLIENT CurrencyConvertor_Currency
 Local cSoap := "" 
 cSoap += WSSoapValue("Value", ::Value, NIL , "string", .F. , .F., 3 , NIL, .F.) 
Return cSoap
WSMETHOD SOAPRECV WSSEND oResponse WSCLIENT CurrencyConvertor_Currency
 ::Value := NIL
 If oResponse = NIL ; Return ; Endif 
 ::Value := oResponse:TEXT
Return
WSMETHOD CLONE WSCLIENT CurrencyConvertor_Currency
Local oClone := CurrencyConvertor_Currency():New()
 oClone:Value := ::Value
Return oClone

Agora, para consumir este serviço, precisamos dar uma olhada com calma na classe proxy gerada. Primeiro, vejamos os métodos disponíveis:

WSMETHOD NEW
 WSMETHOD INIT
 WSMETHOD RESET
 WSMETHOD CLONE
 WSMETHOD ConversionRate

Os quatro primeiros métodos fazem parte da manutenção da instância da classe client. Por hora o que nos interessa é o construtor (NEW), e o método ConversionRate, que é o único método disponibilizado por este serviço. Vamos procurar pela declaração deste método.

WSMETHOD ConversionRate WSSEND oWSFromCurrency,oWSToCurrency WSRECEIVE nConversionRateResult WSCLIENT WSCurrencyConvertor

Ao avaliarmos a implementação do método, vemos que ele têm dois parâmetros, do tipo Objeto, e um resultado numérico. Vamos procurar no fonte pela definição de cada um destes parâmetros (oWSFromCurrency e oWSToCurrency).

WSDATA oWSFromCurrency AS CurrencyConvertor_Currency
WSDATA oWSToCurrency AS CurrencyConvertor_Currency

Cada um dos parâmetros do WebService é declarado como uma propriedade da classe Proxy. Neste caso, ambos são objetos do mesmo tipo ( CurrencyConvertor_Currency ). Vamos ver definição deste objeto:

// WSDL Data Enumeration Currency
WSSTRUCT CurrencyConvertor_Currency
 WSDATA Value AS string
 WSDATA cValueType AS string
 WSDATA aValueList AS Array Of string
 WSMETHOD NEW
 WSMETHOD CLONE
 WSMETHOD SOAPSEND
 WSMETHOD SOAPRECV
ENDWSSTRUCT

Então, este objeto é um “Enumeration”, com base em uma String. Isto permite o código Client do Web Services verificar se o conteúdo informado é válido antes mesmo da requisição ser realizada.

Com estas informações, vamos montar o fonte Client em AdvPL que vai consumir esta classe.

#include "protheus.ch"
/* -------------------------------------------------------------------
Função U_MOEDAS
Autor Júlio Wittwer
Data 28/03/2015
Descrição Fonte de exemplo consumingo um Web Service publico
 de fator de conversão de moedas, utilizando a 
 geração de classe Client de Web Services do AdvPL
Url http://www.webservicex.net/CurrencyConvertor.asmx?WSDL
------------------------------------------------------------------- */
User Function Moedas()
Local oWS
// Cria a instância da classe client
oWs := WSCurrencyConvertor():New()
// Alimenta as propriedades de envio 
oWS:oWSFromCurrency:Value := 'BRL' // Real ( Brasil )
oWS:oWSToCurrency:Value := 'USD' // United States Dollar
// Habilita informações de debug no log de console
WSDLDbgLevel(3)
// Chama o método do Web Service
If oWs:ConversionRate()
 // Retorno .T. , solicitação enviada e recebida com sucesso
 MsgStop("Fator de conversão: "+cValToChar(oWS:nConversionRateResult),"Requisição Ok")
 MsgStop("Por exemplo, 100 reais compram "+cValToChar(100 * oWS:nConversionRateResult )+" Dólares Americanos.")
Else
 // Retorno .F., recupera e mostra string com detalhes do erro 
 MsgStop(GetWSCError(),"Erro de Processamento")
Endif
Return
Ao executar este fonte, o retorno esperado é uma sequência de duas janelas de informações, contendo o fator de conversão, e um exemplo de uso deste fator convertendo 100 reais para dólares americanos.
Agora, vamos ver como este exemplo seria escrito usando a classe tWSDLManager(), da forma mais minimalista possível.
#include "protheus.ch"
/* -------------------------------------------------------------------
Função U_MOEDAS2
Autor Júlio Wittwer
Data 28/03/2015
Descrição Fonte de exemplo consumingo um Web Service publico
 de fator de conversão de moedas, utilizando a 
 classe tWSDLManager()
Url http://www.webservicex.net/CurrencyConvertor.asmx?WSDL
------------------------------------------------------------------- */
User Function Moedas2()
Local oWSDL
Local lOk, cResp, aElem, nPos
oWSDL := tWSDLManager():New()
// Seta o modo de trabalho da classe para "verbose"
oWSDL:lVerbose := .T.
// Primeiro faz o parser do WSDL a partir da URL
lOk := oWsdl:ParseURL( "http://www.webservicex.net/CurrencyConvertor.asmx?WSDL" )
if !lOk 
 MsgStop( oWsdl:cError , "ParseURL() ERROR")
 Return
endif
// Seta a operação a ser utilizada
lOk := oWsdl:SetOperation( "ConversionRate" )
if !lOk
 MsgStop( oWsdl:cError , "SetOperation(ConversionRate) ERROR")
 Return
endif
// Setar um valor para conversão
lOk := oWsdl:SetFirst('FromCurrency','BRL')
if !lOk
 MsgStop( oWsdl:cError , "SetFirst(FromCurrency) ERROR")
 Return
endif
lOk := oWsdl:SetFirst('ToCurrency','USD')
if !lOk
 MsgStop( oWsdl:cError , "SetFirst (ToCurrency) ERROR")
 Return
endif/
// Faz a requisição ao WebService 
lOk := oWsdl:SendSoapMsg()
if !lOk
 MsgStop( oWsdl:cError , "SendSoapMsg() ERROR")
 Return
endif
// Recupera os elementos de retorno, já parseados
cResp := oWsdl:GetParsedResponse()
// Monta um array com a resposta parseada, considerando
// as quebras de linha ( LF == Chr(10) ) 
aElem := StrTokArr(cResp,chr(10))
nPos := ascan(aElem,{|x| left(x,21) == 'ConversionRateResult:'}) 
If nPos > 0 
 nFator := val( substr(aElem[nPos],22) )
 MsgStop("Fator de conversão: "+cValToChar(nFator),"Requisição Ok")
 MsgStop("Por exemplo, 100 reais compram "+cValToChar(100 * nFator )+" Dólares Americanos.")
Else
 MsgStop("Resposta não encontrada ou inválida.")
Endif
Return

Para utilizar este exemplo, é necessário criar um arquivo para conter o Fonte Client da classe AdvPL gerada, e outro para compilar o fonte que consome esta classe client, e para executar, basta chamar os fontes U_Moedas e U_Moedas2 a partir do SmartClient.

A classe tWSDLManager, embora seja superior em relação ao SOAP, ainda precisa de algumas informações adicionais para se tornar mais prática para uso. Para montar o exemplo acima, foi necessário codificar um fonte um pouco maior, chamando os métodos adicionais da classe para obter os nomes dos serviços publicados, os nomes dos elementos de parâmetro e retorno do serviço desejado, e verificar o formato de retorno parseado para extrair o valor retornado pelo Web Service.

Nos próximos tópicos sobre Web Services, vamos abordar entre outras coisas a utilização de serviços que requerem autenticação e/ou conexão segura (SSL) utilizando chaves criptográficas, e como tratar de forma adequada as falhas que podem ocorrer no lado de uma aplicação client de Web Services.

Conclusão

Este post foi só pra abrir o apetite, têm muito mais a ser explorado neste universo. Mais uma vez, agradeço a todos pela audiência do Blog, e espero vê-los por aqui mais vezes. Até o próximo post, pessoal 😉

44 respostas em “Web Services em AdvPL – Parte 01

  1. Excelente artigo! Vou ficar no aguardo dos próximos!
    Só fiquei com uma dúvida, como o fonte U_Moedas irá conseguir instanciar o objeto da classe
    WSCurrencyConvertor sendo que não estamos mais utilizando aquele fonte gerado automáticamente? A classe tWSDLManager irá se encarregar internamente da criação dela?
    Se possível gostaria de sugerir um artigo explicando o REST também 🙂
    Pelo que vi o Fluig esta utilizando o conceito de REST.
    Obrigado por compartilhar! Abraços!!

    Curtido por 1 pessoa

    • Obrigado 😉

      Na verdade a classe tWSDLManager atua como proxy, uma vez que você carregue o WSDL, ela interpreta o WSDL e permite você selecionar os métodos, escolher o método a ser chamado, inputar os parâmetros e fazer a requisição. Porém, cada vez que você for chamar um método do WebService, você precisa fornecer o WSDL, via URL ou por arquivo em disco, pelo menos para fazer a primeira chamada, depois você pode manter a instância na memória e selecionar outros métodos deste Web Service para serem chamados. O melhor dos mundos vai ser quando a tWSDLManager for utilizada internamente para a geração de uma classe estática, aí sim vai ficar mais simples desenvolver aplicações client para Web Services no AdvPL.

      Quanto ao REST, ele está na lista de tópicos a serem abordados em breve 😀

      Abraços

      Curtir

      • Testei a classe e funcionou perfeitamente. Vou começar a utilizá-la nas minhas implementações futuras.
        Valeu!!

        Curtido por 1 pessoa

  2. Tenho alguns projetos na empresa que já usam webservice. Gosto muito da abordagem e da flexibilidade do serviço, apesar de achar que muita coisa poderia ser melhorada. Tenho pouca experiência no desenvolvimento de webservices em ADVPL, mas a empresa já sinalizou que novos projetos virão nesta área, então qualquer boa dica será bem vinda!

    Estou aguardando os próximos posts com muita ansiedade!

    Curtido por 1 pessoa

    • Olá Thais,

      Este assunto será abordado em maior profundidade, esta semana o post está “atrasado” devido a questões parelelas …rs… o fluxo normal do Blog deve retornar na semana que vem !

      Grato pela audiência 😀

      Curtir

  3. Siga0984,

    Estou pesquisando e não encontro informação sobre um determinado assunto, qual o local mais adequado para eu ter fazer uma pergunta?

    Curtir

  4. Olá… um artigo muito atual, bem feito e esclarecedor…. parabéns.

    Mas vc saberia me dizer pq no protheus q utilizo ele dá “invalid class TWSDLMANAGER” quando tento instanciar ela?

    Curtido por 1 pessoa

    • Olá Marcel, obrigado 😀

      Verifique a Build do Protheus Server que você está utilizando. Esta classe somente está disponível a partir da Build 7.00.131227A

      Abraços

      Curtir

  5. Júlio, mais uma vez parabéns!

    Tenho um processo de integração desenvolvido que utiliza a classe WSCLIENT. Monitorando o console do servidor percebi algumas mensagens com esse conteúdo..” Too many XML dinamic nodes This may slow down parser operations”. Isso significa que o processo está sofrendo na performance…pergunta: Converter para a classe TWSDLMANAGER resolve?

    Curtido por 1 pessoa

    • Olá Luiz, obrigado 😉

      Na verdade, existem duas possibilidades disso acontecer, todas relacionadas ao uso da função XmlParser() e XmlParserFile(). Como você utiliza uma classe client de WebServices gerada pelo Advpl, que iternamente utiliza a XmlParser(), isto pode acontecer quando um XML retornado por uma requisição de WebServices têm muitos nós internos, OU , não está sendo feita nenhuma limpeza dos nós de XML. Apos utilizar a XmlParser() e/ou XmlParserFile(), ocorre a criação dinâmica do objeto XML pelo parser. Após você utilizar os objetos dinâmicos gerados pelo parser, a definição deles continua na memória, a menos que você limpe a definição usando a função DelClassIntf() . Ela está documentada na TDN, mas a documentação não está refletindo o seu comportamento, e precisa ( será ) revisada. 😉

      Abraços

      Curtir

    • E, a propósito, se você fizer um parser XML usando a classe tXmlManager(), ela não gera classes dinâmicas, logo ela não têm este problema. Esta classe é bem mais leve e mais rápida para fazer o parser 😀

      Abraços

      Curtir

  6. Boa tarde! Parabéns pelos exemplos, são de grande ajuda, consegui fazer uma requisição para um webservice de terceiro usando a classe TWSDLMANAGER . Porém me deparei com um problema, tenho que fazer uma requisição para um webservice no qual posso enviar vários ítens em uma mesma requisição(um array), porém consigo enviar apenas um. Você tem algum exemplo deste tipo na classe TWSDLMANAGER pra me enviar? Solicitei na TOTVS e infelizmente eles não mandam, falaram pra utilizar os exemplos do TDN, que são horríveis.

    Abraço,

    Felippe

    Curtido por 1 pessoa

  7. Olá um artigo muito bom, parabéns.

    Estou precisando de um exemplo usando Restful, o exemplo que tem no TDN está difícil de entender.

    Já criei o ambiente “Microsiga Protheus – RESTFul API” com base no exemplo criei a classe com os métodos porém não consigo consumir nada.

    Att,

    Curtido por 1 pessoa

  8. Grande Julião,

    Não sei se irá se recordar de mim, pois estava sempre com o Mansano e o Rodrigo. Parceiro, você tem como dar aula, montar umas turmas se tiver disponibilidade claro, sobre webservice, MVC, incluindo este webret e TWsdlManager que não se encontra e é exigido nas vagas. Estou perdendo algumas oportunidades, principalmente devido webret e TWsdlManager.

    Cara fico no aguardo de seu retorno.

    Abraços

    Curtido por 1 pessoa

    • Fala Valdemir, seu nome não me soa estranho ..rs.. mas ainda não “liguei” o nome à pessoa 😉 De qualquer modo, já me passou pela cabeça dar aulas de AdvPL … porém preciso criar o material, e separar tempo para isso, e verificar se isso não fere meu contrato de trabalho com a TOTVS. 😉 MVC , REST e afins são implementações do Framework, preciso aprofundar um pouco mais meus conhecimentos nestes assuntos para estar apto a dar aula sobre isso 😀

      Manda um e-mail pra mim (siga0984@gmail.com), vamos falar mais sobre isso !!

      Abraços

      Curtir

  9. Excelente post… me animou bastante nos estudos iniciais em webservice em advpl.
    Consegui rodar sem problemas as funções U_Moedas e U_Moedas2 porém não compreendi muito bem a relação com o fonte webservice criado no TDS que também foi compilado no meu ambiente.

    Abraços….

    Curtido por 1 pessoa

  10. Bom dia, é possível utilizar um WebService para dar carga em uma rotina MSExecAuto do Protheus 12?
    Já criei a rotina execauto que irá imputar os dados no banco mas não sei como proceder para que um webservice busque um arquivo TXT ou XML em determinado diretorio com os dados e tais informações sejam processadas pela função autoexec criada e grave esses dados no banco de dados. Alguém poderia me dar uma luz?
    Desde já agradeço.

    Sidelcino Souza

    Curtido por 1 pessoa

    • Olá Sidelcino, boa noite,

      Acredito que sua dúvida seja melhor atendida em algum fórum sobre AdvPL e ERP, como por exemplo o HelpFacil, ou outro fórum disponivel em algum dos “links interesantes” na barra lateral direita do Blog 😀

      Abraços

      Curtir

  11. Olá!

    Estou utilizando um Webservice SOAP Protheus que retorna dados do Easy Import Control, porém nos testes, quando a mensagem ultrapassa 1 (um) megabyte, o servidor retorna:

    document.cookie = ”

    Sabes qual parâmetro deve ser alterado? Já passaste por este problema?

    Atenciosamente.

    Curtido por 1 pessoa

  12. Bom dia Julio.
    Necessitamos desenvolver um processo de comunicação webservice no qual o XML deve ser assinado digitalmente utilizando certificado A1 ou A3.
    Preciso saber se o ADVPL do Protheus 12 tem funcionalidades disponíveis para que possamos desenvolver este processo.

    Você tem esta informação?
    E abusando um pouco mais, se for possivel você teria algum material apenas para me dar um “norte”?

    Já abri chamado por duas vezes no portal da Totvs más não recebo nenhuma resposta.

    Att.

    Curtido por 1 pessoa

  13. Olá Júlio, estou com uma dificuldade quando a decimais no envio da função WSSOAPVALUE:
    Olhe o exemplo abaixo onde a variável npriceValue é 139.41 (2 casas decimais):
    Local cSoap := “”
    cSoap += WSSoapValue(“priceTp”, ::cpriceTp, ::cpriceTp , “string”, .F. , .F., 0 , NIL /*”br.com.accurate.acpr.priceservices”*/, .F.)
    cSoap += WSSoapValue(“priceValue”, ::npriceValue, ::npriceValue , “decimal”, .T. , .F., 0 , NIL /*”br.com.accurate.acpr.priceservices”*/, .F.)

    O conteúdo de cSoap fica com: “DFL139.41000000”

    Veja que quando eu retorno ele pela função WSSoapValue ele preenche com 6 zeros à direita.
    Alguma sugestão para correção, pois a documentação do integrador obriga que tenha apenas 2 casas decimais e passaram a bloquear o conteúdo.

    Curtido por 1 pessoa

    • Opa, bom dia !

      Então … A função WSSoapValue() — responsável por criar o nó XML automaticamente a partir do valor AdvPL e do tipo declarado no WSDL — não tem parametrização de precisão decimal … MAS, existe um contorno … Altere o código client gerado para você injetar a Tag XML com o formato esperado … sem usar a WSSoapValue …Por exemplo:

      cSoap += ” + alltrim(str(::npriceValue,2)) + ”

      😀

      Curtido por 1 pessoa

  14. Entendi, então, acabei remediando por motivo de urgência nesse formato.
    StrTran(WSSoapValue(“priceValue”, ::npriceValue, ::npriceValue , “decimal”, .T. , .F., 0 , NIL /*”br.com.accurate.acpr.priceservices”*/, .F.),”000000″,””)
    Gambiarra!
    Poderia colocar nesse formato abaixo?
    WSSoapValue(“priceValue”, Alltrim(str(::npriceValue,2)), Alltrim(str(::npriceValue,2)) , “decimal”, .T. , .F., 0 , NIL /*”br.com.accurate.acpr.priceservices”*/, .F.)

    Curtido por 1 pessoa

  15. Corrigindo (trocar o decimal por string:

    WSSoapValue(“priceValue”, Alltrim(str(::npriceValue,2)), Alltrim(str(::npriceValue,2)) , “string”, .T. , .F., 0 , NIL /*”br.com.accurate.acpr.priceservices”*/, .F.)

    Curtido por 1 pessoa

  16. Olá Júlio, boa noite tudo bem? Eu tenho link WSDL (https://api.angellira.com.br/ws-soap/WSImport.asmx?WSDL) de um WebService que desejo consumir. Gerei o client dele e compilei. Nesse WS, tem um método que estou querendo usar chamado “AgendarViagem”. Para consumi-lo, eu criei uma User Function, instanciei a classe CLIENT do WS, chamei o método, passei os parâmetros necessários e não acontece nada! Não consigo obter nenhum retorno. E o mais engraçado é que quando eu testo esse método pela ferramenta SOAP UI ele roda e retorna o XML com os dados direitinho! E o mesmo envelope que eu passei via SOAP UI é que eu estou passando na minha função.

    Será que você poderia me ajudar?

    Além disso, conversando com uns colegas aqui e ali, me disseram que quando no WSDL do WS existem ‘complexType mixed=”true”‘ o VSCode ou TDS não consegue gerar o CLIENT de forma completa. Tem isso mesmo? Qual sua opinião?

    Curtido por 1 pessoa

    • Olá Marcos,

      Parece que o WSDL fornecido usa algumas coisas mais “rebuscadas” para criar a estrutura de retorno… o Retorno de alguns métodos são extensões ou complementos do objeto de retorno da inclusão, e coisas assim — complex type mixed // any nao oferecem uma descrição que permita “mastigar” o XML retornado pela requisição, fazendo as propriedades de envio serem criadas com o tipo “as SCHEMA” … e a parte de retorno com mixed type realmente não conseguem lidar com isso ..

      Neste caso, existem 3 caminhos : Alterar o fonte da classe client de WS gerada, para pegar os dados necessários ( Caso eles estejam dentro do objeto XML de retorno), ou implementar um client de webservices usando a nova classe tWSDLManager — ( com suporte a mais recursos do WSDL / SOAP e sem a necessidade de geração de fonte), ou ainda a 3a opção: Fazer a API cliente de webservices na unha, fazendo POST do XML e parser do retorno “na mão”….

      Espero que estas informações lhe sejam úteis !! Abraços 😀

      Curtir

    • Opa, beleza ? Olhe .. a aplicação Client AdvPL para webservices cria uma classe client , chamada “_HEADOUT” … Ela é um array, e nele voce pode acrescentar uma ou mais strings, que serao enviadas no Header http da requisição montada na hora da chamada do metodo da classe client.

      O Token em si, e a forma de colocar ele no header, depende do que o serviço WebServices Server exige . Existem autenticações de token feitas para serviços AdvPL do ERP, implementados pelo framework. Se for esse o caso, dê uma mergulhada no TDN, ok ? 😀

      Curtir

Deixe um comentário