JSON-P

JSON-P est une utilisation particulière de JSON.

Lorsqu’on fait un appel ajax via une XHR (XMLHttpRequest), on est soumis à la règle de sécurité dite du « same origin policy » qui précise qu’une page dans le navigateur n’accepte des requètes XHR que si elles interrogent des serveurs situés dans ce domaine.

Ceci commence à changer sur les navigateurs récents avec le CORS(cross-origin resource sharing, norme W3C dont le support ne s’est pas encore généralisé).

Cette restriction du « same origin policy » ne s’appliquent pas sur la baliseSCRIPT. Une page HTML peut en effet comporter des balises SCRIPTtéléchargeant des fichiers sources provenant de n’importe quel domaine.

L’idée est donc d’utiliser des balises SCRIPT pour télécharger du JSON de façon à s’affranchir de la contrainte du « same origin policy ». Néanmoins, charger du JSON de cette façon ne fonctionne pas directement. En effet, un fragment tel que

<SCRIPT SRC="www.mondomaine.com/getJSON"></SCRIPT>

téléchargera effectivement le flux JSON rendu par le serveur,  mais n’en fera rien ; il ne sera ni mémorisé dans une variable javascript, ni utilisable par une fonction javascript.

C’est ainsi qu’est né, en 2005, l’idée du JSON-P ou JSON Padding, il s’agit de demander au serveur d’encapsuler sa réponse JSON dans un appel de fonction, fonction qui est précisée dans l’url d’appel (cette fonction joue le rôle de callback), . Cela pourra donner par exemple (si le serveur utilise l’attribut jsonp_callback pour préciser le callback) :
<SCRIPT SRC="www.mondomaine.com/getJSONP?jsonp_callback=maFonction"></SCRIPT>
Lors de l’appel de l’url précédente, le serveur générera alors le code javascript suivant :

maFonction({ .. le code JSON proprement dit })

qui sera appelé lorsque la balise aura terminé le téléchargement de ce code. La fonction maFonction aura un unique argument qui est le code JSON ramené

function maFonction(jsonData){
...
}

Avantages

Un avantage de cette approche c’est que chaque client du serveur peut préciser sa propre fonction et donc utiliser le code du serveur de la façon qu’il souhaite : le serveur ne choisit pas comment il encapsule le code JSON, c’est bien la page dans le navigateur qui précise la fonction qu’il faudra appeler.

Un second avantage est qu’il est possible d’utiliser une politique de cache sur l’url en question (le fragment JSONP pouvant être conservé dans le cache du navigateur).
Un autre avantage est lié au nombre de connexions simultanées qu’autorise le serveur. Les navigateurs actuels limitent le nombre de connexions simultanées sur le même domaine. Cette contrainte s’applique donc sur les appels XHR qui sont bloqués tant qu’ils restent d’autres requêtes sur le même domaine.

Cette contrainte ne s’applique pas sur la balise SCRIPT : les appels JSONP peuvent donc avoir lieu simultanément à partir du moment où ils appellent des hosts différents.

Inconvénients

Problèmes liés à la sécurité

JSON-P pose de sérieux problèmes de sécurité liées au attaques du type cross-site request forgery. Par exemple, une page malveillante peut récupérer des données JSON appartenant à un autre domaine, ces données (par exemple des données privées de l’utilisateur logué sur l’autre domaine) sont alors évaluées dans le contexte de la page malveillante.

Pour éviter ce genre de problème, il faut que le serveur ne fasse pas de réponse quand il détecte que la requête d’appel n’est pas valide, par exemple en effectuant une vérification du referrer de la request ou en effectuant une vérification syntaxique du callback utilisé.

Un autre problème, lié à l’injection javascript est qu’un serveur malveillant introduise dans sa réponse du code javascript permettant de récupérer des informations personnelles de l’utilisateur du navigateur. Il existe plusieurs tentatives pour éviter ce problème. Par exemple on pourrait introduire au niveau du navigateur un parser spécifique qui vérifierait la syntaxe de la réponse JSON-P, potentiellement en spécifiant un type mime spécifique à la balise SCRIPT. (cf. http://www.json-p.org/).

Gestion d’erreur

La gestion d’erreur avec JSONP, dans le cas où la requête n’arrive pas, est plus complexe que pour les appels XHR. Il existe à cet effet les événementsonerror, mais ils ne sont pas supportés par tous les navigateurs (en fait, la plupart des navigateurs supporte l’événement onerror sur la window, mais sous IE, l’événement onerror sur la window n’est déclenché que sur une erreur javascript, pas sur une erreur de chargement).

Optimisations

En effet, l’utilisation la plus courante de JSONP se fait via l’insertion dynamique de balises SCRIPT. Ainsi, plutôt que de créer dynamiquement une XHR, on insère une balise SCRIPT avec une url JSON-P. Dans ce type d’utilisation de JSON-P, Google, dans son implantation de Google Instant Preview précise deux conseils intéressants :

  • Si on insère la balise SCRIPT directement (viadocument.createElement), certains navigateurs afficheront que la page est encore en cours de chargement même si toutes les requêtes sont terminées. Pour éviter ce problème, il faut encapsuler la création de la balise dans un window.setTimeout.
  • Une fois que l’appel est terminé et la fonction de callback appelée, il est préférable de mettre à null l’attribut SRC de la balise SCRIPT et de retirer la balise. Sur certains navigateurs, l’accumulation de balisesSCRIPTsemble ralentir l’exécution au fur et à mesure.

Exemple

Un programme minimum en PHP pourrait être :

<?php
data = « { name: \ »kate\ » } »;
echo $_GET['jsonp_callback'] . ‘(‘ . $data . ‘);’;
?>

où jsonp_callback est le paramètre de la requête qui contient le nom de la méthode à appeler.

On peut par exemple appeler le serveur dans une page HTML contenant :

<SCRIPT SRC="http://localhost/json_sample1.php?jsonp_callback =alert"></SCRIPT>

évidemment, on utilisera plutôt une création dynamique de la balise SCRIPT, par exemple via un fragment javascript tel que :

var url="http://localhost/json_sample1.php?param=12";
url+="&jsonp_callback=myCallback";

// Création dynamique d'un tag SCRIPT avec la bonne url
var tagScript=document.createElement("SCRIPT");
tagScript.src=url;
var cible=document.getElementById("monAppel");
cible.appendChild(tagScript);

Les fichiers de test correspondants sont disponibles dans cette archive.

<?php
data = « { name: \ »kate\ » } »;
echo $_GET['jsonp_callback'] . ‘(‘ . $data . ‘);’;
?>

<?phpdata = « { name: \ »kate\ » } »;echo $_GET['jsonp_callback'] . ‘(‘ . $data . ‘);’;?>

Ce contenu a été publié dans Javascript, JSON, avec comme mot(s)-clé(s) , , , , , , , . Vous pouvez le mettre en favoris avec ce permalien.