Serveur web et application web en JavaScript
L'idée ne vous a peut-être jamais effleuré l'esprit, mais sachez qu'il est tout à fait possible de se passer d'Apache et remplacer les PHP, JAVA, C#... par du JavaScript pour faire un site web. Je vous vois faire la grimace dans le fond, mais développer votre propre serveur web en JavaScript est un jeu d'enfant. Tout ceci est réalisable grâce à l'exécuteur de script Node.js et ses modules !
Développer un serveur web avec les modules HTTP et Express
Si vous avez lu mon précédent billet sur l'installation et l'utilisation de Node.js ou que vous savez de quoi il s'agit exactement, vous pouvez poursuivre. Sinon je vous conseils de passer par la case départ (sans toucher 2000€) et de lire mon article expliquant comment développer en JavaScript côté serveur avec Node.js.
Un échange client/serveur simple
Pour créer un serveur web —élément essentiel pour présenter à notre client (un navigateur) notre site web ou application web— Node.js utilise le module HTTP qui permet « le support de plusieurs fonctionnalités du protocole qui sont habituellement difficiles à utiliser » d'après la documentation. En d'autres termes : il permet de répondre aux demandes des navigateurs (demande d'une page Internet par exemple). Pour que cela soit encore plus trivial, nous allons utiliser un module communautaire : le Framework Express qui va nous permettre de réaliser aisément des applications web.
Préparation
Préparons notre serveur :
- Nous allons avoir besoin (avec l'aide de la documentation), d'appeler le module HTTP de créer un objet capable de comprendre le protocole http et d'écouter les demandes clientes sur le bon couple ip / port en vue de lui répondre.
- Nous allons nous créer un dossier d'application web ici C:\nodejs\website\ dans lequel nous allons placer notre premier script website.js.
- Nous allons également avoir besoin de télécharger le module « Express » avec la commande npm install express depuis le dossier C:\nodejs\website\. Cela ajoutera donc un dossier « node_modules » dans C:\nodejs\website\ au même niveau que website.js.
Contenu du fichier website.js
var // Nous stockons l'objet HTTP dans la variable globale "http". http = require("http"), // Nous stockons également la fonction Express dans la variable "express". express = require("express"), // Nous instancions un serveur web en exécutant la variable "express" dont le résultat est stocké dans l'objet httpServer. httpServer = express(); // Nous utilisons la fonction listen de httpServer en lui passant comme premier paramètre le port d'écoute de l'application. // Par défaut une page de site Internet est réclamée via le port 80. Si vous n'avez ni Apache, ni IIS ou autre qui utilise ce port, vous pouvez le mettre sur 80. // J'ai pour ma part décidé d'écouter le port 82. httpServer.listen(82); // Nous écoutons les requêtes GET en provenance d'un navigateur client. // Si une requête est envoyé à l'adresse : http://localhost/ à votre navigateur) le code du second paramètre (une fonction de callBack) est exécuté. // Cette fonction de callBack fourni en premier paramètre de quoi manipuler la requête et en second paramètre de quoi manipuler la réponse qui va être faites au client. httpServer.get("/", function (request, response) { // "/" indique que nous écoutons la racine du site web. var // Nous définissons l'entête de la page qui va être renvoyé au client. header = { "Content-Type": "text/html", "Charset": "utf-8" }, // Nous définissons son contenu. template = "<!DOCTYPE html>" + "<html lang='fr'>" + "<head>"+ "<meta charset='utf-8' />" + "<title>Ma première page</title>" + "</head>" + "<body>" + "<p>Hello World !</p>" "</body>" + "</html>"; // Nous écrivons l'entête en indiquant que c'est une page valide (OK : 200). response.writeHead(200, header); // Nous écrivons le contenu de la page que l'on va faire afficher par le navigateur client. response.write(template); // Nous informons le serveur qu'il peut enfin envoyer la réponse au client. response.end(); });
Faisons quelques tests :
- Exécutez le fichier : cd C:\nodejs\webserver\ puis node webserver.js dans votre console.
- Réclamez l'adresse http://localhost:82/ (mettez le bon port) à votre navigateur.
- Réclamez l'adresse http://localhost:82/article (mettez le bon port) à votre navigateur.
L'adresse http://localhost:82/ nous renvoi le contenu de notre page web tandis que http://localhost:82/article nous renvoi « Cannot GET /article ». Effectivement le premier cas fonctionne puisque nous avons défini une réponse à la demande "/" qui correspond à la racine du site. Cependant, n'ayant pas défini une réponse à la demande "/article" le module Express prend la main et prend la liberté de renvoyer un message d'erreur en text/plain (pas en html) avec un code d'erreur 404 dans l'entête http (header).
Création d'un petit site web
Repartons du code précédent et ajoutons de quoi paramétrer notre site :
- pour que l'on puisse le changer d'adresse et de port d'écoute facilement.
- pour que l'on puisse passer d'un environnement de Développement/Test/Qualité/Production facilement.
- Pour qu'il écoute plusieurs pages et également celles qui n'existent pas.
Je vous livre le code final de cette partie si vous souhaitez le comprendre vous-même. Nous allons le décortiquer un peu plus loin.
/*=============================*/ /*== Application web Node.js ==*/ /*=============================*/ var /*== Charger les modules Node.js ==*/ http = require("http"), express = require("express"), /* npm install express */ httpServer = express(), /*== Variables globales de l'application ==*/ appConfig = {}; /*== Paramétrage de l'application ==*/ httpServer /* == Définir un environnement d'exécution : SET NODE_ENV=dev ==*/ .configure(function () { /* Commun à tous les environnements */ }) .configure("dev", function () { /* Uniquement en environnement de Dev */ appConfig.host = "localhost"; appConfig.path = "/"; appConfig.httpPort = 82; appConfig.url = appConfig.host + ((appConfig.httpPort != 80) ? ":" + appConfig.httpPort : "") + appConfig.path; }) .configure("test", function () { /* Uniquement en environnement de Test */ }) .configure("qual", function () { /* Uniquement en environnement de Qualité */ }) .configure("prod", function () { /* Uniquement en environnement de Production */ }).listen(appConfig.httpPort); /*********************************************/ /*** Chemins de réponse - Réécriture d'url ***/ /*********************************************/ /* Accueil */ // http://www.site.com/ httpServer.get(appConfig.path, function (request, response) { var header = { "Content-Type": "text/html", "Charset": "utf-8" }, template = "<!DOCTYPE html>" + "<html lang='fr'>" + "<head>" + "<meta charset='utf-8' />" + "<title>Ma première page</title>" + "<base href='//" + appConfig.url + "' />" + "</head>" + "<body>" + "<p><a href='./connexion/' title='Page de connexion'>Page de connexion</a></p>" "</body>" + "</html>"; response.writeHead(200, header); response.write(template); response.end(); }); /* Connexion */ // http://www.site.com/connexion/ httpServer.get(appConfig.path + "connexion", function (request, response) { var header = { "Content-Type": "text/html", "Charset": "utf-8" }, template = "<!DOCTYPE html>" + "<html lang='fr'>" + "<head>" + "<meta charset='utf-8' />" + "<title>Page de connexion</title>" + "<base href='//" + appConfig.url + "' />" + "</head>" + "<body>" + "<form id='login' action='./connexion/' method='POST'>" + "<div>" + "<label for='login-email' placeholser='Email'>Email : </label>" + "<input id='login-email' type='text' />" + "</div>" + "<div>" + "<label for='login-password' placeholser='Mot de passe'>Mot de passe : </label>" + "<input id='login-password' type='password' />" + "</div>" + "<label><input type='submit' value='Ok' /></label>" + "</form>" + "</body>" + "</html>"; response.writeHead(200, header); response.write(template); response.end(); }); /* Si aucune page ne match */ httpServer.use(httpServer.router); httpServer.use(function(request, response, next) { var header = { "Content-Type": "text/html", "Charset": "utf-8" }, template = "<!DOCTYPE html>" + "<html lang='fr'>" + "<head>" + "<meta charset=\"utf-8\" />" + "<title>Page de connexion</title>" + "<base href='//" + appConfig.url + "' />" + "</head>" + "<body>" + "<p>Cette page n'existe pas.</p>" + "</body>" + "</html>"; response.writeHead(404, header); response.write(template); response.end(); });
Si vous exécutez la commande node website.js, la console va vous renvoyer une erreur : c'est normal.
Définir des variables de portage
Tout d'abord, définissons les quelques variables qu'il va nous falloir changer en cas de déménagement du site.
- Le nom de domaine / l'adresse ip du site : pour ce site blog.haeresis.fr
- La racine d'accès aux fichiers : si l'intégralité du site se trouvait derrière http://www.haeresis.fr/blog/alors ce serait /blog. Pour ce site c'est simplement /.
- L'url : elle est composée des deux précédents points et du port (si le port n'est pas 80).
Voyons alors la première partie de website.js :
/*******************/ /* Haut du fichier */ /*******************/ var /*== Charger les modules Node.js ==*/ http = require("http"), // Disponible de base. express = require("express"), // Disponible avec npm. httpServer = express(), /*== Variables globales de l'application ==*/ appConfig = {}; //On créer un objet vide qui contiendra les variables d'environnement de l'application. /*== Paramétrage de l'application ==*/ httpServer /* Ajout de la configuration du serveur - elle sera utile par la suite */ .configure(function () { appConfig.host = "localhost"; // Ca c'est le host/ip du site. appConfig.path = "/"; // Ca c'est le dossier de base du site. appConfig.httpPort = 82; // Ca c'est le port d'écoute du site. // Ci-dessous la création automatique d'un lien absolue jusqu'aux resources. appConfig.url = appConfig.host + ((appConfig.httpPort != 80) ? ":" + appConfig.httpPort : "") + appConfig.path; }) .listen(appConfig.httpPort); // On écoute le port paramétré.
Définir un environnement d'exécution
Nous alons modifier le code précédent en utilisant plusieurs fonctions configure à la suite de Express. Effectivement, en ajoutant un premier paramètre, il est possible de limiter l'exécution de la configuration aux environnements dont la variable NODE_ENV sera égale à ce paramètre.
Modifions le code précédent de website.js :
/*******************/ /* Haut du fichier */ /*******************/ var /*== Charger les modules Node.js ==*/ http = require("http"), express = require("express"), httpServer = express(), /*== Variables globales de l'application ==*/ appConfig = {}; /*== Paramétrage de l'application ==*/ httpServer .configure(function () { /* Commun à tous les environnements */ }) .configure("dev", function () { // Ne sera exécuté que si NODE_ENV vaut "dev". Dans le code c'est la variable process.env.NODE_ENV qui est testé par configure. /* Uniquement en environnement de Dev */ appConfig.host = "localhost"; appConfig.path = "/"; appConfig.httpPort = 82; appConfig.url = appConfig.host + ((appConfig.httpPort != 80) ? ":" + appConfig.httpPort : "") + appConfig.path; }) .configure("test", function () { // Ne sera exécuté que si NODE_ENV vaut "test". Dans le code c'est la variable process.env.NODE_ENV qui est testé par configure. /* Uniquement en environnement de Test */ }) .configure("qual", function () { // Ne sera exécuté que si NODE_ENV vaut "qual". Dans le code c'est la variable process.env.NODE_ENV qui est testé par configure. /* Uniquement en environnement de Qualité */ }) .configure("prod", function () { // Ne sera exécuté que si NODE_ENV vaut "prod". Dans le code c'est la variable process.env.NODE_ENV qui est testé par configure. /* Uniquement en environnement de Production */ }) .listen(appConfig.httpPort);
Revenons sur l'erreur d'exécution que nous avons eu en essayant d'exécuter le script complet (Error: Router#get() requires a path). Afin de résoudre ce problème, définissez dans votre console la variable d'environnement NODE_ENV avec la commande SET NODE_ENV=dev (uniquement sous Windows) ce qui permettra à la variable process.env.NODE_ENV d'être testée à « dev » dans la fonction configure() de Express. Il est possible que cette variable disparaisse au redémarrage de la machine.
Créer plusieurs pages
Notre site web sera constitué de plusieurs pages. Il n'y a rien de plus simple. Il suffit de router d'autres requêtes GET que « / ».
/*********************/ /* Milieu du fichier */ /*********************/ /* Accueil */ // http://localhost:82/ httpServer.get(appConfig.path, function (request, response) { var header = { "Content-Type": "text/html", "Charset": "utf-8" }, template = '<!DOCTYPE html>' + '<html lang="fr">' + '<head>' + '<meta charset="utf-8" />' + '<title>Ma première page</title>' + '<base href="//' + appConfig.url + '" />' + '</head>' + '<body>' + "<p><a href='./connexion/' title='Page de connexion'>Page de connexion</a></p>" '</body>' + '</html>'; response.writeHead(200, header); response.write(template); response.end(); }); /* Connexion */ // http://localhost:82/connexion/ httpServer.get(appConfig.path + "connexion", function (request, response) { var header = { "Content-Type": "text/html", "Charset": "utf-8" }, template = '<!DOCTYPE html>' + '<html lang="fr">' + '<head>' + '<meta charset="utf-8" />' + '<title>Page de connexion</title>' + "<base href='//" + appConfig.url + "' />" + '</head>' + '<body>' + '<form id="login" action="./connexion/" method="POST">' + '<div>' + '<label for="login-email" placeholser="Email">Email : </label>' + '<input id="login-email" type="text" />' + '</div>' + '<div>' + '<label for="login-password" placeholser="Mot de passe">Mot de passe : </label>' + '<input id="login-password" type="password" />' + '</div>' + '<label><input type="submit" value="Ok" /></label>' + '</form>' + '</body>' + '</html>'; response.writeHead(200, header); response.write(template); response.end(); });
Vous aurez à présent également du contenu à l'adresse http://localhost:82/connexion/.
Note : la balise <base> des lignes 14 et 39 permet de forcer le début des liens relatifs. Ainsi peut importe la profondeur du chemin de la page, la racine sera toujours celle du site et non celle de la page courante.
Les pages non existantes
Afin de changer la réponse de votre serveur quand un fichier inexistant est réclamé (aucun contenu n'est associé à la demande GET) il va falloir définir un contenu 404 par défaut.
/******************/ /* Bas du fichier */ /******************/ /* Si aucune page ne match */ httpServer.use(httpServer.router); // On se place après la vérification de toutes les requêtes associées à un contenu. // On renvoi un contenu. httpServer.use(function(request, response, next) { var header = { "Content-Type": "text/html", "Charset": "utf-8" }, template = '<!DOCTYPE html>' + '<html lang="fr">' + '<head>' + '<meta charset="utf-8" />' + '<title>Page de connexion</title>' + "<base href='//" + appConfig.url + "' />" + '</head>' + '<body>' + '<p>Cette page n\'existe pas.</p>' + '</body>' + '</html>'; response.writeHead(404, header); // On précise que la page n'existe pas avec le Code : 404. response.write(template); response.end(); });
Récupérer du contenu avec la méthode POST
Rendez-vous à l'adresse http://localhost:82/connexion/ et cliquez sur le bouton Ok. Quel surprise ! Un joli message : « Cette page n'existe pas. » vous attend alors que pourtant vous n'avez même pas changé de page !
En fait, quand vous rejoignez la page « http://localhost:82/connexion/ » en suivant un lien ou directement par l'url vous réclamez ce fichier en GET. Hors la fonction get() ne répond pas à une demande faites en POST ce qui est le cas de la demande du formulaire en cliquant sur « Ok ».
Modifions l'écoute de « http://localhost:82/connexion/ » pour ajouter une écoute POST grâce à post() en plus de l'écoute GET.
/* *** */ /* Connexion */ // http://localhost:82/connexion/ // On créer une fonction chargée de répondre à la demande. // Ceci est ni plus, ni moins le code qui était directement exécuté dans httpServer.get(). function connectionRender(request, response) { var header = { "Content-Type": "text/html", "Charset": "utf-8" }, template = '<!DOCTYPE html>' + '<html lang="fr">' + '<head>' + '<meta charset="utf-8" />' + '<title>Page de connexion</title>' + "<base href='//" + appConfig.url + "' />" + '</head>' + '<body>' + '<form id="login" action="./connexion/" method="POST">' + '<div>' + '<label for="login-email" placeholser="Email">Email : </label>' + '<input id="login-email" type="text" />' + '</div>' + '<div>' + '<label for="login-password" placeholser="Mot de passe">Mot de passe : </label>' + '<input id="login-password" type="password" />' + '</div>' + '<label><input type="submit" value="Ok" /></label>' + '</form>' + '</body>' + '</html>'; response.writeHead(200, header); response.write(template); response.end(); } // On répond à la demande en GET et/ou en POST. httpServer.get(appConfig.path + "connexion", function (request, response) { connectionRender(request, response); }).post(appConfig.path + "connexion", function (request, response) { // Ajout d'une réponse à une demande de contenu en POST. connectionRender(request, response); }); /* *** */
Avant la suite
Réflexion sur notre script
Là où un développement traditionnel consiste à déposer des fichiers dans des dossiers et de les fournir depuis l'adresse physique sur le serveur, Node.js embarque nativement la réécriture d'url et propose d'associer à une demande, un contenu. Avec encore plusieurs couches d'abstractions nous verrons comment déporter les contenus de fichier dans de vrais fichiers .htm à part.
Cela signifie également que si vous n'avez pas décidé qu'un contenu/fichier est renvoyé par le serveur sous tel requête GET, POST ou autre, rien ne parviendra jamais au client. Très bon point pour la sécurité.
Pour finir : avez-vous essayé de modifier le contenu de votre script et rechargé une page dans votre navigateur sans voir aucunes modifications ? Etonnant non ? Les fichiers serveur peuvent être modifiés à chaud et aucunes modifications ne sera effectives tant que le script n'aura pas été ré-exécuté (je parle de website.js).
Prochaines étapes
La partie précédente montre comment répondre à une demande en POST mais ne montre pas comment exploiter les paramètres envoyés. De plus ce morceau de script est très frustrant : un seul fichier pour plusieurs pages, HTML mélangé dans le code, pas d'appel de CSS et/ou de JS... Je vais vous demandez un peu de patience car nous verrons ça prochainement. En attendant, l'heure est venu pour nous d'utiliser jQuery côté serveur sur Windows !
Source
Vous pouvez télécharger le fichier résultat de cet article ici.
0 Commentaire
Choisir un Avatar depuis une url
Adresse : (64px x 64px)ou Changer la couleur de fond
et Choisir un Avatar dans la liste
Votre commentaire a été ajouter ci-dessous ! Si vous désirez le supprimer ultérieurement, servez vous du code suivant :
Les commentaires sont actuellement affiché du plus rescent au plus ancien. Vous pouvez inverser l'ordre en cliquant ci-dessous :
Soyez le premier à laisser un commentaire !
Je trouve ce commentaire pertinent ?