:tocdepth: 2 AJAX ==== Principe ++++++++ Jusqu'à maintenant... --------------------- * Les applications côté client que nous avons écrites sont intégralement chargées avec la page HTML. * Toute communication avec le serveur se fait par rechargement de la page... * ... et donc réinitialisation de l'application (ou d'une autre). Ce serait bien si... -------------------- * Nos applications pouvaient communiquer avec le serveur, * sans avoir à se recharger totalement : - conservation de l'état de l'application - fluidité pour l'utilisateur * **Mais** cela suppose que le code Javascript ne se bloque pas en attendant la réponse du serveur → appel *asynchrone* (événements) .. index:: AJAX Définition ---------- AJAX signifie * :en:`Asynchronous` * :en:`Javascript` * :en:`And` * :en:`XML` \... mais en pratique, on peut échanger n'importe quoi avec le serveur, pas uniquement du XML. .. index:: fetch, Request Fetch ----- La fonction fetch__ permet d’aller chercher une ressource sur un serveur de façon asynchrone. * Paramètre(s) : - une chaîne de caractères (l’URL) ou un objet Request__ - un objet javascript contenant des options pour la requête HTTP comme le verbe (GET par défaut), les en-têtes. * Valeur de retour: une promesse qui se résout en objet Response__ __ https://devdocs.io/dom/fetch __ https://devdocs.io/dom/request __ https://devdocs.io/dom/response .. index:: Response L'objet Response ---------------- Il représente la réponse à une requête HTTP. * Propriétés : - body : un flux vers le contenu de la ressource récupérée, - status : le code statut de la réponse, - ok : un booléen qui indique si c’est une réponse considérée comme un succès (standard HTTP : codes 200 - 299). * Méthode : - json() : retourne une promesse qui se résout en objet javascript, en tableau ou en variable issus de l’analyse de contenu d’une réponse en :ref:`format JSON `. .. index:: Promise, promesse L’objet Promise --------------- * Une promesse représente la résolution (ou l’échec) éventuel(le) d’une opération asynchrone et son résultat. * C’est le bon moyen pour représenter le résultat d’une opération asynchrone qui peut s’exécuter de façon favorable et retourner ce qu’on attend, ou qui renvoie une erreur en cas d’échec. * Elle a trois états possibles : - :en:`pending` (en cours) : quand la promesse n’a pas encore ni échoué ni été réalisée, - :en:`fulfilled` (réalisée) : quand la promesse s’est réalisée avec succès, - :en:`rejected` (rejetée) : quand la promesse a échoué. La méthode :en:`then` --------------------- Ajoute un gestionnaire de succès et un gestionnaire d’échec à la promesse. * Elle prend en paramètres une ou deux fonctions de :en:`callback`. * Le premier paramètre sera la fonction appelée lorsque la promesse sera réalisée. * Le second paramètre (optionnel) sera la fonction appelée lorsque la promesse échoue. * Ces deux fonctions ne pourront avoir qu’un paramètre : la valeur retournée en cas de succès ou d’échec. .. hint:: :en:`then` se comporte comme un :en:`listener` qui écoute le changement d’état de la promesse. .. nextslide:: .. code:: //send HTTP request fetch('json/chapitre1.json') //then waits for the Response .then( function(response){//the request succeeds console.log('success:' + response.status); }, function(error){ //network problem console.log('fetch fails:'); console.log(error); } ); Chaîne de promesses ------------------- Les promesses peuvent être enchaînées. .. code:: fetch('json/chapitre1.json') //then waits for the Response .then( function(response){return response.json();}, function(error){console.log(error) ;}) //then wait for the json parsing .then( function(data){console.log(data); }, function(error){console.log(error);}//format error ); La méthode :en:`catch` ---------------------- * Elle peut remplacer l'utilisation d'un second paramètre avec la méthode :en:`then` (plus lisible). * Elle peut permettre de "factoriser" la gestion d’erreurs. .. code:: //send HTTP request fetch('json/chapitre1.json') .then(function(response){ //the request succeeds console.log('success:' + response.status); }) .catch(function(error){ //network problem console.log('fetch fails:'); console.log(error); }); Les méthodes :en:`then` et :en:`catch` -------------------------------------- * Elles fonctionnent comme des "transformateurs": elles font passer le résultat de la promesse à travers les :en:`callback`, et retournent une promesse à leur tour. * Il est donc toujours possible d’enchaîner un :en:`then` après un :en:`then`. .. code:: fetch('toto') .then((x) => 42) // "enveloppe" 42 dans une promesse .then((y) => console.log(y)); // Affiche 42 * :en:`then` avec un seul paramètre ne transforme que les promesses "succès", et laisse passer telles quelles les promesses "erreur". * catch fait l’inverse : elle laisse passer les promesses "succès", et transforme les promesses "erreur". Gestion "factorisée" des erreurs -------------------------------- .. code:: fetch('json/chapitre1.json') //then waits for the Response .then(function(response){ return response.json(); }}) //then wait for the json parsing .then(function(data){ //do what you have to do with data }) //captures and displays all errors .catch(function(error){ console.log(error); }) //avoids duplicated code in then () and catch () .finally(function(){ console.log('Process completed'); }); Exceptions personnalisées ------------------------- Il est inutile de parser le contenu d’une réponse "404 Not Found". .. code:: fetch('json/chapitre1.json') .then(function(response){ if(response.ok){return response.json();} else{throw("Err " + response.status);} }) //then waits for the json parsing .then(function(data){ //do what you have to do with data }) //captures any rejected promise .catch(function(error){console.log(error); }); .. hint:: L’exception lancée dans le :en:`callback` du 1er :en:`then` sera "convertie" en "promesse échouée", et donc sera propagée jusqu’à l’appel de :en:`catch`. AJAX et Sécurité ++++++++++++++++ Imaginez... ----------- * Vous visitez le site ``pirate.net``. * La page de garde contient un script qui effectue une requête sur ``gmail``. * Comme vous êtes déjà identifié, ``gmail`` renvoie une page HTML contenant la liste de vos messages récents. * Le script analyse ces données, les renvoie à ``pirate.net``, et bien sûr n'affiche rien de ce qu'il vient de faire. * Les pirates connaissent maintenant une partie de vos contacts, et savent de quoi vous parlez avec eux. .. nextslide:: * Mais ``pirate.net`` aurait pu tout aussi bien faire une requête sur ``gmail`` qui - efface la liste de vos contacts, - envoie un mail à votre place, - change votre mot de passe... * Bien sûr, il pourrait également tenter une connexion sur - les principaux réseaux sociaux, - les sites de banque, - etc... .. _same_origin_policy: .. index:: CORS Règles de sécurité ------------------ Pour éviter ce genre d'attaque, ``fetch`` possède des limitations : * elle ne peut pas accéder aux URLs en ``file:``, * le code émis par un serveur ne peut se connecter qu'à ce même serveur (:en:`Same Origin Policy`), * ou à un serveur autorisant explicitement les accès par d'autres scripts que les siens (standard CORS_). .. _CORS: http://www.w3.org/TR/cors/ CORS en deux mots ````````````````` * Lorsqu'un script, provenant d'un serveur ``srv1``, émet une requête AJAX vers un serveur ``srv2``, cette requête contient un en-tête supplémentaire : ``Origin: http://srv1`` * Si le serveur ``srv2`` fait confiance à ``srv1``, il inclut dans sa réponse l'en-tête suivant : ``Access-Control-Allow-Origin: http://srv1`` * ... et le navigateur autorise alors le script à accéder à la réponse. * Dans le cas contraire, le navigateur refuse de transmettre la réponse au script. Du point de vue du script, c'est comme si la requête avait échoué. .. nextslide:: .. admonition:: Remarques * Une solution consiste à utiliser un proxy tel que https://crossorigin.me/ . * Auparavant, d'autres méthodes ont été proposées pour permettre des accès :en:`cross-domain`, comme JSONP. .. TODO: lien JSONP ? Objets Javascript et JSON +++++++++++++++++++++++++ Principe -------- * Javascript possède une notion d'objet qui se trouve à mi-chemin entre les objets du Java, les structures du C, et les dictionnaires du Python. * Contrairement à Java ou C, ces objets peuvent être créés sans classe/type prédéfini. .. code:: const p = { "nom": "Doe", "prénom": "John", "age": 42 }; .. hint:: Les attributs peuvent contenir n'importe quel type, y compris bien sûr des types complexes comme des tableaux ou d'autres objets. Utilisation ----------- * Pour accéder à un attribut d'un objet, on utilise la même notation « pointée » qu'en Java ou en C :: let n = p.nom ; * Mais on peut également utiliser la notation « indicée » comme en Python :: let pr = p['prénom'] ; * Cette dernière est utile lorsque : - le nom de l'attribut comporte des caractères non autorisés, ou - le nom de l'attribut est contenu dans une variable. .. nextslide:: .. admonition:: Remarque Cela dit, les caractères accentués (comme dans ``prénom`` ci-dessus) *sont* autorisés. On aurait donc, dans ce cas, pu écrire :: let pr = p.prénom ; Modification ------------ .. code:: p.prénom = "Jane" ; p.adresse = "42, Main road" ; delete p.age ; * On peut ajouter de nouveaux attributs (ex: ``adresse``) * On peut supprimer les attributs (ex: ``age``). * Si on tente d'accéder à un attribut inexistant, on obtient la valeur ``undefined``. .. _JSON: .. index:: JSON JSON ---- JSON (:en:`Javascript Object Notation`) est un sous-ensemble du langage Javascript, utilisé comme format de données sur le Web. Données supportées par JSON --------------------------- * Objet ``{}`` * Tableaux ``[]`` * Chaînes de caractères * Nombres * Booléens * ``null`` .. nextslide:: .. admonition:: Syntaxe * Les chaînes de caractères doivent être entourées par des guillemets doubles (les guillemets simples ne sont pas supportés). * Les nom des attributs des objets doivent être entre guillemets doubles. * NB: ``null`` est supporté, mais pas ``undefined`` * Des structures complexes peuvent être représentées en JSON : tableaux d'objets, objets contenant d'autres objets... Format d'échange ---------------- * Étant directement basé sur Javascript, JSON est bien sûr très utilisé dans ce langage, * mais il l'est également dans la plupart des autres langages de programmation, où il a largement remplacé XML (plus simple, plus compact). Utilisation ----------- * ``JSON.parse`` prend une chaîne de caractères JSON et renvoie l'objet correspondant. * ``JSON.stringify`` prend un objet supporté par JSON et renvoie la chaîne de caractères correspondante. TP : Livre dont vous êtes le héros ++++++++++++++++++++++++++++++++++ Sujet ----- * Récupérez `cette archive`__, qui contient les différents chapitres d'un livre dont vous êtes le héros, sous forme de structures JSON. * Hébergez ces fichiers dans votre ``public_html``, avec une application Javascript permettant de parcourir ce livre. __ _static/json.tar.gz .. hint:: Pour les liens du livre, il vous est conseillé * d'utiliser des liens HTML internes (``href="#xyz"``) qui n'entrainent pas de rechargement de la page, et * d'intercepter les changements en vous abonnant à l'événement ``hashchange`` de ``window``, et en utilisant ``window.location`` pour déterminer le contenu à afficher. .. nextslide:: .. admonition:: Remarque Une alternative consisterait à intercepter les clics sur les liens, pour savoir quand charger et afficher les éléments du livre. Cette solution peut sembler plus naturelle pour le programmeur, mais elle crée une expérience utilisateur moins bonne : * la "navigation" à l'intérieur du livre n'est pas stockée dans l'historique du navigateur ; * par conséquent, le bouton "retour" ne fonctionne pas ; * un rechargement de la page redémarre au premier élément ; * il n'est pas possible de mettre un signet sur l'endroit où l'on se trouve. Annexe ++++++ .. index:: async, await ECMAScript 2017: async/await ---------------------------- .. code:: async function downloadFirstChap(){ try{ let response = await fetch('json/chapitre1. json'); let data = await response.json() ; //do what you have to do with data } catch(error){ console.log(error); } } * Pour utiliser :en:`await`, une fonction doit être :en:`async` * :en:`await` met en pause l’exécution de la fonction :en:`async` tant que la promesse n’est pas résolue et "retourne" sa valeur en cas de succès. .. nextslide:: * Cela permet d’utiliser un simple try/catch pour gérer les erreurs .. code:: async function downloadFirstChap(){ try{ let response = await fetch('json/chapitre1. json'); let data = await response.json(); //do what you have to do with data } catch(error){ console.log(error); } } .. warning:: Si on oublie le :en:`await` devant une fonction qui retourne une promesse, la variable contient une promesse, pas son résultat quand cette promesse est résolue → source de bug parfois difficile à détecter !