Espacio Daycry - Espacio de programación

  • Inicio
  • Categorias
    • - Codeigniter
    • - Symfony
    • - HTML5
    • - Linux / Ubuntu
    • - PHP
    • - Jquery
  • PortFolio - Proyectos Codeiniter
    • - Encuestas Online
    • - Estadísticas - GLPI
    • - Gestión de colas
    • - Web Service - REST

miércoles, 22 de abril de 2015

Jquery - JSONP, llamadas AJAX entre dominios

Posted by daycry at 13:03 Labels: Javascript , Jquery , Plugins

Introducción

Seguro que alguna vez os ha pasado: intentáis hacer una petición AJAX a un dominio distinto al que estáis trabajando y os devuelve un error. En concreto el error que da Firefox es:
Access to restricted URI denied” code: “1012
Esto es normal ya que por seguridad, los navegadores no permiten hacer este tipo de llamadas. ¿Entonces, por qué hay APIs que exponen sus datos en JSON? Bueno para esta pregunta existen al menos dos respuestas:
  • La API puede ser igualmente consumida por cualquier tipo de programa que no sea un navegador, como un script PHP.
  • Realmente existe un truco para poder acceder a eses datos desde Javascript. Es un nuevo concepto llamado JSONP.
Vamos a ver con más detalle qué es JSONP y como siempre, ejemplos de uso.

Información sobre JSONP

JSONP es el acrónimo de JavaScript Object Notation with Padding, es decir, una forma deextension de JSON para soportar llamadas entre dominios. Según la wikipedia, este término fue propuesto en el blog MacPython en el año 2005 y desde entonces comenzó a ser usado por todo el entorno de aplicaciones web 2.0.
El funcionamiento en el que se basa, es que en nuestro código HTML sí podemos cargar un script de un dominio remoto, de forma que si tenemos nuestro dominio ontuts.com, podemos hacer perfectamente lo siguiente:
view plaincopy to clipboardprint?
  1. <script type="text/javascript" src="http://www.dominioremoto.com/datos.js"></script>  
De forma que si dicho script nos devuelve datos en formato JSON, los podremos recibir sin ningún problema. Pero, ¿qué pasa con eses datos? ¿cómo puedo acceder a ellos? Pues no puedes, simplemente recibes informacion pero no puedes acceder a ella.
JSONP se basa en la capacidad que tienen los navegadores de añadir scripts de otros dominios para acceder a la información.
Sin embargo, el método JSONP implica un poco de colaboración extra de nuestro servidor de datos, de forma que el servidor se tiene que encargar de meter dichos datos como parámetro de una función que sí existe en nuetro código. Es posible que andes un poco perdido, y seguramente con un ejemplo lo veas mucho mejor, de forma que vamos a crear un pequeño código que extraiga información de la API de flickr

Cómo funciona: Accediendo a la API de flickr

Si navegáis un poco por la referencia de la API de flickr, podréis encontrar fácilmente unejemplo de URL para recibir datos en formato JSON.
Si accedéis al ejemplo, veréis que nos devuelve algo como esto (puede que cambien la API Key por seguridad, de todas formas la podéis ver desde la sección ejemplos de su documentación):
view plaincopy to clipboardprint?
  1. jsonFlickrApi({"method":{"_content":"flickr.test.echo"}, "format":{"_content":"json"}, "api_key":{"_content":"85f336549dc99ecaddc7d6cdea76b4b8"}, "stat":"ok"})  
Ya os imagináis qué ocurriría si incluímos esta URL(script) en nuestro head del código HTML, ¿no?. Se llamará a la función jsonFlickrApi y de parámetro tendríais toda la información en formato JSON. De esta forma, conseguimos extraer datos del servidor remoto y ademásprocesarlos, que es lo que realmente nos interesa.
Todos los servidores que expongan API en JSON, deberían (o deben) aceptar un parámetro GET en el cual le especificamos cual será en nombre de la función recibidora en nuestro código local, para que pueda ser más dinámico y personalizable. En el caso de flickr este parámetro se llama jsoncallback.
Si un servidor quiere exponer JSON para aplicaciones Javascript, debe de permitirteespecificar el nombre de tu función mediante un parámetro en la URL.
De modo que si accedemos a la siguiente URL: http://www.flickr.com/services/rest/?method=flickr.test.echo&format=json&api_key=fb3db427da4bcda80f74ea31c64cd64d&jsoncallback=mifuncion
Obtendremos lo siguiente:
view plaincopy to clipboardprint?
  1. mifuncion({"method":{"_content":"flickr.test.echo"}, "format":{"_content":"json"}, "api_key":{"_content":"85f336549dc99ecaddc7d6cdea76b4b8"}, "jsoncallback":{"_content":"mifuncion"}, "stat":"ok"})  
En el caso de flickr, también nos permite añadir el parámetro nojsoncallback=1 con el cual sólo nos devolverá los datos, pero esto no nos valdrá de nada en Javascript, porque como he comentado antes, los datos se reciben y…ahí se quedan, no se pueden tratar de ninguna forma.

Crear una llamada AJAX a un servidor remoto

Bien, ahora que conocemos y entendemos la teoría, ha llegado la hora de la práctica. Como ya sabréis, las llamadas se van a hacer gracias a la inserción de etiquetas <script> (Javascript) que nos permiten descargar contenido de servidores remotos. Sabiendo esto, un código sencillo podría ser el siguiente:
view plaincopy to clipboardprint?
  1. function jsonp(url){  
  2.     var head = document.getElementsByTagName("head")[0];  
  3.     var script = document.createElement("script");  
  4.     script.type = "text/javascript";  
  5.     script.src = url;  
  6.     head.appendChild(script);  
  7. }  
  8. function json_process(data){  
  9.     alert(data);  
  10.     console.info(data);  
  11. }  
  12. function test(){  
  13.     var url = "http://www.flickr.com/services/rest/?method=flickr.test.echo&format=json&api_key=fb3db427da4bcda80f74ea31c64cd64d&jsoncallback=json_process";  
  14.     jsonp(url);  
  15. }  
De forma que si ahora llamamos a la función test() en el evento load de la página, recibiremos los datos del dominio de flickr, este sería nuestro ejemplo Hola Mundo (sin Hola mundo :P ).
A continuación vamos a crear un código que pueda ser reusable y más completo.

Mejorando nuestro código

El ejemplo anterior estaba bien para un primer contacto, sin embargo, en una aplicación real, este código sería bastante malo y difícil de mantener, por eso vamos a añadir las siguientes mejoras:
  • Permitir especificar la callback como parámetro de la función
  • Permitir especificar cuál es el nombre del parámetro en el cual se especifica la callback a llamar, en el caso de flickr es jsoncallback
  • Añadir parámetros adicionales a la URL de forma sencilla
  • Encapsular el código en el espacio de nombres JSONP (‘global variables are evil‘)
¡Pues manos a la obra! Antes de nada os pongo el esquema para luego ir rellenándolo poco a poco:
view plaincopy to clipboardprint?
  1. var JSONP = {  
  2.     /*Guarda la referencia al script*/  
  3.     script: null,  
  4.     /*Guarda las opciones especificadas*/  
  5.     options: {},  
  6.     /*Realiza la llamada a la url especificada siguiendo las opciones pasadas*/  
  7.     call: function(url, options){  
  8.     },  
  9.     /*Recibe el resultado*/  
  10.     process: function(data) {  
  11.     }  
  12. };  

El método call

Es el que se encarga de enviar una petición al servidor siguiendo los parámetros que le especificamos. El segundo parámetros debe de ser un objeto con las siguientes propiedades:
  • callback: el nombre de la función a ejecutar cuando llege la respuesta
  • callbackParamName: nombre del parámetro GET que define el nombre de la función a llamar (recuerda jsoncallback en el caso de flickr)
  • params: un objeto del tipo clave-valor que será serializado e incrustado en la URL
Un ejemplo del objeto options es:
view plaincopy to clipboardprint?
  1. var options = {  
  2.     callback:  mifuncion,  
  3.     callbackParamName: "jsoncallback",  
  4.     params: { a=1, b=2}  
  5. };  
Y este sería el código del método:
view plaincopy to clipboardprint?
  1. call: function(url, options){  
  2.     //Comprobacion de las opciones  
  3.     if(!options) this.options = {};  
  4.     this.options.callback = options.callback || function(){};  
  5.     this.options.callbackParamName = options.callbackParamName || "callback";  
  6.     this.options.params = options.params || [];  
  7.     //Determina si se debe añadir el parámetro separado por ? o por &  
  8.     var separator = url.indexOf("?") > -1? "&" : "?";  
  9.     /*Serializa el objeto en una cadena de texto con formato URL*/  
  10.     var params = [];  
  11.     for(var prop in this.options.params){  
  12.         params.push(prop + "=" + encodeURIComponent(options.params[prop]));  
  13.     }  
  14.     var stringParams = params.join("&");  
  15.     //Crea el script o borra el usado anteriormente  
  16.     var head = document.getElementsByTagName("head")[0];  
  17.     if(this.script){  
  18.         head.removeChild(script);  
  19.     }  
  20.     script = document.createElement("script");  
  21.     script.type = "text/javascript";  
  22.     //Añade y carga el script, indicandole que llame a JSONP.process  
  23.     script.src = url + separator + stringParams + (stringParams?"&":"") + this.options.callbackParamName +"=JSONP.process";  
  24.     head.appendChild(script);  
  25. }  
Supongo que no hay mucho que explicar a mayores del código, puesto que simplemente mejora la base anterior, pero la lógica apenas cambia.

El método process

Este es la función que nuestro código fuerza a ejecutar una vez cargados los datos, aquí se pueden hacer tareas comunes de deserialización de datos, comprobación de errores, etc. En nuestro caso sólo sirve de puente entre el servidor y la callback especificada.
view plaincopy to clipboardprint?
  1. process: function(data) {  
  2.     /*Aquí pueden hacerse tareas comunes de tratamiento de los datos*/  
  3.     this.options.callback(data);  
  4. }  
Una vez vistos los métodos, vamos a probarlo.

Probando el código

Ahora que ya tenemos definida nuestra pequeña clase, podemos hacer llamadas a dominos remotos de una forma muy sencilla:
view plaincopy to clipboardprint?
  1. function test(){  
  2.     var url = "http://www.flickr.com/services/rest/?method=flickr.test.echo&format=json&api_key=fb3db427da4bcda80f74ea31c64cd64d";  
  3.     var params = {  
  4.         callback: function(data){ alert(data);},  
  5.         callbackParamName: "jsoncallback",  
  6.     };  
  7.     JSONP.call(url, params);  
  8. }  
O aprovechando al máximo nuestro código:
view plaincopy to clipboardprint?
  1. function test(){  
  2.     var url = "http://www.flickr.com/services/rest";  
  3.     var params = {  
  4.         callback: function(data){ alert(data);},  
  5.         callbackParamName: "jsoncallback",  
  6.         params: {  
  7.             method: "flickr.test.echo",  
  8.             format: "json",  
  9.             api_key: "fb3db427da4bcda80f74ea31c64cd64d"  
  10.         }  
  11.     };  
  12.     JSONP.call(url, params);  
  13. }  
¿Verdad que así queda mucho más curioso y limpio? Además de ser un código mucho más fácil de reutilizar, ya que lo puedes añadir a cualquier proyecto y seguiría funcionando perfectamente.

JSONP en jQuery

La verdad que ni me he molestado en buscar plugins para incluír JSONP en jQuery (de hecho nuncha he tenido la necesidad real de utilizar JSONP). Pero ya que hace un par de semanas os explicaba cómo crear un plugin de jQuery, he decidido convertir el código anterior y así, predicar con el ejemplo :)
view plaincopy to clipboardprint?
  1. (function($){  
  2. $.extend(  
  3. {  
  4.     jsonp: {  
  5.         script: null,  
  6.         options: {},  
  7.         call: function(url, options) {  
  8.             var default_options = {  
  9.                 callback: function(){},  
  10.                 callbackParamName: "callback",  
  11.                 params: []  
  12.             };  
  13.             this.options = $.extend(default_options, options);  
  14.             //Determina si se debe añadir el parámetro separado por ? o por &  
  15.             var separator = url.indexOf("?") > -1? "&" : "?";  
  16.             var head = $("head")[0];  
  17.             /*Serializa el objeto en una cadena de texto con formato URL*/  
  18.             var params = [];  
  19.             for(var prop in this.options.params){  
  20.                 params.push(prop + "=" + encodeURIComponent(options.params[prop]));  
  21.             }  
  22.             var stringParams = params.join("&");  
  23.             //Crea el script o borra el usado anteriormente  
  24.             if(this.script){  
  25.                 head.removeChild(script);  
  26.             }  
  27.             script = document.createElement("script");  
  28.             script.type = "text/javascript";  
  29.             //Añade y carga el script, indicandole que llame al metodo process  
  30.             script.src = url + separator + stringParams + (stringParams?"&":"") + this.options.callbackParamName +"=jQuery.jsonp.process";  
  31.             head.appendChild(script);  
  32.         },  
  33.         process: function(data) { this.options.callback(data); }  
  34.     }  
  35. });  
  36. })(jQuery)  
Por tanto ahora también podréis hacer lo siguiente (suponiendo que la librería jQuery está incluída):
view plaincopy to clipboardprint?
  1. function test(){  
  2.     var url = "http://www.flickr.com/services/rest";  
  3.     var params = {  
  4.         callback: function(data){ alert(data);},  
  5.         callbackParamName: "jsoncallback",  
  6.         params: {  
  7.             method: "flickr.test.echo",  
  8.             format: "json",  
  9.             api_key: "fb3db427da4bcda80f74ea31c64cd64d"  
  10.         }  
  11.     };  
  12.     $.jsonp.call(url, params);  
  13. }  
Como siempre con jQuery nos queda todo muy fácil de utilizar.
Tweet

No hay comentarios :

Publicar un comentario

Entrada más reciente Entrada antigua Inicio
Suscribirse a: Enviar comentarios ( Atom )

Sígueme en las Redes Sociales



Follow @daycry9

Daycry web


Donaciones

Suscribirse a

Entradas
Atom
Entradas
Comentarios
Atom
Comentarios

Datos personales

daycry
Ver todo mi perfil

Entradas populares

  • Crear archivos PHP ejecutables por terminal UBUNTU
    En este apartado vamos a explicar como ejercutar archivos PHP a través del terminal de Ubuntu. Lo primero que tendríamos que hacer es inst...
  • Pâginación PHP con Librería Zebra Pagination
    En este blog voy a comentar un tema que se utilizan en casi todas las páginas web que existen, y es el tema de la paginación. La paginaci...
  • PHPExcel - Codeigniter
    Este post trata de la integración de la librería PHPExcel en Codeigniter, aunque se podría aplicar a cualquier librería, como por ejemplo mP...
  • PHP- Operaciones con fechas - Sumar Horas, minutos y segundos
    Ejemplo para añadir o sumar un número determinado de hora/s, minuto/s, segundo/s a una fecha en php. Con la función strtotime se puede ...
  • Codeigniter - Múltiples conexiones a base de datos
    Este tema es uno de los temas primordiales sobre el framework Codeigniter, ya que en alguna ocación nos hemos visto obligados a recoger dato...

© Espacio Daycry - Espacio de programación 2013 . Powered by Bootstrap , Blogger templates and RWD Testing Tool