Ce document présente un exemple complet d'un système composé des éléments suivants :
Nous supposerons que vous avez téléchargé une version récente de jQuery, pour alléger le propos. Faites-le, ça vaut la peine!
La raison de l'étape intermédiaire est que la minification se fera par voie d'une requête Ajax, et que le service de minification ne sera pas sur le même domaine que celui d'où vient la page Web. Les requêtes Ajax entre domaines sont, par défaut, rejetées par le fureteur, qui voit (légitimement) ces actions comme des risques de sécurité.
L'exemple ne donnera pas le contenu entier de la page Web, mais supposera que vous êtes en mesure de placer dans une variable le texte d'un programme JavaScript.
Évidemment, le service distant est un service que nous utilisons, pas un service que nous implémentons. Nous verrons donc comment y avoir recours, par comment il est implémenté.
Supposons les a priori suivants :
La requête vers le service de relais offert sur le serveur Node.js sera asynchrone, suivant les usages Ajax. Nous utiliserons jQuery pour y arriver.
La fonction minifier procèdera en trois temps :
Avec jQuery, une requête Ajax est relativement simple à exprimer :
La requête Ajax se complète par la fonction à appeler quand la requête se complétera avec succès (qui procède entre autres au décodage de la réponse), et par la fonction à appeler si la requête se conclut par une erreur. |
function minifier() { var txt = obtenirTexte(); var rq= "http://localhost:8888/minify?input=" + btoa(unescape(encodeURIComponent(txt))); $.ajax({ url: rq, type: "post", contentType: "text/plain", crossDomain: true, header: { "Access-Control-Allow-Origin": null }, xhrFields: { witCredentials: false }, success: function(s) { var résultat = decodeURIComponent(escape(atob(s))); // utiliser résultat }, error: function(jqXHR, textStatus, errorThrown) { alert("Zut! " + JSON.stringify(jqXHR) + textStatus + " ; "+ JSON.stringify(errorThrown)); } }); } |
Le code Node.js servira de relais entre la page Web et le service de minification. À ce titre, il sera le serveur pour la page Web, et il jouera le rôle de client pour le service tiers.
Notre serveur se fera en deux fichiers : un fichier index.js qui servira de « programme principal » un peu simpliste, et que nous exécuterons, puis un fichier relais.js qui implémentera le serveur Web de relais à proprement dit.
Pour ce projet, le fichier index.js sera un peu bidon, au sens où il ne servira qu'à démarrer le service de relais. En pratique, ce fichier aurait pu inclure (fonction require) plusieurs modules JavaScript distincts et les organiser de manière à constituer un programme complexe.
var serveur = require("./relais"); serveur.start();
C'est ce fichier que vous exécuterez à la ligne de commande pour lamcer le serveur de relais :
node index.js
Le code proposé en exemple pour ce fichier suit. Il s'agit d'un programme relativement simple, mais qui demande tout de même de s'attarder aux détails puisqu'il interagit avec http, qui est un protocole de communication de qualité commerciale, dans un contexte non-trivial.
Je vous invite, pour vous aider dans votre développement, à utiliser l'affichage à la console de messages opportuns aux endroits qui vous semblent le demander (console.log("J'aime mon prof"), par exemple). Ceci vous aidera à la fois à comprendre le contenu de certains paramètres à vos fonctions, et à saisir l'ordre dans lequel se produisent les événements dans un programme Node.js.
Dans certains cas, surtout lorsque vous manipulez des objets JSON, pensez les transformer en chaîne de caractères avant de les afficher (fonction JSON.stringify prenant en paramètre l'objet JSON à afficher).
La première étape est de placer dans des variables locales les symboles exportés par divers modules Node.js standards. Ici, sans grande surprise, les variables portent les noms des concepts qu'elles représentent. Dans chaque cas, vous trouverez de la documentation en ligne de qualité si vous souhaitez en savoir plus : |
var http = require("http"); var querystring = require('querystring'); var url = require("url"); |
Les fonctions atob et btoa utilisées dans la page Web pour décoder et encoder un texte en format Base64 sont en fait des méthodes du DOM (leur vrai nom est document.atob et document.btoa). Heureusement, il est relativement simple de les écrire nous-mêmes (voir le code à droite). |
var toBase64 = function(str) { return (new Buffer(str || "", "ascii")).toString("base64"); }; var fromBase64 = function(str) { return (new Buffer(str || "", "base64")).toString("ascii"); }; |
Reste la fonction start, qui est celle appelée par index.js plus haut. Cette fonction sera plus complexe, car elle met en place un certain nombre de fonctions qui seront rappelées lors d'événements.
En fait, start est une fonction simple. Elle ne tient que sur deux lignes (que vous verrez plus bas) :
Évidemment, la deuxième instruction est cosmétique. |
function start() { |
Ce qui rend le code plus lourd est que la fonction qui répondra aux requêtes, ici nommée onRequest, est définie localement à start. Ses deux paramètres sont request (la requête reçue du client) et response (la réponse à lui retourner). La réponse sera construite par étapes, plus bas. Cette fonction est plus complexe. Initialement, elle consomme l'URL de la requête qu'elle a reçu et les paramètres de la requête. Portez une attention particulière à inputText, qui est le texte JavaScript à minifier. Le résultat de querystring.parse est un objet JSON, et nous accédons à son paramètre input (voir l'URL dans la page Web, plus haut) comme s'il s'agissait d'une variable « normale ». |
// ... function onRequest(request, response) { var parsedUrl = url.parse(request.url); var pathname = parsedUrl.pathname; var query = parsedUrl.query; var search = parsedUrl.search; var inputText = decodeURIComponent(escape(fromBase64(querystring.parse(query).input))); var zeQuery = querystring.stringify({ input : inputText }); |
Par la suite, certains tests sont faits sur les éléments de l'URL reçue. Puisque nous acceptons des requêtes se terminant par /minify?input=xxx, nous rejerons les « chemins » distincts de /minify. |
// ... if (pathname !== '/minify') { // service non supporté } |
Le bloc le plus important de la fonction est celui traitant une requête POST, qui est ce que nous attendons ici. Une part importante de ce code est tirée de l'exemple donné par le site du service tiers que nous sollicitons, soit http://javascript-minifier.com/examples La vaste majorité des instructions de la fonction tiennent à la définition de la variable req (pour Request), représentant la requête envoyée au service :
|
// ... else if (request.method === 'POST') { var req = http.request({ method : 'POST', hostname : 'javascript-minifier.com', path : '/raw', }, function(resp) { var msgRecu = ""; resp.on('data', function (moton) { msgRecu += moton; }); resp.on('end', function () { response.writeHead(200, { "Content-Type": "text/plain", "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Headers": "X-Requested-With" }); response.write(toBase64(unescape(encodeURIComponent(msgRecu)))); response.end(); }); if ( resp.statusCode !== 200 ) { return; } } ); req.on('error', function(err) { throw err; }); req.setHeader('Content-Type', 'application/x-www-form-urlencoded'); req.setHeader('Content-Length', zeQuery.length); req.end(zeQuery, 'utf8'); } |
Ce qu'il reste pour la fonction onRequest est plutôt simple, soit le traitement de requêtes inattendues. |
// ... else if (request.method == 'GET') { console.log("get..."); } else { console.log("duh?..."); } } |
Enfin, le véritable code de start apparaît : le serveur est créé, associé au port 8888, et exécute le service onRequest. |
// ... http.createServer(onRequest).listen(8888); console.log("Le serveur a ete demarre."); } |
Enfin, pour compléter le fichier, la fonction start est rendue visible aux autres fichiers JavaScript sous le nom... start. |
exports.start = start; |
Et voilà!