Web Services com REST e PHP, parte 2
Agora que você já viu que pra implementar um web service RESTful você não precisa de nada além da sua boa vontade, podemos nos aprofundar no assunto sem grandes riscos. Fiz questão de iniciar este série com um texto focado na parte programática, deixando de lado boa parte da filosofia REST, uma abordagem meio reversa, propositalmente. REST é um conjunto grande de conceitos que engloba a adoção de todo um ponto de vista diferente em relação a Web (se você anda seguindo a cartilha do bom desenvolvedor direitinho, muita coisa nem vai ser tão nova assim), e começar por esta parte pode fazer parecer que as coisas são muito mais complicadas do que são realmente.
REST, como já mencionei, não é um protocolo ou uma especificação, e sim um conjunto de princípios. A idéia é a seguinte: tudo o que existe na rede é visto como um recurso, que possui estados, e pode ser acessado e manipulado por meio de um endereço único e universal. Isto soa abstrato demais, por isto quis colocar o código primeiro (no final deste artigo você vai entender por quê o exemplo do primeiro artigo não é uma aplicação RESTful em seu sentido mais “puro”, apesar de utilizar o mesmo mecanismo de acesso).
Se na primeira parte nos implementamos um código “estilo REST”, mas sem constituir uma aplicação RESTful no seu estado mais puro, agora nós vamos discutir um exemplo de aplicaçao seguindo uma arquitetura totalmente REST, porém contornando a aplicação de métodos PUT e DELETE, para sua (e minha) conveniência; no entanto, deixei comentários nas partes onde eu contornei o uso destes métodos, indicando uma possível implementação que os utilize.
O exemplo é o seguinte: uma loja virtual. Vamos nos concentrar exclusivamente na que diz respeito aos produtos. Todos os resultados das operações abaixo (o que é enviado para o navegador do usuário, ou aplicação que estiver acessando sua aplicação) é feito sob a forma de XML e Headers HTTP indicando o status do recurso acessado. Uma arquitetural RESTful iria resultar em uma aplicação web com uma interface mais ou menos assim:
dominio.com/produtos
- Ao receber um request do tipo POST, um produto é criado no banco de dados da loja;
- Ao receber um request do tipo GET, sem parametros, os produtos da loja são listados (a ordem ou quantidade depende da lógica de negócio do lojista);
dominio.com/produtos/mesa-de-centro
- Exemplo de um request do tipo GET com parametro: o nome do produto. Pressupoe-se que este os nomes são únicos. URLs como dominio.com/produtos/?p=mesa-de-centro devem ser evitadas; use mod_rewrite ou qualquer outra coisa para tratar os requests e permitir URLs inteligentes;
- Ao receber um request do tipo POST, o produto é atualizado com os dados enviados. Sinta-se livre para pensar num modelo de autenticação, não é tão difícil assim. Este é um dos contornos para ausência do método PUT; na verdade, o correto seria que um request do tipo PUT para esta URL realizasse a atualização do conteúdo ou a criação do mesmo no endereço especificado, caso não exista nada ali e o conteúdo possa ser criado.
dominio.com/produtos/mesa-de-centro/delete
- Ao receber um input do tipo POST com alguma informação de autenticação, caso a autenticação ocorra, exclui o produto do banco de dados da loja. O mais correto seria realizar esta operação através de um request do tipo DELETE para dominio.com/produtos/mesa-de-centro; esta é uma maneira de contornar isto, já que DELETE não é exatamente o método mais popular do HTTP.
Agora você tem uma interface RESTful para manipular os produtos da sua loja. Parece bobagem? Não vale a pena? E se você pensar que, através do acesso a dominio.com/produtos/mesa-de-centro, quem fez o request recebe um XML com as informações do produto e um link para sua loja, e nada mais, o que possibilita que qualquer um adicione links para seus produtos em seus sites, com a descrição dos produtos? E que tal se com uma aplicação simples no seu celular você pudesse manipular os produtos na sua loja, sem complicação nenhuma? Estes são apenas alguns exemplos do que pode ser facilmente alcançado com uma arquitetura REST.
As coisas ficam ainda melhores se você pensar que, identificando o cliente que fez o request (coisa trivial), você pode decidir se vai retornar simplesmente um XML com os dados necessários ou a interface completa da sua loja (em XHTML, por exemplo) com menus e tudo mais, pronta para ser exibida num navegador. As possibilidades são enormes.
“Mas e o PHP, Rafael? No título você falou que tinha PHP!”. Tudo bem. Não vou escrever aqui o código para o exemplo acima, nem sonhe com isto; no entanto, vão alguns toques de como fazer as coisas que eu mencionei:
Identificar o user agent
As informações sobre o autor do request (uma string) ficam guardadas aqui:
$_SERVER['HTTP_USER_AGENT']
Para testar:
echo $_SERVER['HTTP_USER_AGENT'];
Retornar um XML com Header 200/OK (operação foi um sucesso, dados retornados)
header('Content-Type: text/xml');
echo '<?xml version="1.0" encoding="UTF-8"?>'
echo '<produto>';
echo '<nome>Mesa de centro</nome>';
echo '<link>http://meudominio.com/mesa-de-centro</link>';
echo '</produto>';
Já é o suficiente. O content-type do header é alterado para text/xml, permitindo que o conteúdo seja corretamente interpretado; o header 200/OK não precisa ser gerado manualmente, já que é o header padrão quando tudo corre bem. Gerar os valores dinamicamente é com você e sua paciência para trabalhar com bancos de dados.
Nos outros casos, quando ocorrer algum erro ou alguma situação diferente, basta definir o status no header com o código correspondente; a W3C (é claro) tem todas as definições que você precisa:
http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html
E definir um header é algo tão simples como:
header('HTTP/1.1 304 Not Modified');
Se quiser, pode usar echo para exibir alguma mensagem indicando o que aconteceu, mas não é recomendável. O header deve prover informação suficiente sobre o status (dê uma olhada na quantidade de headers no link mencionado acima), e deve ser interpretado por quem for acessar seu Web Service. Nada de repetir informação!
Agora é com você. Já deu pra perceber que implementar aplicações RESTful não é nada de outro mundo e não requer nada além do que você já tem; também ficou claro que a arquitetura da aplicação, o grande ponto do REST, resulta numa interface extremamente simples e funcional para acessar sua aplicação num abiente distribuído.
Web Services com REST e PHP
*Notei que a parte 1 deste artigo (esta que você está lendo) tem tido muito mais visualizações do que a parte 2, o que me deixou um pouco receoso; isto é uma introdução programática ao REST, uma abordagem apenas focada em código, ignorando boa parte dos conceitos. Isto é intencional, pois queria mostrar que do ponto de vista da escrita de código não é necessário nada absurdamente diferente do que você já faz; mas se você está lendo este artigo como um introdução ao REST eu recomendo que leia também a parte 2, na qual eu tentei abordar REST de forma mais conceitual, para não sair por aí falando e fazendo besteira!
REST (Transferência de Estado Representacional, ou Representational State Transfer) vem ganhando cada vez mais espaço entre os desenvolvedores, e motivos não faltam: simplicidade, objetividade e mais várias outras vantagens que você vai acabar descobrindo por si mesmo. Até aí, tudo bem; novas tecnologias geralmente aparecem visando exatamente estas coisas. A genialidade do REST é oferecer tudo isto ao preço de… nada. Tudo bem, quase nada.
REST é um paradigma arquitetural, não uma especificação de linguagem ou de protocolo. Pra desenvolver aplicações RESTful você não precisa ler nenhuma das muito organizadas, porém nada objetivas especificações da W3C, nem aprender outra linguagem ou qualquer coisa parecida. REST se baseia nos mesmos métodos GET e POST que você sempre usou, e a parte técnica da coisa para por aí*.
Se você estranhou quando eu falei que “a parte técnica da coisa para por aí”, é isso mesmo que você leu: o resto é conceito e filosofia e, como qualquer coisa envolvendo tecnologia, vai acabar esbarrando em outras tecnologias. No entanto, este artigo visa uma introdução programática ao REST, e não vamos discutir absolutamente nada disto aqui.
Já que a parte técnica é tão pequena, vamos logo pra ela. A parte que mais consumiu meu tempo neste artigo foi decidir sobre o quê usar como primeiro exemplo. Não queria nada que envolvesse banco de dados, nem nenhuma estrutura complexa de diretórios, nem arquivos de configuração de servidor, nem envolvesse mais do que dois arquivos: o cliente e o webservice em questão. Pensei na seguinte função:
function soma($numero1,$numero2) { return $numero1+$numero2; }
Mais claro impossível; esta função recebe dois valores e retorna a soma deles. Só isso. Esta vai ser o serviço oferecido: somar dois valores. Agora, vamos implementar isto de um jeito RESTful. O acesso vai ser feito por método GET, e os parâmetros vão se chamar n1 e n2 (que vão ser usados como os dois parametros da função soma). O conceito chave do REST é que tudo deve ser um recurso com um endereço único, e a manipulação deste recurso acontece pela troca de informações através de um protocolo.
No nosso caso o protocolo é o HTTP, o endereço único é a URI do webservice e as informações são os parametros enviados via GET. Resumidamente, isto significa que para utilizar o web service para saber o resultado da soma de 2 + 3, basta acessar a seguinte URL**: seudominio.com/soma.php?n1=2&n2=3 E o web service (o arquivo soma.php) seria assim:
function soma($numero1,$numero2) { return $numero1+$numero2; } echo soma($_GET['n1'],$_GET['n2']);
Ao acessar a URL, o resultado da operação (neste caso, 5) é exibida no navegador. CERTO. Se você está lendo este artigo como iniciação ao REST, existem grandes chances de você estar pensando algo como “ou este cara está tentando me enganar ou alguém está tentando enganar todo mundo”. Respire fundo e analise bem a situação. Pense sobre os web services que você já desenvolveu, caso tenha desenvolvido, e no que eles faziam. Eu avisei que REST não envolvia nada de novo, é só uma maneira diferente de abordar as coisas.
Vamos implementar o cliente de um jeito diferente; ao invés de web, vamos fazer uma aplicação para rodar na linha de comando. O código é simples:
$resultado = file_get_contents('http://sdc/soma.php?n1='.$argv[1].'&n2='.$argv[2]); echo $resultado;
Pronto! O método file_get_contents recebe um caminho de arquivo e retorna seu conteudo em uma string. No nosso caso, o “arquivo” é qualquer coisa que o nosso service retornar. Salve o trecho de código acima num arquivo chamado cliente.php e acesse agora via linha de comando:
php cliente.php 2 3
O resultado será:
5
Sendo que o processamento da soma aconteceu remotamente, no servidor, através do seu web service RESTful. Até aí, tudo bem. Mas não queremos viver num mundo onde os webservices se limitam a simplesmente retornar a soma de dois valores; se é pra ser assim, que pelo menos eles retornem isto num XML. Vamos alterar nosso código:
function soma($numero1,$numero2) { return $numero1+$numero2; } header('Content-Type: text/xml'); echo '<?xml version="1.0" encoding="iso-8859-1"?>'; echo '<resultado>'.soma($_GET['n1'],$_GET['n2']).'</resultado>';
Pronto. O resto agora fica a cargo da sua imaginação; não é difícil imaginar como extender isto para acessar bancos de dados e consequentemente realizar ações de entrada e persistência de dados (via POST) e recuperaçao de dados persistentes (via GET) ao invés de somente processamento de dados. Este artigo já ficou longo demais, e já cumpriu seu objetivo de dar uma visão geral sobre REST sob uma perspectiva prática.
Vale lembrar que o assunto é extenso, e merece ser estudado a fundo principalmente no que tange a arquitetura do seu sistema (afinal, REST é um estilo de arquitetura) e disponibilização de recursos.
* REST utiliza também os métodos PUT e DELETE, mas vamos ignorá-los aqui impiedosamente (se é que você já não os está ignorando a vida toda). Vários servidores não implementam esta parte do protocolo HTTP, e honestamente, não vejo porque se incomodar com isto, pelo menos por enquanto.
** Como eu disse, estou tentando simplificar as coisas; existem padrões de URL que vão de acordo com a filosofia de arquitetura de aplicações REST, e que merecem destaque em outros artigos. Neste caso, seria mais correto seria disponibilizar o serviço através de uma URL como “seudominio.com/soma/2+3″, mas falar sobre tratamento de URIs não faz parte do escopo deste artigo.
Chamada dinâmica de métodos no Python
Chamada dinâmica de métodos é um dos conceitos chave para construção de frameworks, e também uma peça fundamental para implementação de design patterns como inversão de controle. A idéia é a seguinte: permitir que um método seja chamado durante a execução do programa, porém sem que você saiba em tempo de codificação que método será este.
Uma boa prática de programação é tentar parametrizar tudo, sem deixar nada “forçado” no código. Usando algumas ferramentas de reflexão e introspecção disponiveis no Python podemos levar isto além, e começar a parametrizar até mesmo a chamada de métodos, algo que pode aumentar muito a facilidade de manutenção do seu programa.
Vamos usar como exemplo uma classe chamada UserController. A classe tem três métodos, e se parece com isto:
class UserController: def handle(self,action): pass def login(self): print 'Executou o método login()' def edit(self): print 'Executou o método edit()'
A interface para uso da classe é o método handle; ele recebe o nome de uma ação e então se responsabiliza por chamar o método correspondente. Uma solução seria construir um bloco de ifs:
def handle(self,action): if action=='login': self.login() elif action=='edit': self.edit()
Só com os dois métodos existentes já dá pra perceber o quanto esta solução é pouco prática e trabalhosa Imagine se você tem 100 métodos diferentes que podem ser chamados, vai escrever 100 desvios condicionais? Pior, e se você quiser estender esta classe? Vai acabar tendo que redefinir o método handle em todas as subclasses. Se o código já estava cheirando mal, agora é que está fedendo de verdade.
Uma solução simples é utilizar o poder de reflexão e introspecção embutidos no Python. A função hasattr verficia se um determinado método existe ou não em um objeto. Vamos alterar nosso método para verificar dinamicamente se a ação passada como parâmetro existe no objeto:
def handle(self,action): if hasattr(self,action): pint 'Metodo existente' else: print 'Metodo inexistente'
Agora vamos além: ao invés de simplesmente verificar se o método existe, vamos também executá-lo. O nome da função é getattr, e as semelhanças com hasattr não param por aí; os parâmetros são os mesmos, diferença é o retorno. No caso de hastattr, um valor booleano indicando se o atributo (métodos também são atributos) existe; no caso de getattr, o valor retornado é o próprio método. Estranho? O código abaixo pode deixar as coisas um pouco mais claras:
def handle(self,action): if hasattr(self,action): method = getattr(self,action) method() else: print 'Metodo inexistente'
Muito simples! A variável “method” agora contém o próprio método referenciado na variável “action”. Agora que você já entendeu o conceito, podemos deixar o código um pouco mais bonito:
def handle(self,action): if hasattr(self,action): getattr(self,action)() else: print 'Método inexistente'
O resultado é o mesmo. No final, nossa classe se parece com isto:
class UserController: def handle(self,action): if hasattr(self,action): getattr(self,action)() else: print 'Metodo inexistente' def login(self): print 'Executou o método login()' def edit(self): print 'Executou o método edit()'
Para testar:
controller = UserController()
controller.handle('login')
Vale lembrar que executar qualquer coisa dinamicamente pode ser perigoso, e você deve tomar sempre as precauções cabíveis ao seu programa!