Integração de Sistemas em PHP e Delphi
usando WebServices
Saulo Arruda
(http://sauloarruda.eti.br)
Esta semana tive uma experiência nova em se tratando de integração de
sistemas. O caso envolvia um sistema de gerenciamento da empresa do
cliente, desenvolvido internamente em Delphi com Banco de Dados Microsoft
SQL Server e um site desenvolvido em PHP & MySQL. No sistema em Delphi,
que a partir de agora vou me referir apenas como ERP, os colaboradores da
empresa registram solicitações de serviços. O cliente queria disponibilizar um
espaço no site para que o cliente possa acompanhar o andamento da sua
solicitação.
Arquitetura
Esse é um cenário muito comum de vários clientes para os quais já trabalhei.
A questão interessante desse caso são as tecnologias envolvidas. As primeiras
idéias para a integração entre os sistemas era conectar diretamente no Banco
SQL Server e mostrar as informações no site. Essa solução se mostrou inviável
devido ao nível de segurança exigido para as informações deste banco, não
permitindo acesso externo. Neste caso, os desenvolvedores da equipe do
cliente sugeriram fazer uma sincronização de dados do ERP para o site.
Muito simples, eu pensei, vamos utilizar WebServices e SOAP. Logo a
arquitetura da aplicação segue o esquema do diagrama abaixo:
O site fornece um serviço que expõe os métodos necessários para a
sincronização dos dados do ERP. A comunicação é feita usando o protocolo
HTTPS garantindo a integridade dos dados e antes de qualquer operação.
Questões de autenticação serão discutidas mais adiante neste artigo.
Usando WebServices e SOAP é possível que o próprio servidor que o site está
hospedado, geralmente Apache + PHP, forneça o serviço pela porta 443
eliminando a necessidade de abertura de portas em firewalls tanto do cliente
quanto do servidor.
Para demonstrar a implementação da solução e o sigilo das informações do
cliente, vou usar um exemplo mais simples. Porém, todas as dificuldades
encontradas no desenvolvimento serão apresentadas.
SOAP Server
Desenvolvi a primeira versão do serviço usando a extensão PHP_SOAP que é
nativa para o PHP 5 e me pareceu bastante confiável e simples de usar.
Criei uma classe chamada SolicitacaoService, o arquivo WSDL e sem
nenhuma dificuldade registrei o serviço usando o código abaixo:
<?php
class SolicitacaoService {
// Definição da classe...
}
$server = new SoapServer("solicitacaoService.wsdl");
$server->setClass("SolicitacaoService");
$server->handle();
?>
Devo admitir que tive vários problemas para escrever o arquivo WSDL da
forma correta, já que nunca tinha feito isso “manualmente”.
Depois, criei uma aplicação em Delphi usei o "WSDL Importer" para criar uma
unit a partir do arquivo WSDL. Esse é um recurso bastante prático, pois as
interfaces dos métodos e definição dos tipos já são criados automaticamente.
Após isto, basta criar um form e um botão e escrever o código abaixo:
procedure TForm1.btnLoginClick(Sender: TObject);
var arr : ArrayOfstring;
begin
solicitacaoService := GetSolicitacaoServicePortType();
// Definição do array com os parâmetros...
ShowMessage(solicitacaoService.gravar(arr));
end;
Em ambiente de desenvolvimento, o problema da integração já está resolvido.
Porém, o servidor de produção usava a versão 4.2.2 do PHP que não suporta a
extensão PHP_SOAP. Além disso, essa versão do PHP também não suporta
todos os recursos de Orientação a Objetos que eu havia utilizado.
Neste momento resolvi usar uma implementação do protocolo SOAP para PHP
bastante simples chamada NuSOAP. Agora consegui a compatibilidade com o
PHP4 e melhor, sem necessidade de instalação de nenhuma extensão do PHP
no servidor de produção.
Usando o NuSOAP, o código sofreu várias modificações e ficou assim:
<?php
function gravar($arrAttr) {
return "ACK";
}
// Definição do serviço
$server = new soap_server();
$server->configureWSDL("SolicitacaoService",
"urn:UsuarioService");
// Registra o tipo ArrayOfstring
$server->wsdl->addComplexType("ArrayOfstring",
"complexType","array", "", "SOAP-ENC:Array",
array(), array(
array("ref"=>"SOAP-ENC:arrayType",
"wsdl:arrayType"=>"string[]")
), "xsd:string");
// Registra o método gravar
$server->register("gravar",
array("arrAttr" => "tns:ArrayOfstring"),
array("return" => "xsd:string"),
"urn:SolicitacaoService",
"gravar",
"rpc",
"encoded");
// Executa a requisição
$HTTP_RAW_POST_DATA = isset($HTTP_RAW_POST_DATA)
? $HTTP_RAW_POST_DATA : "";
$server->service($HTTP_RAW_POST_DATA);
?>
Neste caso, é necessário registrar cada método que será acessado
remotamente, porém o NuSOAP gera automaticamente o arquivo WSDL, o que
em minha opinião, é mais vantagem do que escrever somente:
$server = new SoapServer("solicitacaoService.wsdl");
$server->setClass("SolicitacaoService");
e ter de escrever o WSDL, que não é uma tarefa muito divertida. Acredite.
Por outro lado, não foi possível utilizar classes, pois o registro do método
gravar usando SolicitacaoService.gravar gerava um WSDL com nomes
de métodos não muito agradáveis, então preferi usar funções mesmo.
Perceba também que a declaração do tipo complexo ArrayOfstring é
bastante confusa, mas nada que não se possa achar pronto na Internet.
No cliente em Delphi, bastou gerar novamente a unit correspondente ao
serviço usando o WSDL Importer, usando o nome do script PHP com o
parâmetro wsdl (Ex.: http://sauloarruda.eti.br/service/solicitacao.php?wsdl).
Naturalmente a implementação da função gravar deve abrir uma conexão
com o banco de dados MySQL e gravar um registro na tabela de solicitação.
Para maior simplicidade do nosso exemplo esse código foi omitido e a função
retorna simplesmente o texto ACK que, por convenção, significa que a
chamada foi processada com sucesso.
Esse cliente em Delphi também deverá ser incorporado no ERP sendo que cada
vez que uma solicitação for gravada, uma chamada WebService será feita para
sincronizá-la. Além disso, será necessário tratar erros implementando uma fila
de chamadas do serviço que falharam para que seja feita uma nova tentativa.
Autenticação
Todos nós sabemos que um WebService é disponibilizado na Web e por isso,
qualquer pessoa pode usar um WSDL Importer da vida e sair fazendo chamadas
do seu serviço.
Como não é isso que queremos nesse caso, precisamos implementar algum
tipo de autenticação. A primeira idéia era usar autenticação HTTP. Mas antes
de implementar algo em PHP, resolvi testar os componentes em Delphi para
ver se é possível fazer isso. Achei um artigo que pareceu funcionar, mas não
fez diferença alguma.
A segunda opção era implementar um método login que retorna um ticket
usando uma idéia parecida com sistemas de Single Sing On. Na primeira
chamada, o cliente chama um método login passando usuário e senha e
recebe um ticket que será passado como parâmetro nas próximas chamadas.
Desta forma, o método login deve ser definido e registrado conforme o
código abaixo:
<?php
function login($usuario, $senha) {
if ($usuario == "service" && $senha == "secret") {
$ticket = md5(uniqid());
session_name($ticket);
session_start();
$_SESSION["login"] = true;
return $ticket;
} else {
return "401: Usuário ou senha inválidos";
}
}
// Registra o método login
$server->register("login",
array("usuario" => "xsd:string",
"senha" => "xsd:string"),
array("return" => "xsd:string"),
"urn:SolicitacaoService",
"login",
"rpc",
"encoded");
?>
O ticket é gerado usando a função uniqid, que garante a geração de um
identificador único, e aplicando um hash MD5 ao “id” gerado. Esse ticket é
armazenado em uma variável da sessão.
Um detalhe importante da implementação da função login é usar a função
session_name($ticket) para nomear a sessão com o ticket gerado. Desta
forma não existe risco de falhas de segurança, pois cada ticket gerado cria
uma nova sessão.
Para terminar, a função abaixo valida se o ticket passado por parâmetro é
válido:
<?php
function checkLogin($ticket) {
session_name($ticket);
session_start();
return isset($_SESSION["login"]);
}
?>
A implementação da função gravar terá sua assinatura alterada para receber
o ticket como parâmetro e validá-lo, conforme o código abaixo:
<?php
function gravar($arrAttr) {
if (!checkLogin($ticket)) {
return "401: Autenticação não encontrada";
}
return "ACK";
}
// Registra o método gravar
$server->register("gravar",
array("ticket" => "xsd:string",
"arrAttr" => "tns:ArrayOfstring"),
array("return" => "xsd:string"),
"urn:SolicitacaoService",
"gravar",
"rpc",
"encoded");
?>
No cliente, novamente devemos usar o WSDL Importer para gerar a unit do
serviço e alterar o código para chamar primeiro o método login, conforme o
código abaixo:
procedure TForm1.btnLoginClick(Sender: TObject);
var arr : ArrayOfstring;
ticket: string;
begin
solicitacaoService := GetSolicitacaoServicePortType();
ticket := solicitacaoService.login('service',
'secret');
// Definição do array com os parâmetros...
ShowMessage(solicitacaoService.gravar(ticket, arr));
end;
Conclusões
A integração entre sistemas em plataformas diferentes nem sempre é uma
tarefa trivial. Muitas vezes, por limitação da tecnologia, uso de versões
antigas da plataforma (PHP 4 e Delphi 7) e pouca documentação disponível
esse trabalho acaba tomando muito mais tempo que o previsto.
Nestes casos, eu considero de suma importância trocar idéias com colegas de
trabalho ou amigos que já possam ter passado pela mesma situação. Inclusive,
agradeço a valiosa ajuda dos colegas Bruno Freitas e Rodrigo Toledo que me
ajudaram com ótimas idéias para a resolução deste problema.
O código-fonte em Delphi e PHP deste artigo está disponível para download no
endereço: http://sauloarruda.eti.br/artigos/webservice.zip.
Qualquer dúvida, crítica ou sugestão sobre o tema deste artigo pode ser
enviada para o e-mail [email protected] ou via comentários deste
artigo publicado no endereço http://sauloarruda.eti.br/.
Licença
Este artigo está licenciado sob a licença Creative Commons
Attribution-ShareAlike 2.5 License.
Download

Integração de Sistemas em PHP e Delphi usando WebServices