Hoy en día cada vez más webs publican sus APIs para que puedan ser consumidas por aplicaciones de terceros. Sin embargo, en Javascript nos econtramos con una restricción que impide hacer peticiones entre dominios vía AJAX. En este tutorial os voy a mostrar cómo podéis hacer esas llamadas mediante un nuevo concepto llamado JSONP.

Autor:

Intentando mejorar cada día en este mundo tan cambiante de la programación. Sígueme en Twitter o en GitHub. Actualmente estoy disponible para contratar.
Descarga Demostración

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 de extension 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:

<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 un ejemplo 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):

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ás procesarlos, 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 permitirte especificar 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:

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:

function jsonp(url){
	var head = document.getElementsByTagName("head")[0];
	var script = document.createElement("script");
	script.type = "text/javascript";
	script.src = url;
	head.appendChild(script);
}
function json_process(data){
	alert(data);
	console.info(data);
}
function test(){
	var url = "http://www.flickr.com/services/rest/?method=flickr.test.echo&format=json&api_key=fb3db427da4bcda80f74ea31c64cd64d&jsoncallback=json_process";
	jsonp(url);
}

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:

var JSONP = {
	/*Guarda la referencia al script*/
	script: null,
	/*Guarda las opciones especificadas*/
	options: {},
	/*Realiza la llamada a la url especificada siguiendo las opciones pasadas*/
	call: function(url, options){
	},
	/*Recibe el resultado*/
	process: function(data) {
	}
};

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:

var options = {
	callback:  mifuncion,
	callbackParamName: "jsoncallback",
	params: { a=1, b=2}
};

Y este sería el código del método:

call: function(url, options){
	//Comprobacion de las opciones
	if(!options) this.options = {};
	this.options.callback = options.callback || function(){};
	this.options.callbackParamName = options.callbackParamName || "callback";
	this.options.params = options.params || [];
	//Determina si se debe añadir el parámetro separado por ? o por &
	var separator = url.indexOf("?") > -1? "&" : "?";
	/*Serializa el objeto en una cadena de texto con formato URL*/
	var params = [];
	for(var prop in this.options.params){
		params.push(prop + "=" + encodeURIComponent(options.params[prop]));
	}
	var stringParams = params.join("&");
	//Crea el script o borra el usado anteriormente
	var head = document.getElementsByTagName("head")[0];
	if(this.script){
		head.removeChild(script);
	}
	script = document.createElement("script");
	script.type = "text/javascript";
	//Añade y carga el script, indicandole que llame a JSONP.process
	script.src = url + separator + stringParams + (stringParams?"&":"") + this.options.callbackParamName +"=JSONP.process";
	head.appendChild(script);
}

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.

process: function(data) {
	/*Aquí pueden hacerse tareas comunes de tratamiento de los datos*/
	this.options.callback(data);
}

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:

function test(){
	var url = "http://www.flickr.com/services/rest/?method=flickr.test.echo&format=json&api_key=fb3db427da4bcda80f74ea31c64cd64d";
	var params = {
		callback: function(data){ alert(data);},
		callbackParamName: "jsoncallback",
	};
	JSONP.call(url, params);
}

O aprovechando al máximo nuestro código:

function test(){
	var url = "http://www.flickr.com/services/rest";
	var params = {
		callback: function(data){ alert(data);},
		callbackParamName: "jsoncallback",
		params: {
			method: "flickr.test.echo",
			format: "json",
			api_key: "fb3db427da4bcda80f74ea31c64cd64d"
		}
	};
	JSONP.call(url, params);
}

¿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 :)

(function($){
$.extend(
{
	jsonp: {
		script: null,
		options: {},
		call: function(url, options) {
			var default_options = {
				callback: function(){},
				callbackParamName: "callback",
				params: []
			};
			this.options = $.extend(default_options, options);
			//Determina si se debe añadir el parámetro separado por ? o por &
			var separator = url.indexOf("?") > -1? "&" : "?";
			var head = $("head")[0];
			/*Serializa el objeto en una cadena de texto con formato URL*/
			var params = [];
			for(var prop in this.options.params){
				params.push(prop + "=" + encodeURIComponent(options.params[prop]));
			}
			var stringParams = params.join("&");
			//Crea el script o borra el usado anteriormente
			if(this.script){
				head.removeChild(script);
			}
			script = document.createElement("script");
			script.type = "text/javascript";
			//Añade y carga el script, indicandole que llame al metodo process
			script.src = url + separator + stringParams + (stringParams?"&":"") + this.options.callbackParamName +"=jQuery.jsonp.process";
			head.appendChild(script);
		},
		process: function(data) { this.options.callback(data); }
	}
});
})(jQuery)

Por tanto ahora también podréis hacer lo siguiente (suponiendo que la librería jQuery está incluída):

function test(){
	var url = "http://www.flickr.com/services/rest";
	var params = {
		callback: function(data){ alert(data);},
		callbackParamName: "jsoncallback",
		params: {
			method: "flickr.test.echo",
			format: "json",
			api_key: "fb3db427da4bcda80f74ea31c64cd64d"
		}
	};
	$.jsonp.call(url, params);
}

Como siempre con jQuery nos queda todo muy fácil de utilizar.

Conclusión final

Finalmente comentar que este tutorial surge a raíz de una labor de investigación (research quedaría más cool) en mi fin de semana, por lo que si véis algún fallo sea de seguridad, de lógica o incluso de conceptos, espero que lo reportéis y lo corregiré.

Espero que el código que compartido os ayude o incluso os motive a crear aplicaciones basadas en este tipo de API, ya que es realmente interesante por ser asíncrono y por permitir la comunicación con otro servidor directamente sin pasar por el nuestro (eliminamos una capa).

¡Hasta la próxima!

¿Necesitas desarrollar un proyecto web o para móviles? ¡Estamos disponibles!

Visitar Cokidoo

Cokidoo, los creadores de Ontuts, desarrollamos proyectos tecnológicos centrados en redes sociales y aplicaciones web, aplicaciones móviles y consultoría web y bases de datos.

Somos jóvenes, inquietos, versátiles, apasionados por la innovación y enfocados en las nuevas tecnologías. Con Ontuts tratamos de compartir nuestro conocimiento adquirido en los distintos proyectos, ayudando a la comunidad y mostrando nuestra capacidad tecnológica.

Si necesitas un presupuesto sin compromiso, estamos disponibles, no dudes en contactar con nosotros.

Comentarios en esta publicación (12 comentarios)

¿Te ha gustado esta publicación? ¡Puedes compartir tu opinión con todos nosotros! Simplemente pincha aquí mismo.

Te superas con cada tutorial… este me ha parecido además de cojonudo muy muy útil. CRACK!

Lolo

estupendo tutorial,
¿que tendríamos que hacer en la página que sirve los datos para enviarlos correctamente?

si no se usa XML no es AJAX(XML)!

Muy bueno! Muchas gracias, era justo lo que estaba buscando!

Tengo una duda: ¿Cómo podríamos llamar a el callback en el caso de que el servidor web no aceptase ningún parámetre “callback”. Por ejemplo: en Google Maps Elevations Service

http://code.google.com/apis/maps/documentation/elevation/

devuelve resultados en json, pero no acepta ningún parámetro callback. ¿Habría alguna forma de poder llamarlo desde javascript sin necesidad de pasar por un servidor web que haga de proxy? Llevo varios días pegándome con ello.

Gracias y enhorabuena.

Hola jesus barrio, mirando la documentación, la API de momento solo soporta JSON y XML, por lo que tendrás que esperar para usar JSONP

Un saludo

Mauricio

corre en el internet explore pero no en mozila y Google Chrome no corre

lo que hgo es verificar si existe la cedula en bd.

$(document).ready(function(){

$(“#enviar”).click(function(c){

$.post(“Class/Usuarios/VeriCedula.php”, { cedu:$(“#cedula”).attr(‘value’) },
function(data){

if(data == 1)
{
$(“#cedula”).addClass(“Requerido”);
$(“#ECedula”).html(“Cedula Ya Registrada”);
}
});

})

})

Cirio

Es posible tener más de una llamada simultanea a jsonp en la misma página? estoy probando pero no logro hacerlo!

Siul

Esto sirve para llamar con JQUERY MOBILE ?
para utilizar en una app convirtiendola con PHONEGAP

Lomblolfsooca

¿Quién y dónde para organizar este verano en su licencia, parte de la información.

Sam

hola amigo, me parece muy util la informacion que has publicado, pero seria posible que hicieras un ejemplo de como hacer un webservice para que acepte jsonp?

Es decir, he hecho varios web services en asp.net y si intento llamar un jsonp siempre me tira error.
el webservice esta ( por dar un ejemplo ) hospedado en mi maquina virtual llamada Desarrollo, los servicios estan corriendo, pero desde la maquina fisica, llamada Sam, intento accesar a los servicios y tira error, es normal porque estan en distintos dominios, es por eso que intento llamar un jsonp pero igual me arroja error.

Un saludo.