Thématiques :

Comprendre JSONP et l'accès de données à distance en javascript

Posé ici le samedi 31 janvier 2009 à 00:56 par Jay Salvat

JSONP !

Dans un précédent article sur un traducteur Javascript et Ajax utilisant l'API Google Translate, j'abordais brièvement le JSONP et en recommandais le support aux développeurs de petits webservices JSON.

Clément O. me demandait plus d'explications dans l'un de ses commentaires. Je rebondis sur cette demande pour essayer d'apporter quelques approfondissements sur ce qu'est le JSONP et les différentes manières travailler avec.

Au niveau Client

Pour des raisons de sécurité les requêtes Ajax ne peuvent être exécutée que sur le même domaine que la page d'appel. Essayez de faire une requête Ajax sur un serveur distant et vous obtiendrez rapidement une erreur de ce type : Access to restricted URI denied" code: "1012".

Pour palier ce problème il existe deux principales solutions. La première est d'utiliser du code au niveau serveur, PHP par exemple, pour faire la jonction entre le webservice et la requête Ajax. La requête Ajax appellera le script PHP sur le même domaine qui se chargera d'appeler à son tour le serveur distant. Ca fonctionne mais rend le script client dépendant du script serveur et demande de manipuler plusieurs langages et portions de code.

La seconde solution est de ruser et d'appeler le serveur distant par une balise HTML script qui lui n'a pas de limitation au niveau du domaine.

<script type="text/javascript" src="http://www.sitedistant.com/api.php?param=hello">
</script>

De cette manière, on accède aux informations JSON distantes mais deux problèmes se posent.
L'appel des webservices se fait généralement à un moment précis et non forcement à l'ouverture de la page. On devra passer par la création dynamique de la balise script au moment de l'appel. En voici la méthode.

<script type="text/javascript">
    script      = document.createElement("script");
    script.type = "text/javascript";
    script.src  = "http://www.sitedistant.com/api.php?param=hello";
    document.getElementsByTagName("head")[0].appendChild(script);
</script>

Le tag script est créé, pointant sur le serveur distant et inclus dans la balise head. Pour plus de confort, intégrons cette création de tag dans une fonction getRemoteDate(url) qui nous permettra d'appeler le script quand bon nous semblera.

<script type="text/javascript">
    function getRemoteData(url) {
        script      = document.createElement("script");
        script.type = "text/javascript";
        script.src  = url;
        document.getElementsByTagName("head")[0].appendChild(script);
    }
    getRemoteData('http://www.sitedistant.com/api.php?param=hello');
</script>

Premier problème réglé.
Seulement, même ainsi inclus dans la page, du simple code JSON retourné ne produira aucun effet, voire une erreur. C'est là qu'entre en scène le JSONP (JSON with padding, c'est-à-dire préformaté). Le JSONP est une manière de formatter le JSON délivré par le serveur de webservice afin qu'il puisse être interprété et utilisé par le client. Généralement un paramètre callback est passé au serveur, lui indiquant le nom d'une fonction réceptrice. La fonction réceptrice est ajoutée et recevra les données du serveur distant.

<script type="text/javascript">
    function getRemoteData(url) {
        script      = document.createElement("script");
        script.type = "text/javascript";
        script.src  = url;
        document.getElementsByTagName("head")[0].appendChild(script);
    }
    // la fonction réceptrice, ici "myFunction"
    function myFunction(response) {
        alert(response.data);
    }
    // le nom de la fonction réceptrice est passé en paramètre "callback"
    getRemoteData('http://www.sitedistant.com/api.php?param=hello&callback=myFunction');
</script>

Si le webservice supporte le JSONP tout se passera à merveille et les données seront reçues et passées à la fonction demandée, ici myFunction, et ainsi exécutée. Les principaux webservices actuels prennent cette méthode en compte, les webservices Google et Yahoo! entre autres.

Côté serveur

Rien de plus simple à intégrer. L'usage veut que le paramètre se nomme callback (ou jsonp sur certains services). Il suffit de prendre en compte ce paramètre et d'encapsuler les données JSON dans une fonction du même nom. Un cours exemple :

<?php   
$json = "{ data: 'Plein de données utiles' }";
if (isset($_GET['callback'])) {
    echo $_GET['callback'].'('.$json.');';
} else {
    echo $json;
}
?>

Ce qui renverra soit :

{ data: 'Plein de données utiles' }

Soit les mêmes données dans la fonction demandée :

myFunction({ data: 'Plein de données utiles' });

jQuery

jQuery est une excellente librairie qui propose une fonction d'abstraction de requêtes Ajax. Ici rien de particulier à faire, la fonction Ajax peut être appelé de la même manière que le serveur soit distant ou nom.

<script type="text/javascript">
$(function() {
    $.ajax({
        dataType:'jsonp',
        url: 'http://www.monsitedistant.com/api.php',
        data: { param:'hello' },
            success:function(response) {
                alert("Réponse : "+ response.data);
            }
    });
});
</script>

Il suffira de préciser dataType:'jsonp' pour que jQuery se charge de faire tout le boulon précédemment décrit en toute discrétion :

  • création et inclusion de la balise script
  • création d'une fonction temporaire au nom unique de type jsonp1234567
  • ajout automatique du paramètre callback=jsonp1234567 à la requête

La grande classe !

Conclusion

Je pense avoir fait le tour. Le principale soucis est qu'il n'est possible que de faire des requêtes de type GET. Il est impossible de reproduire une méthode POST par ce système.

axe.png

Crédits images : David Arazim

Trackbacks

0 trackback

Commentaires

7 commentaires

Ah effectivement on y voit un peu plus clair ! Jusque là je me servais de la 1ère méthode pour rapatrier des données via la fonction getscript de jQuery. JSONP permet aussi de mieux organiser ses données, mais dommage qu'il ne permette pas de POST.

Merci pour les précisions :)

1. Par Julien le samedi 31 janvier 2009 à 11:00

Super article, merci.

2. Par Clem le lundi 02 février 2009 à 19:36

Merci beaucoup pour la réponse sous forme d'article. J'en demandais pas tant, quel honneur :)

3. Par Clément O. le lundi 02 février 2009 à 21:08

Petite précision : avec un tel appel à la méthode $.ajax(), le paramètre ajouter à l'url du service est toujours "callback=..."

On peut souhaiter changer celà car certains services utilisent un autre nom pour ce paramètre (par exemple Flicker : "jsoncallback=...").

On peut donc rajouter jsonp : "jsoncallback" dans les paramètrse de la méthode $.ajax().

$.ajax({
dataType:'jsonp',
jsonp : 'jsoncallback', // 'callback' par défaut
url: 'Monsitedistant.com/api.php',
success: function(data){...}
});

On peut également utiliser $.getScript() sans ajouter de paramètres, il suffit de mettre un "?" dans l'url du service, jQuery le remplacera par jsonp1234...

4. Par Florian Cargoët le jeudi 19 février 2009 à 19:02

Merci de ce complément d'information Florian.
Je ne connaissais pas la propriété jsonp permettant de changer le nom de la fonction de callback.

5. Par Jay Salvat le jeudi 19 février 2009 à 19:51

De rien, je l'ai découvert en voulant comprendre les rouages pendant que j'écrivais mon article. J'ai plongé dans le code de jQuery et j'ai trouvé ça :

// Handle JSONP Parameter Callbacks
if ( s.dataType == "jsonp" ) {

Donc j'ai creusé un peu et déchiffré le code :

s.url += (s.url.match(/\?/) ? "&" : "?") + (s.jsonp || "callback") + "=?";

Il prend l'url, y ajoute un & ou un ? selon l'url et ajoute jsonp si il est défini sinon "callback" et enfin colle un =? à la fin.

Ensuite, il crée la callback, remplace le =? et on connait la suite...

6. Par Florian Cargoët le vendredi 20 février 2009 à 21:37

Pour info, j'ai fait un plugin pour jQuery qui palie à certaines limitation de $.ajax.

Code.google.com/p/jquery-jsonp

En espérant que ça puisse aider ceux qui, comme moi, se sont attaqués à des data api récalcitrantes.

7. Par Julian Aubourg le vendredi 20 mars 2009 à 17:44

Obligatoire. Vrai nom apprécié.

Il ne sera ni affiché, ni spammé.

Votre blog ou votre site web.

Constructif, courtois et correctement écrit. SMS proscrit. Merci.