quinta-feira, 21 de julho de 2016

Sobre Fullstack, AngularJS, Catalyst and CORS

Introdução


Recentemente resolvi começar a estudar frontend um pouco mais à sério, e resolvi aproveitar um projeto que estava desenvolvendo a algum tempo, e resolvi aplicar Angular no frontend substituindo completamente o que havia anteriormente, que basicamente eram templates construídos com Template Toolkit dentro do ambiente do Catalyst. Por quê? Bom, várias razões:

1. Mercado

Como alguns devem saber, AngularJS é um framework Javascript mantido pelo Google, extremamente popular entre designers e programadores frontend. Além de ter uma demanda razoavelmente grande no mercado de trabalho. Fora que sua arquitetura permite resumir bastante referências a código javascript utilizando tags que podem ser criadas e manipuladas por seus handlers de eventos de uma maneira relativamente simples, o que achei fantástico.

2. Necessidade

Resumidamente, algum engravatado filho da p.. resolveu que 'não valia à pena' pagar 3 pessoas em um time de TI para fazer a porra toda funcionar, e determinou que 'profissionais multifunção'(ou qualquer buzzword bosta do tipo), são a nova tendência de mercado, e deu um nome 'bonitinho' para isso: 'fullstack developer'. A tradução disso é 'especialista em tudo SQN', mas que ganha por apenas uma pessoa. Como 'morrer na praia' não está nos meus planos ainda, resolvi juntar minha atração pelo Angular com a necessidade de aprendê-lo e tentar me divertir enquanto faço isso, se possível.

Pessoalmente acho que a idéia do 'fullstack developer' é um grande erro. Mas foda-se a minha opinião! Se valesse alguma coisa nem estaria escrevendo esse artigo pra começo de conversa... :P

Contextualizando o Problema


CORS - Cross Origin Resource Sharing ou, 'Compartilhamento de Recursos com Origem Cruzada' em tradução livre. Trata-se de uma especificação que é utilizada para determinar se um recurso pode ser acessado de um outro domínio ou não, sendo que essa interação pode ou não ocorrer entre o browser e o servidor onde o recurso se encontra. Resumidamente isso acontece segundo as regras do 'same origin police'.

Recursos - No caso de APIs REST, correspondem aos endpoints. Esses tem origem em seus hosts e portas, que são o objeto de foco do CORS.

AngularJS - Trata-se de um framework MVC escrito em Javascript que facilita a organização e manipulação de objetos DOM oferecendo ferramentas úteis para organizar código HTML + CSS + Javascript, e tratar eventos diversos dentro de uma plataforma MVC. É bem popular e bem utilizado para se consumir serviços REST com APIs, por exemplo.

Catalyst - Bom e velho framework MVC feito em Perl. Confiável, flexível e cheio de coisas legais!


Problema


Ao escolher utilizar AngularJS, não tinha a informação de que CORS é algo que ele trata internamente por padrão. Isso a princípio seria bom, já que vem para evitar problemas de segurança. No entanto, como não tinha conhecimento prévio desse fato, no momento em que fazia requisições do tipo POST, através de um serviço interceptado pelos handlers de eventos do Angular, automaticamente transformou minhas requisições POST em uma requisições do tipo OPTIONS e enviava uma série de cabeçalhos, o que para o servidor que provia o serviço que eu tentava consumir simplesmente não existia, retornando o código http 404(resource not found). 

Isso me gerou um problema, já que as requisições não eram reconhecidas pelo servidor REST(pois esperavam POST e não OPTIONS). Então o problema se resumiu em descobrir como lidar com CORS.


Visão geral da arquitetura do serviço REST


Basicamente, tenho um frontend em Angular, cujos serviços consomem uma API REST disponibilizada por um nginx que faz proxy reverso para a porta 5000 do servidor, que é mapeada pela plataforma de middleware 'starman', que sobe o serviço implementado do Catalyst. Mais ou menos assim:





Apenas uma arquitetura 'clássica' de uma aplicação dividida em frontend e backend, onde frontend compreende toda a interação com os usuários(Clientes), e o backend todas as partes que disponibilizam dados da aplicação através de uma instância Catalyst, que se comunica com o servidor HTTP através de PSGI utilizando o starman como ferramenta.


Solução


Na verdade são duas:

1 - Garanta que a origem das requisições(host + porta) são iguais as do seu recurso;

2 - Implemente CORS nos seus recursos

Isso signfica devolver um 'response' com uma série de headers. Os headers são os seguintes:

Access-Control-Allow-Origin: Controla exatamente as origens de requisições permitidas para esse recurso. Você pode ter uma, uma lista deles, ou simplesmente aceitar todos(*)!

Access-Control-Allow-Methods: Controla os métodos http que são aceitos de outras origens pelo recurso.

Access-Control-Allow-Headers: Idem para headers;

Onde colocar isso? Atenção, porque agora falarei apenas em Catalyst!

Considerando que você está(e deveria estar) usando Catalyst::DispatchType::Chained, você deve setar os headers sempre no 'setup' da feature, ou seja, action que representa o primeiro nível da cadeia depois do '/' que é controlado pelo Controller Root. Mas porque não no root? Bom, pra dizer a verdade eu tentei, mas a requisição ficava travada e eu não entendi o porque ainda. Exemplo:



package api::Controller::Login;

BEGIN{extends 'Catalyst::Controller';}


sub setup :Chained('/') :PathPart('login') :CaptureArgs(0) {
  my ($self,$c) = @_;

  $c->response->headers->header(
   'Access-Control-Allow-Origin' => '*',
   'Access-Control-Allow-Methods' => 'GET, POST, PUT, DELETE, OPTIONS',
   'Access-Control-Allow-Headers' => $c->request->headers->header("Access-Control-Request-Headers")
  );



}



É possível centralizar isso, removendo esse código e criando um método dentro da classe da aplicação. Nesse caso, em lib/api.pm. Depois é só referenciar o método utilizando o objeto de contexto do Catalyst ($c).



Fim...