Sicuramente quanche volta vi è successo: si tenta di fare una richiesta AJAX in un dominio diverso da quello che si sta lavorando e restituisce un errore. In particolare, l’errore che dà Firefox è il seguente: Access to restricted URI denied” code: “1012
Questo è normale, perché per ragioni di sicurezza, i browser non consentono tali chiamate.

Allora, perché ci sono le API che espongono i loro dati in JSON? Bene per questa domanda ci sono almeno due risposte:

  • L’API può anche essere utilizzata da qualsiasi programma diverso da un browser web, come uno script in php, jsp o asp.
  • C’è davvero un trucco per accedere ai dati da Javascript. Si tratta di un nuovo concetto chiamato JSONP.

Vediamo in dettaglio cosa è JSONP, e come sempre, con qualche esempio di utilizzo.

[nextpage title=”Informazioni su JSONP”] JSONP sta per JavaScript Object Notation with Padding, cioè una forma di estensione JSON per supportare chiamate tra domini. Secondo wikipedia, questo termine è stato proposto nel blog MacPython nel 2005 e da allora ha cominciato ad essere utilizzato su tutti gli ambienti con applicazioni web 2.0.
Il funcionamento in cui essa si fonda, è che con il nostro codice HTML siamo in grado di caricare uno script da un dominio remoto, in modo che se abbiamo un nostro dominio miodominio.com, siamo in grado di fare perfettamente il seguente:
Copia codice

<script type="text/javascript" src="http://www.dominioremoto.com/dati.js"></script>
Quindi, se questo script restituisce i dati in formato JSON, possiamo processarli senza alcun problema. Ma cosa succede con questi dati? Come possiamo accedere a loro? Beh si può semplicemente avere informazioni, ma non è possibile accedervi.
JSONP si basa sulla capacità dei browser di aggiungere dei script da altri domini per accedere a tali informazioni.
Tuttavia, il metodo JSONP comporta un po’ di collaborarione aggiuntiva dal nostro server di dati, nel senso che il server dovrà occuparsi di mettere questi dati come parametri in una funzione che esiste nel nostro codice client. È possibile che ti sia un po’ perso, ma sicuramente con un esempio si può vedere meglio, quindi cerchiamo di creare un piccolo codice per estrarre informazioni attraverso l’API di flickr.

Come funziona: Accesso alle API di Flickr
Se si naviga un po’ per il riferimento delle API di flickr, si può facilmente trovare un esempio di URL per ricevere i dati in formato JSON.
Se si accede all’esempio, vedrete che tornerà qualcosa del genere (è possibile che la chiave API cambi, per sicurezza; in ogni caso si può vedere dalla sezione di esempi della sua documentazione):
Copia codice

jsonFlickrApi({
  "method":{
    "_content":"flickr.test.echo"
  },
  "format":{
    "_content":"json"
  },
  "api_key":{
    "_content":"ca961e5f59eebb3339a87c168e390998"
  },
  "stat":"ok"
});
Ora immaginate cosa accadrebbe se si includiamo l’URL (script) nel nostro head del codice HTML della pagina. Si chiamerà la funzione jsonFlickrApi e come parametro si avranno tutte le informazioni in formato JSON. In questo modo, siamo riusciti ad estrarre i dati dal server remoto e anche di processarli, che è quello che realmente ci interessa.
Tutti i server che espongono le API in formato JSON, dovrebbero (o devono) accettare un parametro GET in cui si specificherà quale sarà in nome della funzione ricevitrice nel nostro codice locale, in modo da poter essere più dinamico e personalizzabile. Nel caso di flickr questo parametro si chiama jsoncallback.

Se un server vuole esporre JSON per applicazioni Javascript, deve permettere di specificare il nome della tua funzione mediante un parametro nell’URL.

Quindi, se accediamo al seguente URL: http://www.flickr.com/services/rest/?method=flickr.test.echo&format=json&api_key=fb3db427da4bcda80f74ea31c64cd64d&jsoncallback=miaFunzione
Otterremo il seguente codice

Copia codice

miaFunzione({
  "method":{
    "_content":"flickr.test.echo"
  },
  "format":{
    "_content":"json"
  },
  "api_key":{
    "_content":"85f336549dc99ecaddc7d6cdea76b4b8"
  },
  "jsoncallback":{
    "_content":"miaFunzione"
  },
  "stat":"ok"
});
Nel caso di Flickr, ci permette anche di aggiungere il parametro nojsoncallback = 1 con il quale ritorneranno solo dati, ma questo non conterà niente in Javascript, perché come scritto prima, i dati si ricevono e … lì rimangono, non può essere trattato in alcun modo.
Creare una chiamata AJAX ad un server remoto
Bene, ora che conosciamo e capiamo la teoria, è il momento della pratica. Come sapete, le chiamate devono essere effettuate attraverso l’inserimento del tag -script- (Javascript) che ci permettono di scaricare contenuti da server remoti. Sapendo questo, un semplice codice potrebbe essere scritto come segue:
Copia codice

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);
}
Quindi, se noi ora chiamiamo la funzione test() su l’evento di caricamento della pagina, riceveremo i dati dal dominio di flickr, questo sarebbe il nostro esempio ‘Ciao Mondo’ (senza Ciao Mondo).
Poi creeremo un codice che possa essere riutilizzabile e più completo.
[nextpage title=”Migliorando il nostro codice”] L’esempio precedente andava bene per un primo contatto, però , in un’applicazione reale, questo codice sarebbe piuttosto povero e difficile da mantenere, quindi cercheremo di aggiungere i seguenti miglioramenti:

  • Permettere di specificare la callback come parametro della funzione
  • Permettere di specificare qual è il nome del parametro nel quale si specifica la callback da richiamare in automatico, in caso che flickr sia jsoncallback
  • Aggiungere ulteriori parametri all’URL in modo semplice
  • Incapsulare il codice nello spazio dei nomi JSONP
    (‘le variabili globali sono un male‘)

Quindi, al lavoro! Prima di tutto ho messo lo schema per poi andare a riempirlo mano a mano:

Copia codice

var JSONP = {
  // Salva il riferimento allo script
  script: null,
  // Salva le opzioni specificate
  options: {},
  // Effettua la chiamata all'url specificato
  // seguendo le opzioni passate
  call: function(url, options){
  },
  // Riceve il risultato
  process: function(data) {
  }
};
Il metodo call
È il metodo che si occupa d’inviare la richiesta al server seguendo i parametri specificati. Il secondo parametro deve essere un oggetto con le seguenti proprietà:

  • callback: Il nome della funzione da eseguire quando arriva la risposta
  • callbackParamName: nome del parametro GET che definisce il nome della funzione da chiamare (ricorda jsoncallback nel caso di Flickr)
  • params: un oggetto del tipo chiave-valore da serializzare e incorporarlo nell’URL

Un esempio dell’oggetto options sarebbe:

Copia codice

var options = {
  callback:  miaFunzione,
  callbackParamName: "jsoncallback",
  params: {
    a=1,
    b=2
  }
};
E questo sarebbe il codice del metodo:
Copia codice

call: function(url, options){
  // Verifica le opzioni
  if(!options) this.options = {};
  this.options.callback = options.callback || function(){};
  this.options.callbackParamName = options.callbackParamName || "callback";
  this.options.params = options.params || [];
  // Determina se aggiungere un parametro
  // separato da ? o da &
  var separator = url.indexOf("?") > -1? "&" : "?";
  // Serializza l'oggetto in una stringa di testo
  // con formato URL
  var params = [];
  for(var prop in this.options.params){
    params.push(prop + "=" + encodeURIComponent(options.params[prop]));
  }
  var stringParams = params.join("&");
  // Crea lo script o cancella l'utilizzato in precedenza
  var head = document.getElementsByTagName("head")[0];
  if(this.script){
    head.removeChild(script);
  }
  script = document.createElement("script");
  script.type = "text/javascript";
  // Aggiunge e carica lo script,
  // indicando di chiamare JSONP.process
  script.src = url + separator + stringParams + (stringParams?"&":"") + this.options.callbackParamName +"=JSONP.process";
  head.appendChild(script);
}
Credo che non ci sia molto da spiegare, poiché migliora solo la base precedente, solo la logica cambia appena.
Il metodo process
Questa è la funzione che nostro codice forza a eseguire una volta che i dati vengono caricati: qui si possono fare i compiti ordinari di deserializzazione di dati, controllo degli errori, ecc. Nel nostro caso serve solo da ponte tra il server e il callback specificato.
Copia codice

process: function(data) {
  // Qui si possono fare
  // le operazioni comuni per trattare i dati
  this.options.callback(data);
}
Dopo aver visto i metodi, vediamo di provarlo.
[nextpage title=”Provando il codice”] Ora che abbiamo definito la nostra piccola classe, siamo in grado di effettuare chiamate a domini remoti in modo molto semplice:
Copia codice

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 approfittando in pieno il nostro codice:
Copia codice

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);
}
Non è che così rimane più curioso e pulito? Oltre ad essere un codice molto più facile da riutilizzare, è possibile implementarlo su qualsiasi progetto e continua a funzionare perfettamente.

JSONP in jQuery
In verità non mi sono preso la briga di cercare dei plugin per includere JSONP in jQuery (di fatto non ho mai avuto bisogno di utilizzare JSONP). Ma da qualche precedente articolo avevo spiegato come creare un plugin per jQuery, ho deciso di rendere il codice di sopra un plugin, e quindi dare il buon esempio:
Copia codice

(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 se aggiungere un parametro
        // separato da ? o da &
        var separator = url.indexOf("?") > -1? "&" : "?";
        var head = $("head")[0];
        // Serializza l'oggetto in una stringa di testo
        // con formato URL
        var params = [];
        for(var prop in this.options.params){
          params.push(prop + "=" + encodeURIComponent(options.params[prop]));
        }
        var stringParams = params.join("&");
        // Crea lo script o cancella l'utilizzato in precedenza
        if(this.script){
          head.removeChild(script);
        }
        script = document.createElement("script");
        script.type = "text/javascript";
        // Aggiunge e carica lo script,
        // indicando di chiamare il 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);
Così ora è anche possibile effettuare quanto segue (assumendo che la libreria jQuery sia già inclusa:
Copia codice

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);
}
Come sempre con jQuery ci rimane molto più facile da usare.
Conclusione
Infine, commentare che questa guida nasce da un lavoro di ricerca (research sarebbe più fico) nel mio week-end, quindi se vedete qualche baco sia sulla sicurezza di logica o persino di concetti, mi auguro di essere informato per riparare.
Spero che il codice che ho condiviso vi aiutino o addirittura vi motivino a creare applicazioni basate su questo tipo di API, giacché è davvero interessante per essere asincrona e per permettere la comunicazione con un altro server direttamente senza passare dal nostro.
Fino alla prossima volta!