IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

JavaScript Éloquent

Une introduction moderne à la programmation
Image non disponible


précédentsommairesuivant

XIV. Requêtes HTTP

Comme mentionné dans le chapitre 11, les communications sur le World Wide Web se passent via le protocole HTTP. Une simple requête pourrait ressembler à ça :

 
Sélectionnez
GET /files/fruit.txt HTTP/1.1
Host: eloquentjavascript.net
User-Agent: Le Navigateur Imaginaire

Ce qui demande au serveur eloquentjavascript.net le fichier files/fruit.txt. En plus, la requête spécifie que la version de HTTP utilisée est 1.1 (la version 1.0 est encore utilisée et fonctionne légèrement différemment). La ligne Host et User-Agent suivent un même modèle : elles commencent par un mot qui identifie l'information qu'elle contient, suivi par un deux-points et l'information elle-même. Ces lignes sont appelées « en-têtes ». L'en-tête User-Agent dit au serveur quel navigateur (ou autre type de programme) a lancé la requête. D'autres en-têtes sont souvent utilisés tout le long, par exemple pour déclarer quels types de documents le client peut comprendre, ou pour spécifier le langage qu'il préfère.

Après avoir reçu la requête ci-dessus, le serveur peut envoyer la réponse suivante :

 
Sélectionnez
HTTP/1.1 200 OK
Last-Modified: Mon, 23 Jul 2007 08:41:56 GMT
Content-Length: 24
Content-Type: text/plain
 
pommes, oranges, bananes

La première ligne indique encore la version du protocole HTTP utilisée, suivie par l'état de la requête. Dans ce cas, le code d'état est 200, ce qui signifie « OK, rien d'anormal ne s'est produit, je vous envoie les fichiers ». Viennent ensuite quelques en-têtes indiquant (dans ce cas) la dernière fois que le fichier a été modifié, sa longueur et son type (texte brut). Après l'en-tête, vous obtenez une ligne blanche suivie par le fichier lui-même.

En plus des requêtes commençant par GET, indiquant que le client veut seulement récupérer le document, le mot Post peut aussi être utilisé pour indiquer que des informations seront envoyées avec la requête dont on attend que le serveur les traite d'une manière ou d'une autre.(20)

Lorsque vous cliquez sur un lien, soumettez un formulaire ou encouragez de quelque manière votre navigateur à aller sur une nouvelle page, il fera une requête HTTP et déchargera immédiatement l'ancienne page pour afficher le nouveau document. Dans les situations classiques, c'est exactement ce que vous voulez - c'est la manière dont le Web fonctionne traditionnellement. Parfois cependant, un programme JavaScript veut communiquer avec le serveur sans avoir à recharger la page. Le bouton « Load » de la console, par exemple, peut charger des fichiers sans quitter la page.

Pour être capable de faire des choses comme celles-là, le programme JavaScript doit faire une requête HTTP lui-même. Les navigateurs actuels fournissent une interface pour faire cela. Comme pour ouvrir une fenêtre, cette interface est sujette à certaines restrictions. Pour empêcher les scripts de faire quoi que ce soit d'effrayant, il est uniquement permis de faire une requête HTTP sur le domaine d'où vient la page actuelle.

Un objet utilisé pour faire une requête HTTP peut, dans la plupart des navigateurs, être créé en faisant new XMLHttpRequest(). Les versions plus anciennes d'Internet Explorer qui inventa originellement cette technique nécessitent de faire new ActiveXObject("Msxml2.XMLHTTP"), ou pour des versions encore plus anciennes new ActiveXObject("Microsoft.XMLHTTP"). ActiveXObject est l'interface d'Internet Explorer à différentes spécificités de ce navigateur. Nous sommes déjà habitués à écrire des fonctions pour prendre en charge les incompatibilités, alors faisons-le encore une fois :

 
Sélectionnez
function makeHttpObject() {
  try {return new XMLHttpRequest();}
  catch (erreur) {}
  try {return new ActiveXObject("Msxml2.XMLHTTP");}
  catch (erreur) {}
  try {return new ActiveXObject("Microsoft.XMLHTTP");}
  catch (erreur) {}
 
  throw new Error("La création de l'objet pour les requêtes HTTP n'a pas pu avoir lieu.");
}
 
show(typeof(makeHttpObject()));

La fonction encapsulatrice essaie de créer l'objet des trois manières en utilisant try et catch pour détecter celles qui échouent. Si aucune des manières ne fonctionne, ce qui peut être le cas avec les plus vieux navigateurs ou les navigateurs avec des paramètres de sécurité stricts, une erreur est signalée.

Maintenant, pourquoi cet objet est-il appelé XML HTTP request ? C'est un nom un peu trompeur. XML est un moyen de stocker des données textuelles. Il utilise des balises et des attributs comme HTML, mais est plus structuré et flexible - pour stocker vos propres sortes de données vous pouvez définir vos propres types de balises XML. Ces objets requêtes HTTP ont certaines fonctionnalités intégrées pour s'occuper de la récupération de documents XML, raison pour laquelle ils ont XML dans leur nom. Ils peuvent cependant gérer également d'autres types de documents, et d'après mon expérience sont utilisés aussi souvent pour des requêtes non XML.

Maintenant que nous avons notre objet HTTP, nous pouvons l'utiliser pour fabriquer une requête.

 
Sélectionnez
var requete = makeHttpObject();
requete.open("GET", "files/fruit.txt", false);
requete.send(null);
print(requete.responseText);

La méthode open est utilisée pour configurer la requête. Dans ce cas, nous choisissons de fabriquer une requête GET pour notre fichier fruit.txt. L'URL donnée ici est facultative, elle ne contient pas la partie http:// ou le nom d'un serveur, ce qui signifie qu'elle va chercher le fichier sur le serveur d'où vient le document courant. Le troisième paramètre, false, sera examiné dans un moment. Après que open a été appelé, la véritable requête peut être faite avec la méthode send. Lorsque la requête est une requête POST, les données à envoyer au serveur (comme une chaîne de caractères) peuvent être passées par cette méthode. Pour les requêtes GET, il y a juste à passer null.

Une fois que la requête a été faite, la propriété responseText de l'objet requête contient le contenu du document récupéré. Les en-têtes que le serveur a renvoyés peuvent être inspectés avec les fonctions getResponseHeader et getAllResponseHeaders. La première cherche un en-tête particulier tandis que la seconde nous donne une chaîne de caractères contenant tous les en-têtes. Ceux-ci peuvent être utiles en certaines occasions pour obtenir des informations supplémentaires sur le document.

 
Sélectionnez
print(requete.getAllResponseHeaders());
show(requete.getResponseHeader("Date"));

Si pour une quelconque raison, vous voulez ajouter des en-têtes à la requête qui est envoyée au serveur, vous pouvez utiliser la méthode setRequestHeader. Elle prend deux chaînes de caractères en arguments, le nom et la valeur de l'en-tête.

Le code de réponse, qui était 200 dans l'exemple, peut être trouvé dans la propriété status. Si quelque chose se passe mal, ce code obscur l'indiquera immédiatement. Par exemple, 404 signifie que le fichier que vous avez demandé n'existe pas. Le statusText contient une description légèrement moins énigmatique de l'état.

 
Sélectionnez
show(requete.status);
show(requete.statusText);

Lorsque vous voulez vérifier si une requête a fonctionné, comparer status avec 200 est en général suffisant. En théorie, le serveur pourrait retourner le code 304 dans certaines situations pour indiquer que l'ancienne version du document stockée par le navigateur dans son « cache » est encore à jour. Cependant, il semble que les navigateurs vous protègent de cela en définissant status à 200 même lorsqu'il vaut 304. Il faut également savoir que si vous faites une requête à travers un protocole non HTTP(21), comme FTP, status ne sera pas utilisable parce que le protocole n'utilise pas les codes d'états HTTP.

Lorsqu'une requête est faite comme dans l'exemple suivant, l'appel à la méthode send ne rend pas la main tant que la requête n'est pas terminée. C'est pratique car cela signifie que responseText est disponible après l'envoi de send et que l'on peut immédiatement l'utiliser. Il y a cependant un problème. Lorsque le serveur est lent ou que le fichier est lourd, faire une requête peut prendre un certain temps. Tant qu'elle est en train d'être faite, le programme attend, ce qui fait que le navigateur tout entier attend. Jusqu'à ce que le programme s'achève, l'utilisateur ne peut rien faire, même pas faire défiler la page. Les pages qui tournent sur un réseau local rapide et fiable pourraient s'en sortir en faisant des requêtes comme cela. Les pages sur l'immense et imprévisible Internet ne peuvent pas, pour ce qui les concerne, en faire autant.

Lorsque le troisième argument de open est true, la requête est définie pour être « asynchrone ». Cela signifie que send rendra la main immédiatement pendant que la requête se fera en arrière-plan.

 
Sélectionnez
requete.open("GET", "files/fruit.xml", true);
requete.send(null);
show(requete.responseText);

Mais attendez un moment et...

 
Sélectionnez
print(requete.responseText);

« Attendez un moment » peut être implémenté avec setTimeout ou quelque chose du même genre, mais il existe un meilleur moyen. Un objet requête a une propriété readyState indiquant l'état dans lequel il se trouve. Il passera à 4 lorsque le document aura été complètement chargé, et aura une valeur inférieure avant cela(22). Pour réagir au changement de cet état, nous pouvons définir la propriété onreadystatechange de l'objet par une fonction. Cette fonction sera appelée à chaque fois que l'état change.

 
Sélectionnez
requete.open("GET", "files/fruit.xml", true);
requete.send(null);
requete.onreadystatechange = function() {
  if (requete.readyState == 4)
    show(requete.responseText.length);
};

Lorsque le fichier récupéré par l'objet requête est un document XML, la propriété responseXML de la requête contiendra une représentation de ce document. Cette représentation fonctionne de la même manière que l'objet DOM examiné dans le chapitre 12, mis à part qu'il n'a pas de fonctionnalités spécifiques au HTML telles que style ou innerHTML. responseXML nous fournit un objet document dont la propriété documentElement fait référence à la balise extérieure du document XML.

 
Sélectionnez
var catalogue = requete.responseXML.documentElement;
show(catalogue.childNodes.length);

De tels documents XML peuvent être utilisés pour échanger des informations structurées avec le serveur. Leur forme - des balises contenant d'autres balises - est souvent très adaptée pour le stockage de choses qu'il serait difficile de représenter seulement avec du texte plat. Cependant, l'interface DOM est plutôt mal fichue pour extraire des informations, et les documents XML sont connus pour être verbeux. Le document fruit.xml a l'air imposant alors qu'il dit seulement « les pommes sont rouges, les oranges sont orange et les bananes sont jaunes ».

Les programmeurs JavaScript ont trouvé une alternative à XML appelée JSON. Elle utilise la notation JavaScript élémentaire des valeurs pour représenter les informations hiérarchisées sous une forme plus minimaliste. Un document JSON est un fichier contenant un seul objet JavaScript ou un tableau, pouvant lui-même contenir d'autres objets, des tableaux, des chaînes de caractères, des nombres, des booléens ou la valeur null. Par exemple, regardez fruit.json :

 
Sélectionnez
requete.open("GET", "files/fruit.json", true);
requete.send(null);
requete.onreadystatechange = function() {
  if (requete.readyState == 4)
    print(requete.responseText);
};

Un morceau de texte comme celui-ci peut être converti en valeur JavaScript normale en utilisant la fonction eval. Des parenthèses doivent être ajoutées autour de lui avant d'appeler eval, car sinon JavaScript pourrait interpréter un objet (entouré d'accolades) comme un bloc de code et engendrer une erreur.

 
Sélectionnez
function evalJSON(json) {
  return eval("(" + json + ")");
}
var fruit = evalJSON(requete.responseText);
show(fruit);

Lorsque vous exécutez eval sur un morceau de texte, vous devez garder à l'esprit que cela signifie que vous permettez à ce bout de texte d'exécuter arbitrairement n'importe quel code. Comme JavaScript ne nous permet de faire des requêtes que sur notre propre domaine, vous connaitrez généralement de manière précise le genre de texte que vous récupérez et cela ne pose pas de problème. Dans d'autres situations, cela peut se révéler dangereux.

Ex. 14.1

Écrivez une fonction appelée serializeJSON qui, lorsqu'on lui fournit une valeur JavaScript, crée une chaîne de caractères avec la représentation JSON de la valeur. Les valeurs simples comme les nombres et les booléens peuvent simplement être données à la fonction String pour les convertir en chaînes de caractères. Les objets et les tableaux peuvent être traités par récursion.

Il faut reconnaître que les tableaux peuvent être sournois car ils sont du type « object ». Vous pouvez utiliser instanceof Array, mais cela fonctionnera uniquement pour les tableaux créés dans la même fenêtre - les autres utiliseront le prototype d'Array des autres fenêtres et instanceof renverra false. Une astuce est de convertir la propriété constructor en chaîne de caractères et de voir si elle contient « function Array ».

Quand vous convertissez une chaîne, vous devez faire attention à échapper ses caractères. Si vous utilisez des guillemets doubles autour de la chaîne, les caractères à échapper sont \", \\, \f, \b, \n, \t, \r, et \v(23).

 
Sélectionnez
function serializeJSON(valeur) {
  function isArray(valeur) {
    return /^\s*function Array/.test(String(valeur.constructor));
  }
 
  function serializeArray(valeur) {
    return "[" + map(serializeJSON, valeur).join(", ") + "]";
  }
  function serializeObject(valeur) {
    var proprietes = [];
    forEachIn(valeur, function(nom, valeur) {
      proprietes.push(serializeString(nom) + ": " +
                      serializeJSON(valeur));
    });
    return "{" + proprietes.join(", ") + "}";
  }
  function serializeString(valeur) {
    var caracteresSpeciaux =
      {"\"": "\\\"", "\\": "\\\\", "\f": "\\f", "\b": "\\b",
       "\n": "\\n", "\t": "\\t", "\r": "\\r", "\v": "\\v"};
    var valeurAvecEchappements = valeur.replace(/[\"\\\f\b\n\t\r\v]/g,
                                function(c) {return caracteresSpeciaux[c];});
    return "\"" + valeurAvecEchappements + "\"";
  }
 
  var type = typeof valeur;
  if (type == "object" && isArray(valeur))
    return serializeArray(valeur);
  else if (type == "object")
    return serializeObject(valeur);
  else if (type == "string")
    return serializeString(valeur);
  else
    return String(valeur);
}
 
print(serializeJSON(fruit));

L'astuce utilisée dans serializeString est similaire à celle que nous avons vue dans la fonction escapeHTML du chapitre 10. Elle utilise un objet pour chercher les substitutions nécessaires pour chacun des caractères. Certaines d'entre elles, comme "\\\\", ont l'air assez étranges parce qu'il est nécessaire de mettre deux antislashes devant chaque antislash dans la chaîne de résultat.

Notez également que les noms de propriétés sont entre guillemets comme des chaînes. Pour certaines d'entre elles ce n'est pas nécessaire, mais c'est préférable pour ceux qui incluent des espaces et d'autres choses curieuses, donc le code joue la sécurité et met tout entre guillemets.

Quand on fait de nombreuses requêtes, on ne souhaite pas, bien entendu, répéter à chaque fois le même rituel open, send, onreadystatechange. Voilà à quoi peut ressembler une fonction encapsulatrice très simple :

 
Sélectionnez
function simpleHttpRequest(url, succes, echec) {
  var requete = makeHttpObject();
  requete.open("GET", url, true);
  requete.send(null);
  requete.onreadystatechange = function() {
    if (requete.readyState == 4) {
      if (requete.status == 200)
        succes(requete.responseText);
      else if (echec)
        echec(requete.status, requete.statusText);
    }
  };
}
 
simpleHttpRequest("files/fruit.txt", print);

La fonction accède à l'URL qu'on lui donne et appelle la fonction qu'on lui donne comme second argument avec le contenu. Quand un troisième argument est passé, il sert à indiquer une erreur - un code d'état différent de 200.

Pour pouvoir faire des requêtes plus complexes, on peut s'arranger pour que la fonction accepte des paramètres supplémentaires pour préciser la méthode (GET ou POST), une chaîne facultative pour l'envoyer comme donnée, une façon d'ajouter des en-têtes supplémentaires et ainsi de suite. Quand vous aurez autant d'arguments, vous souhaiterez probablement les passer comme un « objet d'arguments » comme nous l'avons vu dans le chapitre 9.

Certains sites Web font un usage intensif de la communication entre les programmes qui tournent côté client et ceux qui tournent côté serveur. Dans de tels systèmes, il peut être pratique de considérer certaines requêtes HTTP comme des appels à des fonctions qui s'exécutent sur le serveur. Le client fait une requête vers des URL qui identifient les fonctions, leur donnant des arguments sous forme de paramètres URL ou de données POST. Le serveur appelle alors la fonction, et met le résultat dans un document JSON ou XML qu'il renvoie. Si vous écrivez quelques fonctions de support pratiques, ceci peut rendre les appels côté serveur presque aussi simples qu'ils le sont côté client... à l'exception bien sûr du retour des résultats qui ne sera pas aussi instantané.


précédentsommairesuivant
Ce ne sont pas les seuls types de requêtes. Il y a aussi HEAD pour demander uniquement les en-têtes d'un document sans le contenu, PUT pour ajouter un document sur un serveur et DELETE pour supprimer un document. Ceux-ci ne sont pas utilisés par les navigateurs et ne sont souvent même pas pris en charge par les serveurs Web.
La partie XML du nom XMLHttpRequest n'est pas la seule à être trompeuse - l'objet peut aussi être utilisé pour une requête à travers des protocoles autres que HTTP, Request est donc la seule partie significative qu'il nous reste.
0 (« non initialisé ») est l'état de l'objet avant qu'open ne soit appelé dessus. Appeler open le passe à 1 (« ouvert »). Appeler send le fait poursuivre vers 2 (« envoyé »). Lorsque le serveur répond, il passe à 3 (« réception »). Enfin, 4 signifie « chargé ».
Nous avons déjà rencontré \n, qui crée une nouvelle ligne. \t est un caractère de tabulation, \r un « retour chariot », que certains systèmes utilisent au lieu d'un \n pour indiquer une fin de ligne. \b (backspace), \v (tabulation verticale), et \f (saut de page) sont utiles quand on travaille avec de vieilles imprimantes mais moins utiles quand on parle de navigateurs Web.

Licence Creative Commons
Le contenu de cet article est rédigé par Marijn Haverbeke et est mis à disposition selon les termes de la Licence Creative Commons Attribution 3.0 non transposé.
Les logos Developpez.com, en-tête, pied de page, css, et look & feel de l'article sont Copyright © 2013 Developpez.com.