Le modèle classique du Web
Dans le modèle standard, chaque requète HTTP génère le chargement d'une nouvelle page: le navigateur n'est rien d'autre qu'un "afficheur" de pages, et tout le travail revient au serveur. Avantages:
simplicité du client
le serveur fait tout le travail de génération des pages : modèle assez sûr (sécurité) Inconvénients:
transmission fréquente de pages entières, donc consommation de bande passante attente du chargement des pages parfois long, à un moment la fenêtre affiche une
page blanche...
ergonomie dégradée par rapport à une application native
Le modèle AJAX
AJAX veut dire "Asynchronous Javascript and XML". Dans ce modèle, le code javascript d'une page peut effectuer des requètes HTTP, sans change de page. On travaille alors en mode asynchrone: le code javascript tourne en tâche de fond, et les requètes HTTP faite en javascript ne sont pas blocantes (on leur associe une fonction de callback).
Dans ce modèle, plutôt que quelque chose de statique une page est vraiment un "programme" javascript qui va faire des appels sur le web de façon autonome.
Avantages:
La page n'échange que les données qui ont besoin de changer: un e-mail, une image, etc.On a donc une bande passante et temps de transfert réduits
La page peut rester opérationnelle pendant l'attente d'une réponse à une requête Inconvénients:
Nécessité d'avoir un moteur JavaScript performant dans le navigateur → course actuelle aux optimisations
Sécurité: attention à ne pas faire confiance au client ! Contenu pas indexable par les moteurs de recherche Bouton « retour » non fonctionnel par défaut
Exemples
Les services google sont de façon générale des applications AJAX. Par exemple: GMail ne charge que les messages, pas la page entière
Google Maps ne charge que des portions carrées des cartes/images satellite
(«tuiles»). Même lorsque des dalles sont en cours de chargement on peut continuer à déplacer/zoomer sur la carte
Le moteur de recherche affiche des suggestions au cours de la frappe. L'obtention se fait en arrière-plan, ce qui n'empêche pas de continuer à taper (fonction de callback!) Mais essentiellement tous les services modernes du web le sont aussi (au moins en partie):
Flickr: les images sont chargeés dynamiquement Twitter: le flux des tweets est rafraîchi à la volée Tous les jeux tels agar.io sont de l'AJAX pur ...
Et en particulier, la grosse majorité propose des interface pour faire des requêtes directement: l'API Flickr avec la recherche de photos
l'API google map
l'API d'OpenWeatherMap, pour la météo l'API de Twitter
...
Notez que pour utiliser ces services, il vous sera en général demandé de vous créer un ID sur le site. C'est gratuit, si vous utilisez le service dans des limites raisonnables.
Requêtes HTPP en AJAX
L'objet essentiel qui permet de faire des requêtes HTTP en javascript est XMLHttpRequest. On doit faire un certain nombre de choix:
mode synchrone ou asynchrone type de requête: POST, GET, PUT format d'échange: XML ou JSON
Mode synchrone
Tant que la requête n'a pas abouti, la fenêtre reste bloquée et l'utilisateur ne peut pas interagir avec elle.
Mode asynchrone
Dans ce mode, la requête est "instantanée", et l'exécution du code javasript peut continuer sans interruption. L'interaction de l'utilisateur avec la page n'est pas impactée.
L'idée est d'utiliser une fonction de callback qui sera utilisée pour traiter la requête lorsqu'elle aura abouti.
Des précautions sont à prendre dans ce mode:
rien ne garantit que les résultats de la précédente requête sont arrivés l'ordre des réponses peut même être inversé
D'où la nécessité de prendre en compte ces aléas: ils ne doivent pas perturber le fonctionnement de l'application
Par exemple, si on tape « a » puis « b » dans le champ de recherche Google, si le résultat de la recherche pour « a » arrive après celui pour « ab », il ne faut pas afficher les résultats pour « a » !
GET et POST
Il s'agit de deux modes principaux pour passer des paramètres à une requête sur le web. GET
Les paramètres éventuels sont dans l'URL: "url?para1=val1¶2=val2&..." L'URL peut être mémorisée dans les favoris/marque-pages, elle peut être indexée par
les moteurs de recherche
ne doit pas provoquer de mise à jour sur le serveur, uniquement l'obtention de données POST
En plus de l'URL on peut envoyer des données au serveur (XML, JSON...) convient bien à la modification de données
AJAX avec jQuery
Dû à des implémentations différentes suivant les navigateurs, il n'est pas recommandé d'utiliser directement l'objet XMLHttpRequest. À la place, on utilise... La librairie jQuery bien sûr. Celle-ci masque les incompatibilités entre navigateurs.
Une requête AJAX se fait comme suit:
1
2
3
4
5
6
$.ajax({ url: "http://example.com/myService",data: {para1 : "val1", para2 : "val2", ...}, dataType: "xml",
type: "GET",
success: processSuccess, error: processError,
7
8
9
10
11
12
13
});functionprocessSuccess(data) { ... }
functionprocessError(jqXHR, textStatus, errorThrown) { console.log(errorThrown + " : "+ textStatus);
}
On donne deux fonctions de callback: une pour traiter les données dans elles sont arrivées, et une pour traiter les erreurs éventuelles. Cette fonction attends en argument l'objet httpRequest, et deux chaines de caractères qui contiennent explicitent l'erreur.
On retrouve le type de l'appel qui peut être GET ou POST, et les paramètres de requête peuvent être donnés dans l'attribut data
Le dataType décrit le type de réponse attendu, qui peut être: xml : l'appel rend alors un objet DOM correspondant json : l'appel renvoie un object javascript correspondant
jsonp : comme "json", mais encapsulé dans une balises <script> créée à la volée (voir plus bas)
text : l'appel renvoie une chaine de caractère.
Racourcis
.get()
Un raccourci est la fonction .get() que vous avez déjà rencontrée. Cette méthode effectue une requête HTTP GET de façon asynchrone.
.load()
Cette fonction charge un fichier (HTML) et l'insère dans l'arbre DOM. Voir la doc pour plus d'information. Dans sa version simple, elle s'utilise comme suit:
1
$("#result").load("monfichier.html");Le contenu de monfichier.html va être placer à l'intérieur du noeud d'id #result.
Requêtes inter-domaine
Par défaut, les requêtes inter-domaines sont interdites: ''Same-Origin Policy''. Donc on ne peut pas faire en javascript une HttpRequest à un site arbitraire. La raison est simple: comme on peut incorporer à une page des fichiers javascripts de provenance quelconque, ces fichiers pourraient contenir des scripts malicieux, qui par exemple récupéreraient des identifiants et mots de passe.
Contourner la restriction
La restriction ne s'applique que lorsque la page est hébergée sur un serveur externe. En principe, il n'y a pas de restriction lorsque vous travaillez en local (c'est à dire si le fichier HTML se trouve sur votre ordinateur).
Mais si la page est hébergée en ligne, on va avoir le problème. Les méthodes standards pour y répondre sont les suivantes.
Avec un proxy sur le serveur
Une solution simple pour contourner la restriction est de demamder au serveur de faire la requête... Si cela résoud le problème (puisque le serveur peut faire ce qu'il veut), cela
complexifie un peu le travail du serveur et rends l'appel à un service externe un peu moins fluide.
CORS
La technique CORS (Cross Origin Resource Sharing) est un entête HTTP
particulier: Access-Control-Allow-Origin qui permet d'indiquer quels sont les sites externes autorisés pour les appels AJAX.
Encore une fois, il s'agit d'une configuration au niveau du serveur. Si c'est beaucoup moins lourd pour le serveur qu'un proxy, cela nécessite une configuration spéciale et requière d'avoir un navigateur qui comprend l'entête CORS (de fait, à l'heure actuelle tous les navigateurs le comprenne).
Le hack JSONP
Les deux techniques précédentes nécessitent de pouvoir configurer le serveur. Une solution alternative, purement AJAX, est d'utiliser la technique JSONP, pour JSON with Padding. L'idée est la suivante: si javascript ne peut pas faire une requête à un serveur qui ne soit pas celui à l'origine de la page, le navigateur peut bien sûr charger des resources issues de sites quelconques: images, mais aussi fichiers CSS ou javascript.
En résumé, il y a deux façons d'obtenir une resource javascript sur internet à partir d'une page web:
Par une balise <script type="text/javascript" src="...">. La source peut être quelconque. Donc par exemple, on peut aller chercher une resource sur un serveur de google, de twitter, ...
Par un appel AJAX avec un objet HttpRequest, depuis javascript. Ceci n'est en général autorisé que pour faire un appel au domaine à laquelle la page appartient. Donc on est en général coincé: on ne peut pas faire appel à google par là.
L'astuce JSONP consiste à remarquer que l'on peut ajouter des noeuds quelconques à l'arbre DOM de la page... Donc en particulier rien ne nous interdit d'ajouter une balise <script> à la volée, avec donc une adresse potentiellement quelconque. C'est là que réside l'astuce: il suffit que le service web que l'on interroge puisse renvoyer la réponse attendue dans un fichier javascript...
Donc une application web peut renvoyer:
Par exemple, essayez les appels suivants à l'application openweathermap, qui fournit des données météorologiques: XML: http://api.openweathermap.org/data/2.5/weather?q=London,uk&appid=22e21ef6495 26ef2b1be4db6d2b0857d&mode=xml JSON: http://api.openweathermap.org/data/2.5/weather?q=London,uk&appid=22e21ef649 526ef2b1be4db6d2b0857d&mode=json JSONP: http://api.openweathermap.org/data/2.5/weather?q=London,uk&appid=22e21ef64 9526ef2b1be4db6d2b0857d&callback=maFonctionQuiTraiteLaReponse
Dans la dernière version, la réponse du serveur est un fichier javascript très simple qui contient un appel à la fonction demandée (vous pouvez choisir le nom que vous voulez), avec comme argument le contenu de la requête:
1
maFonctionQuiTraiteLaReponse({"coord":{"lon":-0.13,"lat":51.51},"weather": ...);En bref
Une requête à une application web peut renvoyer du XML
du JSON
... mais aussi un petit fichier javascript qui appelle une fonction locale.
De façon générale, l'appel JSONP est le plus "portable", même si la majeure partie des services web qui proposent ce type d'accès à leur base de donnée le font avec les bons entêtes CORS.
Un exemple AJAX complet
Dans cet exemple, nous allons mettre en oeuvre des appels à l'application openweathermap vue plus haut afin d'obtenir la météo à Londres. Nous allons faire un appel avec le mode JSON, avec le mode XML, puis avec le mode JSONP.
JSON
La chaine de caractère rendue par le serveur à la requête
http://api.openweathermap.org/data/2.5/weather?q=London,uk&appid=22e21ef649526ef2b1b e4db6d2b0857d&mode=json
ressemble à
{"coord": {"lon": -0.13,"lat": 51.51},"weather": [{"id": 800,"main": "Clear","description": "Sky is Clear","icon": "01d"}],"base": "cmc stations","main": {"temp": 277.211,"pressure": 1028.87,"humidity": 84,"temp_min": 277.211,"temp_max": 277.211,"sea_level": 1039.14,"grnd_level": 1028.87},"wind": {"speed": 7.08,"deg": 353.501},"clouds": {"all": 0},"dt": 1455538262,"sys": {"message": 0.0077,"country": "GB","sunrise": 1455520476,"sunset": 1455556528},"id": 2643743,"name": "London","cod": 200}
Donc la description du temps se trouve dans objectJsonEnQuestion.weather[0].description (voir la doc pour plus de détail).
Un exemple simple d'utilisation avec cet appel en mode JSON pourrait être le suivant:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!DOCTYPE html> <html> <head> <metacharset="utf-8"> <title>Test AJAX</title> <scripttype="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.0/jquery.min.js"></script> <script> function uneFonction(ville) { alert(ville.weather[0].description); }function traiteErreur(jqXHR, textStatus, errorThrown) { alert("Erreur " + errorThrown + " : " + textStatus); }
$.ajax({
url : "http://api.openweathermap.org/data/2.5/weather",
data : { q : "London,uk", appid : "22e21ef649526ef2b1be4db6d2b0857d", mode : "json" }, dataType : "json", success : uneFonction, error : traiteErreur }); </script> </head> <body> <h1>Test AJAX</h1> </body> </html>
XML
http://api.openweathermap.org/data/2.5/weather?q=London,uk&appid=22e21ef649526ef2b1b e4db6d2b0857d&mode=xml ressemble à
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<current><cityid="2643743"name="London"> <coordlon="-0.13"lat="51.51"/> <country>GB</country>
<sunrise="2016-02-15T07:14:35"set="2016-02-15T17:15:29"/> </city>
<temperaturevalue="277.211"min="277.211"max="277.211"unit="kelvin"/> <humidityvalue="84"unit="%"/>
<pressurevalue="1028.87"unit="hPa"/> <wind>
<speedvalue="7.08"name="Moderate breeze"/> <gusts/>
<directionvalue="353.501"code=""name=""/> </wind>
<cloudsvalue="0"name="clear sky"/> <visibility/>
<precipitationmode="no"/>
<weathernumber="800"value="Sky is Clear"icon="01d"/> <lastupdatevalue="2016-02-15T12:19:55"/>
</current>
Notez d'abord combien c'est plus lisible (bien que plus verbeux). En tous les cas, la description du temps s'accède avec jQuery à
1
$(laVariableDOM).find("weather").attr("value")Donc la même chose qu'au dessus mais en utilisant le mode XML donnerait:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html> <html> <head> <metacharset="utf-8"> <title>Test AJAX</title> <scripttype="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.0/jquery.min.js"></script> <script> function uneFonction(ville) { alert($(ville).find("weather").attr("value")); }function traiteErreur(jqXHR, textStatus, errorThrown) { alert("Erreur " + errorThrown + " : " + textStatus); }
$.ajax({
url : "http://api.openweathermap.org/data/2.5/weather",
data : { q : "London,uk", appid : "22e21ef649526ef2b1be4db6d2b0857d", mode : "xml" }, dataType : "xml", success : uneFonction, error : traiteErreur }); </script> </head> <body>
22
23
24
25
<h1>Test AJAX</h1> </body> </html>JSONP
Comme expliqué plus haut, JSONP permet de demander la réponse encapsulé dans un appel de fonction au serveur. Donc la chaine de caractère rendue par le serveur à la requête
http://api.openweathermap.org/data/2.5/weather?q=London,uk&appid=22e21ef649526ef2b1b e4db6d2b0857d&callback=uneFonction
ressemble à
uneFonction({"coord": {"lon": -0.13,"lat": 51.51},"weather": [{"id": 800,"main": "Clear","description": "Sky is Clear","icon": "01d"}],"base": "cmc stations","main": {"temp": 277.211,"pressure": 1028.87,"humidity": 84,"temp_min": 277.211,"temp_max": 277.211,"sea_level": 1039.14,"grnd_level": 1028.87},"wind": {"speed": 7.08,"deg": 353.501},"clouds": {"all": 0},"dt": 1455538262,"sys": {"message": 0.0077,"country": "GB","sunrise": 1455520476,"sunset": 1455556528},"id": 2643743,"name": "London","cod": 200})
Il suffit donc en théorie pour utiliser le mode de faire cette requête comme on ferait un appel à un fichier javascript: son contenu va simplement être exécuté. La seul contrainte est donc de demander l'appel d'une fonction déjà définie...
1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html> <html> <head> <metacharset="utf-8"> <title>Test AJAX</title><scripttype="text/javascript"src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.0/jquery.min.js"></script> <script>
function uneFonction(ville) { alert(ville.weather[0].description); } </script> <scripttype="text/javascript" src="http://api.openweathermap.org/data/2.5/weather?q=London,uk&appid=22e21ef649526ef2b1be4db6d2b0857d&callback=uneFonction"></script> </head> <body> <h1>Test AJAX</h1> </body> </html>
On définit une fonction uneFonction qui va chercher une information dans l'objet généré par le serveur (allez voir la doc). Puis on charge un fichier javascript, qui n'est autre que l'appel de cette fonction avec l'information donnée par le serveur:
1
uneFonction({"coord":{"lon":-0.13,"lat":51.51},"weather": ...); Bien sûr, à ce niveau nous ne sommes pas encore AJAX... Mais maintenant, plutôt que d'écrire cette balise script en dûr dans le fichier html, on peut le faire à la volée. Soit à la main:1
2
3
4
5
6
7
8
<!DOCTYPE html> <html> <head> <metacharset="utf-8"> <title>Test AJAX</title><scripttype="text/javascript"src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.0/jquery.min.js"></script> <script>
function uneFonction(ville) { alert(ville.weather[0].description); }
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var b = $("<script>", { type : "text/javascript", src : "http://api.openweathermap.org/data/2.5/weather?q=London,uk&appid=22e21ef649526ef2b1be4db6d2b0857d&callback=uneFonction" }); $("head").append(b); }$(document).ready(makeBalise); </script> </head> <body> <h1>Test AJAX</h1> </body> </html>
ou, de façon équivalente, avec un appel JSONP à .ajax(): ?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!DOCTYPE html> <html> <head> <metacharset="utf-8"> <title>Test AJAX</title><scripttype="text/javascript"src="https://code.jquery.com/jquery-1.12.0.min.js"></script> <script>
function uneFonction(ville) {
alert(ville.weather[0].description); }
function traiteErreur(jqXHR, textStatus, errorThrown) { alert("Erreur " + errorThrown + " : " + textStatus); }
function appelWeather() {
$.ajax({ url: "http://api.openweathermap.org/data/2.5/weather",
data : { q : "London,uk", appid : "22e21ef649526ef2b1be4db6d2b0857d" }, dataType: "jsonp", success: uneFonction, error: traiteErreur }); }
$(document).ready(appelWeather); </script> </head> <body> <h1>Test AJAX</h1> </body> </html>
Une mini-appli de météo temps-réel
On peut bien sûr faire un appel AJAX de façon interactive. Plutôt que d'appeler le serveur de façon péremptoire au début, on peut faire un petit formulaire pour interroger le serveur de façon interactive. Dans cet exemple: weather.html vous trouverez la méthode JSONP implémentée.
Dans le champ de texte, rentrez par exemple "Nice,fr", ou "Paris,fr", ou "Rome,it": en principe, le paragraphe en dessous devrait se mettre à jour en rapport avec la météo courante.
Coté serveur: javascript pour les
services web
Introduction
Dans ce tutoriel, nous allons voir le framework Node.js, qui permet de développer rapidement un web service répondant avec des données formatées ou des pages internet.
Le modèle standard
Dans un site web traditionnel, le serveur répond aux requêtes des clients en renvoyant le contenu d'un fichier: Si un client requiert l'adresse http://domain.com/page.html, typiquement le serveur du domaine domain.com va aller chercher le fichier page.html, le lire et l'envoyer sur le réseau en réponse. C'est ce que propose par défaut les serveurs web tels que le serveur ISS de Microsoft, Apache, ou encore le léger lighttpd.
Un modèle un tout petit peu plus dynamique permet à un serveur internet d'exécuter un script pour répondre à une requête, et à rediriger la sortie du script en réponse à la requête. En général, l'interface utilisée est CGI (pour Common Gateway Interface). Tous les serveurs permettent cela.
Quelques langages usuels de scripts: Perl (avec l'extension ".pl") PHP (avec l'extension ".php")
ASP (pour
Active Server Pages
, essentiellement avec le serveur IIS) Mais aussi Python, Ruby...Le modèle Node.js
À l'inverse de ce schéma, Node.js est un programme autonome. Il est basé sur le moteur d'exécution JavaScript V8 de Google et a été créé en 2009 par Ryan Dahl (Joyent Inc.). Écrit en C++, c'est essentiellement une plateforme d'exécution javascript avec des APIs
spécialisées pour gérer le fait d'être un serveur: la gestion des connections réseaux, mais aussi le système de fichiers,... On a donc pas de "comportement par défaut", et tout doit se programmer en javascript.
Les grands principes sous-tendant Node.js sont: Éxécution pilotée par les événements
Appels de fonctions asynchrones (utilisation de callback) Entrées/sorties non bloquantes
Les deux premiers sont "standards" dans la philosophie javascript. Le troisième principe est essentiel de la part d'un serveur: on souhaite qu'il reste réactif autant que possible.
Des exemples de l'utilisation de Node.js en production: Un joli site :
https://tweetping.net/
Mais aussi :
Et
Microsoft Azure
Et
Walmart
Et
Paypal
...
Un serveur Node.js consiste au minimum en le programme nodejs
un fichier javascript
Un script pour Node.js va faire les choses suivantes:
Initialiser des objets correspondant aux librairies avec la fonction require par exemple, la librairie http qui gère le protocole HTTP
définir un serveur lancer le serveur
Par exemple, voila un code minimal.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
varhttp = require('http');varserv = http.createServer( //création d'un serveur web
function(req, res) { //callback sur les requêtes HTTP //construction d'une réponse HTTP
res.writeHead(200, {'Content-Type': 'text/plain'}); res.write('Hello world !');
res.end(); //envoi de la réponse }
);
serv.listen(8000); //commence à accepter les requêtes
console.log("Server running at http://localhost:8000");
Pour l'exécuter:
Placez ce texte dans monserveur.js (avec gedit ou jedit par exemple) Ouvrez un terminal
Rendez-vous dans le dossier où vous avez sauvé le fichier:
1
cd le/répertoire/en/question Tappez la commande1
nodejs monserveur.js1
http://localhost:8000 Si le message "Hello World" s'affiche: bravo, vous avez un serveur web.
Le concept
Comme dit plus haut, le script Node.js ne fait rien tout seul: il ne fait que réagir à des événements. Ici, on crée un objet serv qui contient les propriétés pour de fait être un serveur. La principale caractéristique d'un serveur est la façon dont il réagit aux requêtes: c'est le but de la fonction (non nommée) en argument de la méthode createServer. Cette fonction est appelée à chaque fois qu'un client se connecte: elle prend en argument req, objet qui décrit la requête, et res, objet que la fonction va peupler et qui décrit la réponse HTTP (voir le cours/tuto HTTP).
L'objet req est de la classe http.IncomingMessage
L'objet res est de la classe http.ServerResponse
La deuxième chose que fait le le script est de demander au serveur de l'objet serv d'écouter le port 8000.
Donc le serveur ne fait rien tant qu'un client ne se connecte pas. Son comportement est ensuite complètement décrit par la fonction non-nommée liée par la méhode createServer.
Voir l'échange HTTP
Avec Firefox, vous pouvez utiliser par exemple l'extension Live Http Headers.
Boucle de traitement
Un seul programme (un seul thread) traite les événements dans l'ordre de leur occurrence Les traitements qui concernent les entrées/sorties sont exécutées de manière asynchrone
par d'autres threads: « non blocking I/O »
Slogan:
Avec Node.js, on ne fait que spécifier des réponses à des événements Et donc tout est virtuellement en parallèle.
Exemple: serveur plus évolué
__dirname correspond au dossier où se trouve le programme javascript (voir ce post et la doc)
La fonction readFile() génére un événement lorsque le fichier est ouvert (ou qu'une erreur survient), et la fonction en argument est appelée à ce moment.
Le contenu du fichier est du texte. Le serveur n'a à priori pas moyen de savoir plus. Ici, on a donné comme type de contenu text/plain. Vous pouvez essayer text/html, pour voir. En général, on utilise l'extension du fichier pour lui associer un type de contenu -- mais il faut le faire faire au serveur explicitement. On peut par exemple utiliser la
librairie node-mime, mais sinon plus simplement utiliser la bibliothèque express : voir plus bas.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
varhttp = require('http'); varfs = require('fs');//création d'un serveur web varserv = http.createServer(
//callback sur les requêtes HTTP functiontraiteRequete(req, res) { //log de l'url demandée
console.log(req.url);
//construction d'une réponse HTTP
fs.readFile(__dirname + req.url, "utf-8",
//code exécuté quand le fichier est effectivement ouvert function(err, fd) {
if(err) { // une erreur est survenue res.writeHead(500);
res.write(err.message); res.end();
} else{ // sinon, on produit le fichier voulu
res.writeHead(200, { "Content-Type": "text/plain"} ); res.write(fd);
res.end(); //envoi de la réponse }
}); });
serv.listen(8000); //commence à accepter les requêtes
26
Les bibliothèques
Node.js possède une communauté vibrante à l'origine de plus de 240000 projets, centralisée par le gestionnaire de paquets npm (Node Package Manager). Une bibliothèque Node.js s'installe avec la commande
1
npm install unebibliothequeutileNotez que pour que cela fonctionne sur les machines du bâtiment Bréguet, il faut d'abord configurer le proxy:
1
npm config set https-proxy http://proxy.supelec.fr:8080 En standard dans Node : http : pour communiquer via le protocole HTTP fs : pour interagir avec le système de fichiers timer : pour schéduler des traitements crypto : pour chiffrer/déchiffrer des données
Exemple de paquets accessibles le Node Package Manager (NPM) : jquery : pour avoir accès aux facilités de jQuery
async : helpers pour faciliter la gestion d'appels asynchrones express : pour construire rapidement des applications web sqlite3 : pour connecter à une base de données SQLite mongoose : pour connecter à une base de données MongoDB ...
Applications web avec Express
La librairie express permets de développer plus simplement des applications web. En particulier, elle permet d'associer à une "route" une fonction de callback.
Dans ce schéma, une "route" est la donnée d'un mode de requête (GET ou POST) et d'un chemin depuis la racine du site.
Installation
Pour l'installer, configurer le proxy (uniquement sur les machine du bâtiment Bréguet):
1
npm config set https-proxy http://proxy.supelec.fr:8080 puis1
npm install expressUtilisation
On peut par exemple configurer deux routes:
1
2
3
4
5
varexpress = require('express'); varapp = express();
app.get('/accueil', function(req, res) { res.sendfile(__dirname + "/page.html");
6
7
8
9
10
11
12
13
});app.get('/autre', function(req, res) {
res.sendfile(__dirname + "/autrepage.html"); });
app.listen(8000);
console.log("App listening on port 8000...");
Ici, à chaque "route" on associe une fonction (qui ici fait quelque chose de simple). Pour que cela fonctionne:
Dans le répertoire du fichier javascript, placez un fichier "page.html" et un fichier "autrepage.html".
Dans votre navigateur, essayez les addresse
1
http://localhost:8000/accueil et1
http://localhost:8000/autrela fonction sendfile
C'est une fonction spécifique de la librairie express, qui envoie le fichier avec le bon type mime en fonction de l'extension.
Avec des paramètres
Il est possible de récupérer les paramètres donnés dans l'url.
1
2
3
4
5
6
7
8
9
10
11
12
varexpress = require('express'); varapp = express();
app.get('/autre', function(req, res) { if("donnee"inreq.query) {
vartxt = "Donnee reçue :"+ req.query.donnee; res.send(txt); } else{ res.sendfile(__dirname+'/autrepage.html'); } });
app.listen(8000);
13
console.log('App listening on port 8000...');Essayez l'adresse http://localhost:8000/autre?donnee=hello pour voir.
Avec express, l'attribut query de la variable req contient la liste des paires (paramètre,valeur) sous la forme d'un objet javascript: on peut donc facilement accèder aux paramètres d'une requête.
Note
Techniquement, le script au dessus est un service web ! Pas très intéressant, certe, mais néanmoins un service web. En utilisant les techniques du TD5, il est possible de faire une page html indépendante qui questionne ce service en ajax.
Utilisation de SQLite3
SQLite3 est une bibliothèque C (la version 3 du nom) qui implémente un moteur de base de données sans serveur. Une base de donnée SQLite est implémentée par un simple fichier. A l'heure actuelle, SQLite est l'un des moteurs de base de données les plus simples à installer, à manipuler et à administrer, ce qui en fait un très bon candidat pour l'utilisation dans un site web. Pour en savoir plus sur les usages de SQLite, consulter cette page. Néanmoins, le site dit lui-même:
"Le site web SQLite utilise bien-sûr lui-même SQLite et à l'heure de l'écriture de ce texte (2015) il traite entre 400.000 et 500.000 requêtes HTTP par jour, dont 15-20% sont des pages dynamiques utilisant la base de donnée. Chaque page fait en moyenne 200 requêtes SQL. Bien que la base de donnée se trouve sur une seule machine virtuelle partageant un serveur physique avec 23 autres MV, la charge moyenne reste en dessous de 0,1 la plupart du temps." Le fin mot de l'histoire est que SQLite est une bonne solution de base donnée dans le contexte d'une serveur web à traffic raisonnable.
Installation
Pour l'installer, éventuellement configurer le proxy:
1
npm config set https-proxy http://proxy.supelec.fr:8080 puis1
npm install sqlite3SQLite en deux mots
En bref, une base de donnée SQL est une collection de tableaux (tables) auxquels on accède grâce à leur nom. Outre son nom, un tableau est défini par ses colonnes: à chaque colonne on associe un nom et un type. Les données du tableau consistent en les lignes du tableau. Par exemple
1
2
3
4
5
$ sqlite3 test.db3 SQLite version 3.8.7.1 2014-10-29 13:59:56 Enter ".help" for usage hints.sqlite> create table message(id INT, nom TEXT, content TEXT); sqlite> insert into message values(0,"Bob","Hello");
6
7
8
9
10
11
sqlite> insert into message values(1,"Alice","Goodbye");
sqlite> insert into message values(2,"Charlie","Good afternoon"); sqlite> select * from message; -- les commentaires sont comme ça 0|Bob|Hello
1|Alice|Goodbye
2|Charlie|Good afternoon sqlite>
Ici, on trois commandes:
CREATE TABLE : pour créer une table. On donne le nom de la table et une liste de nom de colonnes avec leur type. Outre TEXT et INT, on peut aussi choisir FLOAT par exemple (voir la doc pour tous les choix possibles)
INSERT INTO nomdelatable VALUES(lesvaleursseparéespardesvirgules) : pour remplir la table
SELECT * FROM nomdelatable : pour lister le contenu de la table Les commandes peuvent être en majuscule ou en minuscule On peut faire des commentaires en commençant par "--"
Notez que les commandes se terminent par un point virgule (";").
La sélection peut aussi être filtrée. Par exemple, avec la table qui précède:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
sqlite> select * from message where id = 0 ; 0|Bob|Hello
sqlite> select content from message where id = 0 ; Hello
sqlite> select nom from message where id = 0 ; Bob
sqlite> select nom,content from message where id = 0 ; Bob|Hello
sqlite> select max(id) from message 2
sqlite> select * from message where id > 0 ; 1|Alice|Goodbye
2|Charlie|Good afternoon
sqlite> select * from message where nom like "%i%"; 1|Alice|Goodbye
2|Charlie|Good afternoon
sqlite> select * from message where nom like "%i%" and id > 1; 2|Charlie|Good afternoon
sqlite> select * from message where nom like "%i%" and not id > 1; 1|Alice|Goodbye
Notes:
L'étoile ("*") dans le "select" sélectionne toutes les colonnes. On peut aussi mettre une ou plusieurs colonnes entre virgules.
On peut aussi mettre une fonction (comme "max") pour récupérer une information sur la liste des valeurs dans une colonne.
On peut filtrer sur des opérateurs de comparaisons sur les nombres
On peut aussi filtrer par pattern sur un champ de texte avec "like". Le symbole "%" est remplacé par n'importe quelle séquence de caractères
On peut combiner les assertions booléennes avec "and", mais aussi "or", et "not" Outre l'insertion et les requêtes, on peut modifier une ligne dans un tableau avec update
1
2
3
4
5
sqlite> update message set content = "Nope" where id = 0 ; sqlite> select * from message;
0|Bob|Nope 1|Alice|Goodbye
2|Charlie|Good afternoon
On peut aussi simplement supprimer une ou plusieurs lignes:
1
2
3
sqlite> delete from message where nom like "%i%"; sqlite> select * from message;
0|Bob|Nope
(ici on a supprimé deux lignes)
Finalement, on peut aussi effacer un tableau complètement avec "drop table"
1
2
3
sqlite> drop table message; sqlite> select * from message; Error: no such table: message (Argh ! Le tableau a disparu !)
La bibliothèque sqlite3 avec Node.js
Une fois installée avec npm, on peut utiliser la librairie, comme d'habitude, en créant au début du fichier javascript un objet javascript qui va contenir toutes les fonctions:
1
2
3
varsq = require('sqlite3');
sq.verbose(); // pour obtenir des informations sur l'exécution des // requêtes SQL (utile pour le débug)
On est prêt à créer une connexion sur une base de données. Pour les besoins du tutoriel, nous allons re-créer la base de donnée du dessus qui a été malencontreusement effacée. Mais on va utiliser un nom plus malin.
Donc, dans la console, dans le répertoire du fichier javascript:
1
2
3
4
5
6
7
$ sqlite3 touslesmessages.db3 SQLite version 3.8.7.1 2014-10-29 13:59:56 Enter ".help" for usage hints.sqlite> create table message(id INT, nom TEXT, content TEXT); sqlite> insert into message values(0,"Bob","Hello");
sqlite> insert into message values(1,"Alice","Goodbye");
sqlite> insert into message values(2,"Charlie","Good afternoon");
On peut maintenant rajouter dans notre fichier javascript
1
vardb = newsq.Database(__dirname + '/touslesmessages.db3'); et on a un objet db qui est lié à la base de données.Comment l'utiliser ? Il y a essentiellement trois méthodes utiles: db.each("COMMANDE SQL", function(err,row) { ... })
Exécute la commande sql
Sur chaque ligne retournée, applique (en séquence) la fonction.
L'objet row a pour attributs les noms de colonnes et commes valeurs... les valeurs. db.all("COMMANDE SQL", function(err,rows) { ... })
Pareil, mais retourne la liste des lignes et applique la fonction dessus (l'argument rows). db.run("COMMANDE SQL")
Pour quand on ne souhaite pas faire quelque chose après (par exemple, après une insertion ou une modification de ligne
Exemple simple
Un exemple complet (qui ne lance pas de serveur) pourrait être:
1
2
3
4
5
6
7
8
9
10
varsq = require('sqlite3'); sq.verbose();vardb = newsq.Database(__dirname + '/touslesmessages.db3');
db.each("SELECT * FROM message", function(err, row) { if(err) {
console.log(err); } else{
11
}});
Sauvez ce programme comme "message.js" et exécutez-le avec la commande
1
nodejs message.js Admirez le résultat.Vous pouvez facilement imaginer comment demander au code de rajouter ou d'effacer une ligne dans la base de données.
Note
On peut maintenant évidemment transformer ce code en un service web avec express, et c'est l'objectif du TD6.
Avec des paramètres externes
La fonction run peut être utilisée avec des paramètres dans la commandes SQL: on met des "?" à la place de ce que l'on veut mettre, et donner en deuxième argument de run un
tableau. Par exemple: ?
1
db.run("INSERT INTO message VALUES(?,?,?)", [3,"David","I am here"]); va effectivement faire?
1
db.run("INSERT INTO message VALUES(3,\"David\",\"I am here\")"); L'intérêt est que vous n'avez pas besoin d'échapper les caractères problématiques: javascript le fait pour vous
la commande SQL est écrite une fois pour toute
Les autres fonctions peuvent aussi prendre des paramètres externes. Par exemple: ?
1
2
3
4
5
6
7
8
db.each("SELECT * FROM message WHERE id > ?", [2], function(err, row) {
if(err) {
console.log(err); } else{
console.log(row.nom + "("+ row.id + ") a écrit '"+ row.content + "'"); }