Contenido

Ajax cross-domain con Safari 4, Google Chrome 2 y Firefox 3.5

9 Jul

+ 8

Uno de los problemas con los que nos encontramos al trabajar con Ajax, es la limitación a trabajar siempre bajo el mismo dominio. Para entendernos, desde https://www.anieto2k.com no puedo acceder mediante una petición Ajax a un contenido en http://www.google.com.

Debido a las peticiones por parte de la comunidad de desarrolladores se creó una estandarización para permitir extender el objeto XMLHttpRequest() con la capacidad de permitir peticiones entre diferentes dominios, permitiendo una mejor integración entre servicios online.

Safari4, Google Chrome 2 y ahora Firefox 3.5, ya implementan dicha mejora y nos permite trabajar con ella. Por otro lado Microsoft, en otro mundo, desarrolla XDomainRequest() que permite realizar exactamente lo mismo en la última versión de su navegador (la 8.0).

¿Como funciona?

La idea básica es muy sencilla, el nuevo objeto XMLHttpRequest() envia una nueva cabecera al servidor destino

Access-Control-Allow-Origin: http://servidor.destino

Y este, permite el acceso o no indicándo una cabecera nueva en la respuesta:

//PHP
<?php header('Access-Control: allow <*>'); ?> //Permitimos todos
<?php header('Access-Control: deny <*>');?>  //Denegamos todos
<?php header('Access-Control: allow <mozilla.org>');?> //Permitimos solo mozilla.org
<?php header('Access-Control: allow <mozilla.org> exclude <developer.mozilla.org>');?> //Permitimos mozilla.org y denegamos developer.mozilla.org
<?php header('Access-Control: allow <developer.mozilla.org:80> method GET, POST');?> //Permitimos developer.mozilla.org para el puerto 80 en los métodos GET y POST

//XML
<?access-control allow="*"?> //Permitimos todos
<?access-control deny="*"?> //Denegamos todos
<?access-control allow="mozilla.org"?> //Permitimos solo mozilla.org
<?access-control allow="mozilla.org" exclude="developer.mozilla.org"?> //Permitimos mozilla.org y denegamos developer.mozilla.org
<?access-control allow="developer.mozilla.org" method="GET POST"?> //Permitimos developer.mozilla.org para el puerto 80 en los métodos GET y POST

Veamos un ejemplo de la cabecera devuelta al navegador:

HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 00:23:53 GMT
Server: Apache/2.0.61
Access-Control-Allow-Origin: http://servidor.destino
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml

Cross-domain && Cross-browser

Como en casi todas las nuevas funcionalidades tenemos que contemplar las posibilidad de que esta no esté disponible en el navegador del usuario,  para ello debemos detectarlas y ofrecer una alternativa…

Ejemplo simple de envio por GET

var url = "http://bar.other/publicNotaries/";
if(XMLHttpRequest) {
  var request = new XMLHttpRequest();

  if("withCredentials" in request) {
   // Firefox 3.5 y Safari 4
   request.open('GET', url, true);
   request.onreadystatechange = handler;
   request.send();

  } else if (XDomainRequest) {
   // IE8
   var xdr = new XDomainRequest();
   xdr.open("get", url);
   xdr.send();

  } else {
  // Otros navegadores
  }
}

En este caso, usaremos como flag el atribute withCredentials para comprobar que está disponible esta nueva característica en el navegador del usuario.

“Preflighted” Request (No lo he sabido traducir).

Otra opción de la que disponemos es la capacidad de realizar una consulta previa al servidor para comprobar la disponibilidad de la funcionalidad.

var invocation = new XMLHttpRequest();
var url = 'http://bar.other/resources/post-here/';
var body = '<?xml version="1.0"?><person><name>Arun</name></person>';
function callOtherDomain(){
if(invocation) {
    invocation.open('POST', url, true);
    invocation.setRequestHeader('X-PINGOTHER', 'pingpong');
    invocation.setRequestHeader('Content-Type', 'application/xml');
    invocation.onreadystatechange = handler;
    invocation.send(body);
}

En este caso, estamos enviando una cabecera previa llamada OPTIONS:

OPTIONS /resources/post-here/ HTTP/1.1
Origin: http://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER

Y el servidor de destino nos devolverá información sobre las capacidades disponibles:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://arunranga.com
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER
Access-Control-Max-Age: 1728000

Posteriormente, devolverá las cabeceras con la respuesta:

POST /resources/post-here/ HTTP/1.1
...
Content-Type: application/xml; charset=UTF-8
X-PINGOTHER: pingpong
...

Esta funcionalidad no está disponible en XDomainRequest() de Internet Explorer 8.

Envio de Credenciales

Por defecto, las credenciales como Cookies o HTTP Auth, no se envian en la petición XMLHttpRequest(), si necesitamos enviarlos, la nueva implementación nos lo permite con un atributo que hemos visto previamente.

Por defecto, el valor de  withCredentials es false, así que tendremos que modificarlo antes de realizar la petición.

var request = new XMLHttpRequest();
var url = 'http://bar.other/resources/credentialed-content/';
function callOtherDomain(){
  if(request)
  {
   request.open('GET', url, true);
   request.withCredentials = "true";
   request.onreadystatechange = handler;
   request.send();
  }

Otra vez, Internet Explorer 8 y si XDomainRequest() no permiten hacer esto.

Alternativas para navegador más antiguos

Comentar

#

Me reservo el derecho de eliminar y/o modificar los comentarios que contengan lenguaje inapropiado, spam u otras conductas no apropiadas en una comunidad civilizada. Si tu comentario no aparece, puede ser que akismet lo haya capturado, cada día lo reviso y lo coloco en su lugar. Siento las molestias.