Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

JSONP e le richieste cross-domain

Un metodo per superare l'ostacolo della same-domain-policy
Un metodo per superare l'ostacolo della same-domain-policy
Link copiato negli appunti

La cosiddetta same-domain-policy è una restrizione presente nei recenti browser che impedisce a script scaricati dalla rete di accedere, tramite qualsiasi tipo di richiesta HTTP, a risorse che si trovano su server diversi rispetto a quello iniziale che ha inviato lo script. Questa inibizione non riguarda solo host diversi tra di loro, ma anche processi in ascolto su porte diverse della stessa macchina.

Questo significa che da un qualsiasi script che faccia uso di AJAX e dell'oggetto XmlHttpRequest non è possibile accedere a qualsiasi tipo di risorsa (HTML, XML, JSON, testo) che si trovi su un server (e in ascolto su una porta) diverso da quello di partenza; uno script scaricato dal dominio google.com potrà accedere solamente a file che rispondano a URL che inizino con google.com.

Quella che può sembrare una forte limitazione nello sviluppo di applicazioni web rappresenta invece un fattore importante per la sicurezza dei nostri computer. Basta infatti pensare a quante  possibilità esisterebbero per far eseguire al nostro browser codice maligno scaricato da chissà quale server semplicemente aprendo un sito che a prima vista può sembrare innoquo.

Il same-domain-policy è stato introdotto molto tempo fa, all'epoca di Netscape Navigator 2.0 ed è stato attualizzato fino ai browser di recente sviluppo come Firefox e Safari. Ovviamente Internet Explorer segue una sua politica che si discosta da quello che ormai è diventanto uno standard de facto. Il browser di casa Microsoft si affida alle cosiddette security zones permettendo infatti all'utente di scaricare script residenti anche su macchine diverse appartenenti alla stessa LAN.

JSONP: una possibile soluzione

JSONP è l'acronimo di JSON with Padding e rappresenta una tecnica che permette di ovviare a questa limitazione permettendo a un browser di accedere, ovviamente con alcuni limiti, a risorse remote indipendentemente dall'host di origine. 

Il meccanismo di funzionamento è semplice ma alquanto particolare. Affrontiamolo con calma.

Uno dei modi per accedere a script on-demand è quello di modificare il DOM della pagina inserendo un nuovo tag <script> all'interno della testata del documento (DOM Lazy Loading - per approfondire si veda questo articolo uscito tempo fa su HTML:it). Questo approccio, nonstante permetta di accedere a file JavaScript remoti, presenta un grosso limite: non si ha infatti  controllo sul codice generato da questa richiesta che verrà eseguoto automaticamente dall'interprete JavaScript appena lo riceverà dal server. Analizziamo un piccolo esempio.

Se all'interno di file JavaScript è presente la definizione di un oggetto libro:

{
	title: “I promessi sposi”,
	author: “Manzoni”
}

esso verrà interpretato automaticamente senza dare la possibilità al programmatore di modificare il flusso normale di esecuzione del codice e quindi di elaborare i dati ricevuti. Le informazioni verranno lette dall'interprete JavaScript ma non verranno in nessun modo fornite al programmatore che non potrà praticamente elaborarle in nessun modo.

Proprio per questa “mancanza di controllo” l'approccio basato sul DOM Lazy Loading non è molto diffuso all'interno di applicazioni web.

JSONP può essere visto come un'estensione a questo approccio che permette di invocare una funzione di callback automatizzata (come spesso viene fatto per le richieste AJAX basate su XmlHttpRequest) al ricevimento di dati. Immaginiamo infatti lo stesso file JavaScript precedente con una leggera modifica:

displayBook({
	title: "I promessi sposi",
	author: "Manzoni"
});

In questo caso grazie all'invocazione automatica appena ricevuto lo script l'interprete eseguirà la funzione displayBook (correttamente definita nel nostro script prima di effettuare la richiesta) avendo a disposizione i nuovi dati scaricati in formato JSON. Immaginiamo infatti di avere nelle nostre pagine la funzione così definita:

function displayBook(book) {
	alert(book.title);
}

Cosi facendo abbiamo utilizzato del tutto dati ricevuti da host differenti da quello di partenza! 

Esiste però un ulteriore problema. Come è possibile istruire il server riguardo alla funzione da invocare? Senza questa informazione, lo script ricevuto dal server, avrebbe generato un errore se nella nostra applicazione non fosse stata definita la funzione displayBook.

La soluzione alquanto banale è quella di avere uno script server-side in grado di generare codice JavaScript dinamico in base ad un parametro ricevuto dal server tramite il metodo GET. Questo script, una volta ottenuti i dati da un datastore, presenta i dati in formato JSON anticipandoli però da una funzione in base a questo parametro ottenuto precedentemente dal client.

A prima vista un approccio di questo tipo può sembrare complesso ma il successivo esempio servirà a chiarire le idee su questa tecnica.

Implementiamo un servizio remoto JSONP

Come al solito il miglior modo per apprendere una nuova tecnologia è quello di realizzare un seppur semplice esempio. In questo paragrafo realizzeremo una piccola applicazione che simulerà una piccola biblioteca composta da diverse sedi, ognuna con un host web diverso e che deve comunicare alla sede centrale l'elenco dei libri posseduti tramite appunto JSONP.

Prima di iniziare con lo sviluppo è necessario preparare nel miglior modo l'ambiente di test. Visto che dobbiamo simulare richieste da host differenti possiamo procedere in diversi modi. Le modalità che suggerisco però sono due:

  • configurare diversi virtualhost su Apache in modo da far rispondere la stessa macchina a host differenti (per esempio biblio1, biblio2, ecc...);
  • configurare temporaneamente il file hosts creando dei veri e propri alias all'interno del sistema operativo (per gli utenti Windows il file da modificare si trova in c:/windows/system32/drivers/etc mentre per gli utenti delle principali distribuzioni Linux /etc/hosts)

In entrambi i modi è quindi possibile accedere alla macchina locale anche passando da URL assoluti che nel caso dell'esempio saranno del tipo http://biblioX/libri.php che senza l'ausilio di JSONP sarebbero totalmente inaccessibili da script scaricati da http://localhost/jsonp.

Dopo questa configurazione iniziale, possiamo partire con l'implementazione vera e propria.

Client JSONP

Per realizzare un client JavaScript che acceda a servizi JSONP basta realizzare una funzione che permetta di inserire nuovi tag <script> all'interno della pagina e una funzione che gestisca i dati ricevuti dal server. Guardiamo il piccolo esempio:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<script>
function jsonp_request(url) { 
	var head = document.getElementsByTagName("head")[0];
	var script = document.createElement("SCRIPT"); 
	script.type = "text/javascript";
	script.src = url + "?cb=update_table";
	head.appendChild(script);
} 
function update_table(data) { 
	var container = document.getElementsByTagName("div")[1];
	var html = ""; 
	for(var i = 0; i<data.length; i++) {
		html += "<span>"+data[i].id+"</span>";
		html += "<span>"+data[i].title+"</span>"
		html += "<span>"+data[i].author+"</span>";
	}
	container.innerHTML += html;
}
window.onload = function() { 
	for(var i = 1; i<5; i++) { 
		jsonp_request("http://biblio"+i+"/libri.php");
	}
}
</script> 
<style>
div { width: 300px; }
div span { display:block; float: left; width: 100px; }
div span.th { color: blue; }
</style>
</head> 
<body>
<div>
	<span class="th">Id</span>
	<span class="th">Libro</span>
	<span class="th">Autore</span>
</div> 
<div>
</div>

Il markup della pagina è abbastaza semplice: sono presenti due div con larghezza fissa; il primo servirà come testata per la nostra pseudo-tabella mentre il secondo sarà il container delle informazioni scaricate da remoto. Non ho utilizzato una tabella per il semplice motivo che su Internet Explorer non è possibile inserire righe in maniera dinamica se non attraverso hack che è sempre meglio non utilizzare. Grazie a poche regole CSS è possibile simulare una tabella utilizzando solamente <div> e <span>.

Focalizzando la nostra attenzione su JavaScript notiamo due funzioni.

jsonp_request permette di aggiungere un tag <script> all'interno della testata del file HTML. Aspetto importante di questa funzione è l'aggiunta all'url ricevuto di un parametro GET di nome cb che contiene una stringa che rappresenta la funzione che verrà chiamata come callback. Questo parametro lo ritroveremo quando tratteremo la parte server-side.

La funzione che viene passata come parametro è la funzione update_table che come si può capire dal nome permette di aggiungere righe alla tabella per mostrare i dati appena ricevuti nel formato JSON dal server remoto. Essa crea una stringa HTML ciclando sui vari libri e la appende al <div> container.

Per ultimo viene assegnato il listener all'evento window.onload. Questo si occupa di effettuare la richiesta presso 5 biblioteche diverse (identificate dal differente host nel formato definito in precedente http://biblioX/) richiedendo il file libri.php che sarà appunto il nostro script server-side.

Una volta aperta la pagina possiamo notare con Firebug che effettivamente sono presenti 5 nuovi tag <script> che puntano al nostro server remoto e che la tabella è stata effettivamente riempita con i dati indipendentemente dal loro host di provenienza.

Server JSONP

Lo script server-side è abbastanza banale e non presenta criticità. Guardiamo subito il sorgente:

In questo esempio i dati sono stati scritti direttamente nel codice per semplicità, ma ovviamente in un'applicazione davvero utile i libri dovrebbero essere salvati in un datastore sia esso un database o un file xml. Una volta riempito il vettore $books esso viene encodato in JSON tramite la funzione json_encode e la stringa ritornata viene anticipata con il parametro cb ricevuto e le corrette parentesi in modo da generare una chiamata alla funzione JavaScript.

Se il server ricevesse una chiamata a libri.php?cb=update_table l'output generato sarebbe infatti cosi:

update_table({ .... } );

In questo modo la funzione di callback può essere invocata automaticamente una volta scaricato tutto lo script sulla macchina client.

Ovviamente è stato mostrato lo script utilizzato solamente per la biblioteca numero 1, ma le altre si comportano esattamente allo stesso modo.

Guardiamoci attorno

Librerie JavaScript

Nonostante implementare un client JSONP non sia un compito molto arduo (e l'esempio visto ne è la prova) è sempre meglio, una volta capita la tecnica e i meccanismi nascosti, utilizzare una libreria che permetta una migliore suddivisione dei compiti all'interno dell'applicazione e una maggiore sicurezza e configurabilità.

Ultimamente sono molti i framework che hanno aggiunto al loro set di funzioni anche alcuni componenti in grado di effettuare richieste JSONP.

Nelle ultime versioni di JQuery la funzione $.getJSON è stata estesa ed è ora disponibile anche per richieste JSONP. L'esempio di utilizzo proposto nella documentazione ufficiale è simile a questo:

var url = "http://api.flickr.com/services/feeds/photos_public.gne?tags=cat&tagmode=any&format=json&jsoncallback=?"
$.getJSON(url, function(data){
  showMyData(data);
});

Basta infatti creare un URL assoluto che punti ad un host diverso da quello originario e che contenga un punto interrogativo al posto del parametro callback. In automatico il motore interno di JQuery sostituirà questo punto interrogativo con una funzione interna definita a runtime (e distrutta una volta completata la richiesta) che invocherà la funzione passata come parametro a $.getJSON. Con questo approccio JQuery “nasconde” l'intera implementazione di JSONP all'utilizzatore.

Un buon esempio funzionante che utilizza le API di Flickr è presente su questa pagina.

Anche Dojo Toolkit presenta alcune funzionalità per realizzare applicazioni basate su JSONP com per esempio la funzione dojo.io.script.get() che permette di scaricare script utilizzando il tag apposito. Per approfondire vi rimando a questo documento.

Servizi web JSONP

La crescita di questa tecnica ha in qualche modo costretto i principali portali web che non volessero restare indietro tecnologicamente ad esportare alcuni servizi e API anche utilizzando JSONP. In questo paragrafo analizzeremo alcuni di questi servizi disponibili a chiunque.

Quello che presenta maggiori funzionalità da questo punto di vista è senza dubbio Flickr, la community di video e foto sharing. Ciascun servizio esporto è disponibile anche in formato JSONP tramite l'utilizzo del parametro format=json. Oltre all'invio di questo parametro è necessario allegare alla richiesta il metodo server-side da invocare (basta dare un'occhiata a questa guida per trovare il metodo richiesto), la API key richiedibile in maniera totalmente gratuita e ovviamente la callback (di default viene utilizzata jsonFlickrApi ma comunque consiglio di utilizzarne una propria).

Per approfondire sull'utilizzo di JSONP il miglior punto di partenza rimane la documentazione ufficiale.

Un altro sito che permette l'utilizzo remoto tramite JSONP è il famoso motore di ricerca Yahoo che tra i tanti servizi esposti permette anche di effettuare ricerche utilizzando il proprio motore di ricerca. Per approfondimenti vi consiglio di leggere questo documento.

Ti consigliamo anche