Requests Ajax com Cross-Origin Resource Sharing (CORS) entre Sencha Touch e ExtJS e Backend

Oi pessoal,

Muitas pessoas às vezes perguntam como uma aplicação que está em um dominio1.com fazer requests Ajax para o dominio2.com ou até mesmo subdomínios do mesmo domínio.

A resposta que o Ext JS e Sencha Touch tem para isso é usar o proxy JsonP, onde você faz um request cross-domain e consegue ler um JSON como resposta. Nesse caso um script javascript será adicionado automaticamente na página para que essa leitura seja possível. Mas o JsonP fazer apenas requests GET, ou seja, só é possível ler dados.

Com uma requisição GET também é possível enviar parâmetros na url, mas como você vai fazer login passando usuário e senha na própria URL (http://dominio1.com?user=loiane&senha=123456)? Não dá né gente!?

Será que é possível então fazer requests AJAX usando GET, POST ou até mesmo REST (GET, POST, PUT, DELETE) para domínios diferentes (cross-domain)?

cors

É possível sim e a resposta é o CORS (Cross-Origin Resource Sharing).

Nesse caso, quando a gente usa CORS precisamos mudar apenas o backend para que este aceite requisições cross-origin. No Ext JS ou Sencha Touch não precisamos fazer nada, vamos usar um proxy Ajax ou Rest (o que preferir) normalmente.

Para adicionar o CORS no backend é muito simples. Nessa página tem todos os detalhes: http://enable-cors.org/

No PHP por exemplo, basta adicionar o seguinte código no início do código de cada arquivo PHP que irá aceitar requisições cross-origin:

<?php
   header('Access-Control-Allow-Origin: *');
?>

Se você usa Java ou Python, pode olhar os seguintes links:

É importante saber também que nem todos os browsers suportam CORS: http://enable-cors.org/client.html

Ok, pode nos mostrar um exemplo Loiane pra ficar mais claro?
Claro que sim! :)

Vou usar o esse exemplo como base, que é um CRUD que já usei algumas vezes aqui no blog.

Se você executar locamente vai rodar tranquilo.

Agora, vou retirar a parte PHP e vou fazer deploy em outro servidor/computador e vou executar o Ext JS de um computador. Para melhor exemplificar, vou executar o código Ext JS no código do meu laptop e o PHP vai ficar deployado no meu desktop:

Meu desktop - computador 01:

encha-cors-loiane01

Meu notebook - computador 02:

encha-cors-loiane02

Como os códigos cliente e servidor estão separados, na Store do ExtJS preciso atualizar a url, já que o acesso ao php não é mais local. No meu caso ficou assim:

api: {
    create: 'http://192.168.0.14/blog/sencha-cors-comp01/extjs4-crud-mvc/php/criaContato.php',
    read: 'http://192.168.0.14/blog/sencha-cors-comp01/extjs4-crud-mvc/php/listaContatos.php',
    update: 'http://192.168.0.14/blog/sencha-cors-comp01/extjs4-crud-mvc/php/atualizaContato.php',
    destroy: 'http://192.168.0.14/blog/sencha-cors-comp01/extjs4-crud-mvc/php/deletaContato.php'
}

Se tentar executar agora, não vai funcionar e o Ext JS vai lançar uma exception dizendo que não consegue fazer esse request e/ou o servidor não consegue processar:

encha-cors-loiane03

Note que o código onde está o código ExtJS é localhost (127.0.0.1) e estou tentando fazer request para 192.168.0.14, ou seja, domínios diferentes.

Agora vamos voltar no código PHP e adicionar o Access-Control-Allow-Origin: * - na verdade, no site do CORS fala que basta adicionar o header, mas isso não procede. Para que o seu código trate o CORS de maneira correta no PHP, é necessário um pouco mais que isso! Criei um arquivo novo chamado enableCORS.php com o seguinte conteúdo:

<?php

function enableCORS() {

    // Allow from any origin
    if (isset($_SERVER['HTTP_ORIGIN'])) {
        header("Access-Control-Allow-Origin: {$_SERVER['HTTP_ORIGIN']}");
        header('Access-Control-Allow-Credentials: true');
        header('Access-Control-Max-Age: 86400');    // cache for 1 day
    }

    // Access-Control headers are received during OPTIONS requests
    if ($_SERVER['REQUEST_METHOD'] == 'OPTIONS') {

        if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD']))
            header("Access-Control-Allow-Methods: GET, POST, OPTIONS");

        if (isset($_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']))
            header("Access-Control-Allow-Headers: {$_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS']}");

        exit(0);
    }

    return true;
}

?>

E no código para listar os contatos, por exemplo, o início do arquivo ficaria assim:

<?php

	include("enableCORS.php");
	enableCORS();

	//chama o arquivo de conexão com o bd
	include("connect.php");

	$start = $_REQUEST['start'];
	$limit = $_REQUEST['limit'];

	$queryString = "SELECT * FROM contact LIMIT $start,  $limit";

...

E vamos tentar novamente:

encha-cors-loiane04

Agora funciona! E basta fazer o mesmo para todos os arquivos PHP para que o CRUD funcione como esperado.

Se a gente olhar os detalhes do request, vamos ver as informações do response e ver que o request foi feito usando CORS:

encha-cors-loiane05

O mesmo se aplica a alguma app Sencha Touch - ou até mesmo requisição Ajax com JQuery.

Caso queria conferir o código completo usado nesse post, segue o exemplo de CRUD com CORD habilitado: https://github.com/loiane/sencha-touch-extjs-CORS

Até a próxima! :)