Les ressources fournies par les serveurs web sont soit stockées sur le serveur dans des fichiers, soit générées à la demande par le serveur.
On parle de web statique lorsque le serveur web donne un accès aux fichiers et de web dynamique lorsque le serveur web construit dynamiquement les ressources. Les serveurs web modernes fonctionnent à la fois statiquement et dynamiquement en fonction des ressources demandées.
Ce chapitre présente ces deux modes de fonctionnement des serveurs web (mode statique et mode dynamique). Il précise les conventions utilisées pour mettre en place le mode statique. Il explique enfin comment générer dynamiquement des pages HTML.
Certaines ressources qui composent les pages web ne changent jamais ou très rarement. C'est le cas notamment des images ou des vidéos. L'exemple le plus évident est celui du logo d'une entreprise qui est affiché en haut de toutes les pages web de l'entreprise.
D'autres ressources évoluent avec le temps mais restent relativement stables et, en tout cas, ne sont pas modifiées à chaque fois qu'un utilisateur y accède. C'est le cas des feuilles de style CSS qui sont relativement stables même si elles peuvent être modifiées lorsqu'on change le design d'un site. C'est aussi le cas des codes javascript qui sont relativement stables même s'ils peuvent être modifiés lorsqu'on ajoute des fonctionnalités à un site.
Ces ressources stables peuvent prendre beaucoup de place (notamment pour les vidéos). C'est pour cela qu'on préfère souvent les stocker sous forme de fichier sur le serveur web et les servir à la demande. C'est ce que l'on appelle le web statique.
La principale difficulté du web statique est de donner une URL différente pour chaque ressource sachant qu'il y a souvent un très grand nombre de ressources. Pour faire face à cette difficulté il suffit de prendre pour convention que l'URL de la ressource corresponde au chemin du fichier sur le serveur. Pour facilement mettre en place cette convention on décide souvent de stocker toutes les ressources statiques sous un seul répertoire du serveur (souvent nommé le répertoire public ou static). Ainsi, par exemple, accéder au fichier image1.jpg stocké dans le répertoire public se fait via l'URL http://localhost:8080/public/image1.jpg
. De même, l'URL pour accéder au fichier image2.jpg stocké dans le répertoire public/images est http://localhost:8080/public/images/image2.jpg
.
Un serveur web qui applique cette convention va devoir analyser l'URL demandée par le navigateur et vérifier que le chemin de la ressource commence par le nom du répertoire racine du serveur (par exemple /public/). Si tel est le cas, cela veut dire que l'URL demandée cible une ressource statique (un fichier). Il suffit alors d'extraire le chemin de l'URL et de chercher le fichier correspondant dans le répertoire public du serveur.
Le Code 4.1 donne le code d'un serveur web statique. Ce code applique bien les deux conventions : les URLs sont similaires aux chemins des fichiers, tous les fichiers sont stockés dans le répertoire public. La ligne 8 vérifie que l'URL demandée commence par /public/. Si tel est le cas, la ligne 10 lit le contenu du fichier correspondant à l'URL demandée et le renvoie au navigateur (ligne 11). Si l'URL demandée ne correspond pas à un fichier, une exception est levée et un message d'erreur est renvoyé au navigateur (ligne 14). Notons aussi que nous avons utilisé un template literal en ligne 22 (opération ${}
). Cela permet d'évaluer le contenu mis entre les accolades et de le transformer en une chaîne de caractères.
const fs = require("fs");
const http = require("http");
const host = 'localhost';
const port = 8080;
const server = http.createServer();
server.on('request', (req, res) => {
if (req.url.startsWith('/public/')) {
try {
const fichier = fs.readFileSync('.'+req.url);
res.end(fichier);
} catch (err) {
console.log(err);
res.end('erreur ressource inconnue');
}
} else {
res.end("erreur URL invalide");
}
});
server.listen(port, host, () => {
console.log(`Server running at http://${host}:${port}/`);
});
Code 4.1 : Code d'un serveur web statique.
Nous considérons maintenant que le fichier index.html est stocké dans le répertoire public et que son HTML est celui du code 4.2 . Nous considérons de plus que le serveur stocke aussi dix images (cinq images de taille normale et cinq petites) dans ce même répertoire public. Grâce au serveur statique, la Figure 4.1 présente l'affichage de la page web accessible via l'URL http://localhost:8080/public/index.html.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Media</title>
</head>
<body>
<header>Mes Images</header>
<nav>
<ul>
<li><a href="index.html">index</a></li>
</ul>
</nav>
<section>
<div>
<a href="image1.jpg"><img src="image1_small.jpg"></a>
<a href="image2.jpg"><img src="image2_small.jpg"></a>
<a href="image3.jpg"><img src="image3_small.jpg"></a>
</div>
<div>
<a href="image4.jpg"><img src="image4_small.jpg"></a>
<a href="image5.jpg"><img src="image5_small.jpg"></a>
</div>
</section>
</body>
</html>
Code 4.2 : Code HTML de la page web statique index.html.
A l'inverse des ressources statiques, les ressources dynamiques sont générées à chaque fois qu'un navigateur en fait la demande. C'est le cas notamment des pages web qui dépendent de données qui changent avec le temps. Par exemple, un mur d'images qui affiche les images les plus populaires de la journée. C'est aussi le cas de pages web dont le code HTML contient des informations propres à un contexte d'exécution. Un serveur web de commerce électronique par exemple va générer dynamiquement une page web pour présenter la commande en cours d'un client. Enfin c'est le cas de pages web qu'il serait impossible de coder statiquement car elles seraient trop nombreuses. Par exemple, si on souhaite qu'une page web affiche les mois d'un calendrier perpétuel, il faudrait alors coder une infinité de pages web en mode statique, on utiliserait alors la génération dynamique.
En mode dynamique, un serveur web génère la ressource demandée et la renvoie au navigateur. Même si en théorie n'importe quelle ressource peut être générée dynamiquement, nous considérons ici essentiellement la génération de code HTML. La génération d'image, de vidéo, de code Javascript ou même de feuille de style CSS est bien plus rare que la génération de code HTML.
Le code HTML étant du texte, le serveur web doit donc simplement construire la chaîne de caractères du code HTML. Pour se faire, le moyen le plus basique en JavaScript est d'utiliser l'opérateur "+" qui permet de concaténer deux chaînes de caractères. Il suffit alors de construire les différentes parties du code HTML et de les concaténer pour obtenir le code HTML complet. Dès que la chaîne de caractères est construite intégralement, le serveur web peut la renvoyer au navigateur. Cela se fait en utilisant la méthode end() de la réponse qui prend comme argument une chaîne de caractères.
A titre d'exemple le Code 4.3 présente un serveur web qui génère dynamiquement un mur d'images à partir de toutes les images qui se trouvent dans le répertoire public. Ce code reprend le Code 4.1 du serveur statique (lignes 8 à 15). La partie dynamique se trouve dans les lignes 16 à 26. Le serveur commence à lister tous les fichiers présents dans le répertoire public (ligne 17). Puis il filtre les fichiers pour ne garder que ceux qui se terminent par "_small.jpg" (ligne 18). Enfin, il construit le code HTML (ligne 20 à 25) et le renvoie au navigateur (ligne 26). La construction du code HTML exploite une boucle for (ligne 22 à 24) pour construire autant de balises <img>
qu'il y a d'images dans le répertoire public.
const fs = require("fs");
const http = require("http");
const host = 'localhost';
const port = 8080;
const server = http.createServer();
server.on("request", (req, res) => {
if (req.url.startsWith('/public/')) {
try {
const fichier = fs.readFileSync('.'+req.url);
res.end(fichier);
} catch (err) {
console.log(err);
res.end("erreur ressource");
}
} else if (req.url === '/mur-image') {
let files = fs.readdirSync('./public');
let sFiles = files.filter(f => f.endsWith('_small.jpg'));
let html = '<!DOCTYPE html><html lang="fr">';
html += '<head><title>Mur d\'images</title></head>';
html += '<body> <h1>Mur</h1>';
for (let f of sFiles) {
html += '<img src="/public/' + f + '">';
}
html += '</body></html>';
res.end(html);
} else {
res.end("erreur URL");
}
});
server.listen(port, host, () => {
console.log(`Server running at http://${host}:${port}/`);
});
Code 4.3 : Serveur web qui génère dynamiquement un mur d'images.
Un serveur web qui génère dynamiquement une page web peut aussi devoir analyser la requête demandée pour comprendre ce qu'il doit générer. C'est le cas dans un site de commerce électronique où il faut comprendre qui est le client pour retrouver la commande en cours. C'est le cas aussi d'un calendrier où il faut comprendre quelle année et quel mois sont demandés.
Le protocole HTTP propose plusieurs moyens pour préciser des informations qui seront exploitées par le serveur pour générer la ressource. Par souci de simplicité nous ne considérons dans ce chapitre que l'exploitation de l'URL des requêtes GET. Il est en effet possible de donner des informations supplémentaires dans le corps de la requête HTTP mais cela n'est pas nécessaire pour illustrer le fonctionnement des pages web dynamiques (voir chapitre 5).
Une URL peut contenir de l'information supplémentaire soit dans sa partie chemin soit dans sa partie paramètre (voir Chapitre 5 ). Prenons l'exemple du calendrier qui nécessite de préciser l'année et le mois. On peut alors utiliser l'URL http://localhost:8080/calendrier/2021/12 en exploitant la partie chemin de l'URL pour demander le calendrier de décembre 2021. On peut aussi utiliser l'URL http://localhost:8080/calendrier?annee=2021&mois=12 en précisant l'année et le mois dans la partie paramètre
de l'URL. Le Code 4.4 donne alors le code du serveur dynamique en considérant que les URL demandées intègrent le mois et l'année dans la partie chemin (par exemple : http://localhost:8080/calendrier/2021/12). La ligne 8 vérifie que l'URL demandée commence bien par /calendrier/. La ligne 9 découpe l'URL en plusieurs parties en utilisant le caractère "/" comme séparateur. La ligne 10 vérifie que l'URL contient bien quatre parties. Les lignes 13 et 14 récupèrent l'année et le mois. Les lignes 15 à 21 construisent le code HTML du calendrier. Par souci de simplicité, le calendrier n'est pas affiché mais il est possible de le faire en utilisant des balises HTML permettant d'afficher un tableau. Enfin, la ligne 22 renvoie le code HTML au navigateur.
const fs = require("fs");
const http = require("http");
const host = 'localhost';
const port = 8080;
const server = http.createServer();
server.on("request", (req, res) => {
if (req.url.startsWith('/calendrier/')) {
let urlParts = req.url.split('/');
if (urlParts.length != 4) {
res.end("erreur mois / annees");
} else {
let annee = urlParts[2];
let mois = urlParts[3];
let html = '<!DOCTYPE html><html lang="fr"><head><title>Calendrier</title></head>';
html += '<body> <h1>Calendrier</h1>';
html += '<h2>' + mois + '/' + annee + '</h2>';
//html += '<table border="1">';
//TODO : faire un tableau avec les jours du mois
//html += '</table>';
html += '</body></html>';
res.end(html);
}
} else {
res.end("erreur URL");
}
});
server.listen(port, host, () => {
console.log(`Server running at http://${host}:${port}/`);
});
Code 4.4 : Serveur web qui génère dynamiquement un calendrier en fonction des informations fournies dans l'URL.
La Figure 4.2 montre l'affichage d'une page web servie dynamiquement par notre serveur web si l'URL http://localhost:8080/calendrier/2021/12 est demandée.
Ce chapitre présente les concepts de web statique et web dynamique. Trois points sont à retenir:
Qu'est-ce qu'un serveur web statique ?
Qu'est-ce qu'un serveur web dynamique ?
Est-ce qu'un serveur web peut faire du web statique et du web dynamique ?
Pour générer dynamiquement une page HTML, comment peut-on passer des informations dans l'URL ?
En mode statique, faut-il que les ressources soient stockées dans le répertoire 'public' ?
Réponses: 1-b, 2-a, 3-a, 4-c, 5-c
const fs = require("fs");
const http = require("http");
const host = 'localhost';
const port = 8080;
const server = http.createServer();
server.on("request", (req, res) => {
if (req.url === '/images/image1.jpg') {
const image1 = fs.readFileSync("./images/image1.jpg");
res.end(image1);
} else if (req.url === '/images/image2.jpg') {
const image2 = fs.readFileSync("./images/image2.jpg");
res.end(image2);
} else if (req.url === '/images/image3.jpg') {
const image3 = fs.readFileSync("./images/image3.jpg");
res.end(image3);
} else if (req.url === '/all-images') {
const images = fs.readdirSync("./images");
let pageHTML = "";//let pageHTML = "<!DOCTYPE html><html><body>";
for (let i = 0; i < images.length; i++) {
if (!images[i].endsWith("_small.jpg")) {
pageHTML += images[i];
}
}
res.end(pageHTML);
} else {
const index = fs.readFileSync("./index.html", "utf-8");
res.end(index);
}
})
server.listen(port, host, () => {
console.log(`Server running at http://${host}:${port}/`);
});
Code 4.5 : Code du serveur de l'exercice 1.
<!DOCTYPE html>
<html lang="fr">
<head>
<title>Mon Mur</title>
</head>
<body>
<img src="/images/image1.jpg" width="300">
<img src="/images/image2.jpg" width="300">
<img src="/images/image3.jpg" width="300">
<a href="/all-images">Toutes les images </a>
</body>
</html>
Code 4.6 : Code de la page index.html de l'exercice 1.
Créez un répertoire chap4-exercice1
.
Téléchargez le fichier image.zip. Dézippez-le et sauvez les dix premières images dans un répertoire que vous nommerez images sous le répertoire chap4-exercice1.
Recopiez-le code 4.5 dans un fichier nommé serveur.js que vous stockerez dans le répertoire chap4-exercice1.
Recopiez-le code 4.6 dans un fichier nommé index.html que vous stockerez dans le répertoire chap4-exercice1.
Modifiez le code du serveur web pour qu'il serve les images statiquement et qu'il respecte les deux conventions d'un serveur statique. Par souci de simplicité, on considérera que le répertoire racine est le répertoire images.
La page HTML accessible via l'URL http://localhost:8080/all-images est générée dynamiquement (voir server.js
).
Ce code commence par lire le contenu du répertoire './images', puis il construit une chaîne de caractères contenant tous les noms des fichiers contenus dans ce répertoire, enfin il retourne cette chaîne de caractères au navigateur web.
Modifiez ce code pour qu'un document HTML conforme à la spécification HTML5 soit retourné au navigateur web plutôt qu'une chaine de caractères quelconque. La variable 'pageHTML' doit maintenant contenir du texte au format HTML. Vous devez notamment utiliser la balise <!DOCTYPE html>
pour spécifier que le document est conforme à la spécification HTML5 (voir commentaire ligne 22).
Vous devez aussi faire attention à bien fermer toutes les balises HTML.
Continuez à modifier ce code pour faire en sorte que le document HTML retourné contienne une liste d'images miniatures avec des liens vers les grandes images.
Nous allons reprendre notre projet qui permet d'afficher des images (voir Chapitre 3, projet).
On souhaite maintenant faire en sorte que notre serveur web fonctionne en mode statique et en mode dynamique.
Vous pouvez voir le résultat attendu ici.
Récupérez le code que vous avez réalisé dans le chapitre 3 et copiez-le dans un nouveau répertoire nommé chap4-projet.
Téléchargez le fichier image.zip. Dézippez-le et sauvez toutes les images ainsi que leur miniature dans un répertoire que vous nommerez images sous le répertoire chap4-projet.
Modifiez server.js pour que les images ainsi que les pages HTML soit fournies en mode statique.
Modifiez server.js pour que la page images.html soit générée dynamiquement (vous pourrez supprimer le fichier images.html).
Modifiez le serveur pour que les pages image1.html, image2.html, etc. soient générées dynamiquement (vous pourrez supprimer les fichiers HTML). Vous ferez en sorte que les URLs des images correspondent au pattern suivant : /page-image/id où id correspond à l'id de l'image.