
Création d'un serveur HTTP
Apprenez les fondamentaux de la creation d'un serveur HTTP en Node.js pur avec le module `http`. Gerez requetes, reponses, headers et codes de statut.
Les fondations de la communication web : le protocole HTTP
Au coeur de la quasi-totalité des interactions sur le World Wide Web se trouve le protocole HTTP (HyperText Transfer Protocol). C'est le langage standardisé qu'utilisent les navigateurs (clients) pour demander des ressources (pages HTML, images, données API...) et les serveurs pour fournir ces ressources. Comprendre les bases de HTTP est donc indispensable pour quiconque souhaite développer des applications web.
Node.js, étant une plateforme conçue pour le réseau, intègre nativement un module puissant et fondamental pour travailler avec ce protocole : le module `http`. Ce module fournit les outils de bas niveau nécessaires pour créer des serveurs capables d'écouter les requêtes HTTP entrantes et d'y répondre, ainsi que pour agir en tant que client en envoyant des requêtes HTTP vers d'autres serveurs.
Bien que la plupart des applications web modernes en Node.js utilisent des frameworks plus évolués comme Express.js qui simplifient grandement le processus, comprendre le fonctionnement du module `http` de base est essentiel. Cela permet de mieux saisir ce que font ces frameworks sous le capot et offre la flexibilité nécessaire pour des cas d'usage très spécifiques ou pour des applications nécessitant une performance maximale avec un minimum de dépendances.
Votre premier serveur : `http.createServer()`
La création d'un serveur HTTP basique en Node.js est étonnamment simple grâce au module `http`. La fonction clé est `http.createServer()`. Cette fonction prend en argument une fonction de rappel (callback), souvent appelée "request listener", qui sera exécutée chaque fois que le serveur recevra une nouvelle requête HTTP.
Cette fonction de rappel reçoit automatiquement deux arguments principaux : un objet représentant la requête entrante (communément nommé `req` ou `request`) et un objet représentant la réponse à construire (communément nommé `res` ou `response`). C'est à l'intérieur de cette fonction que vous allez analyser la requête et générer la réponse appropriée.
Une fois le serveur créé avec `http.createServer()`, il n'est pas encore actif. Il faut explicitement lui demander d'écouter sur un port spécifique de la machine en utilisant la méthode `server.listen(port, [hostname], [callback])`. Le port est un numéro (par exemple, 3000, 8080) qui identifie l'application sur la machine. Le callback de `listen` est exécuté une seule fois, lorsque le serveur a démarré avec succès.
Voici un exemple minimaliste d'un serveur "Hello, World!" :
const http = require('http'); // 1. Importer le module http
const hostname = '127.0.0.1'; // Adresse locale
const port = 3000;
// 2. Créer le serveur avec le request listener
const server = http.createServer((req, res) => {
console.log(`Requête reçue pour : ${req.url} avec la méthode ${req.method}`);
// 3. Définir le code de statut et les en-têtes de la réponse
res.statusCode = 200; // 200 OK
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
// 4. Ecrire le corps de la réponse
res.end('Bonjour le monde !\n'); // end() envoie la réponse et la termine
});
// 5. Démarrer l'écoute du serveur
server.listen(port, hostname, () => {
console.log(`Serveur démarré sur http://${hostname}:${port}/`);
});Vous pouvez enregistrer ce code dans un fichier (par exemple, `server.js`) et l'exécuter avec `node server.js`. En ouvrant votre navigateur à l'adresse `http://127.0.0.1:3000`, vous devriez voir le message "Bonjour le monde !".
Gerer les requetes (`request`) et les reponses (`response`)
Les objets `request` et `response` passés à votre listener `createServer` sont les outils essentiels pour interagir avec le client. Ils sont des instances de classes spécifiques : `http.IncomingMessage` pour la requête et `http.ServerResponse` pour la réponse. Ils héritent également des fonctionnalités des Streams (Readable pour `request`, Writable pour `response`).
L'objet `request` (`req`) vous donne accès aux informations envoyées par le client :
req.url: La chaîne de caractères de l'URL demandée (par exemple, `/users`, `/about?id=123`).req.method: La méthode HTTP utilisée ('GET','POST','PUT','DELETE', etc.).req.headers: Un objet contenant les en-têtes (headers) de la requête envoyés par le client (host,user-agent,accept,content-type, etc.). Les noms des headers sont en minuscules.- Evénements de Stream : Comme `req` est un stream Readable, vous pouvez écouter l'événement `data` pour recevoir le corps d'une requête (par exemple, pour les requêtes POST ou PUT) et l'événement `end`.
L'objet `response` (`res`) vous permet de construire et d'envoyer la réponse au client :
res.statusCode: Propriété pour définir le code de statut HTTP (par exemple, 200, 404, 500).res.setHeader(name, value): Définit un en-tête HTTP pour la réponse (par exemple, `'Content-Type'`, `'Location'`, `'Cache-Control'`).res.writeHead(statusCode, [statusMessage], [headers]): Méthode pratique pour écrire le code de statut et les en-têtes en une seule fois. Attention, elle doit être appelée avant `res.write()` ou `res.end()`, et une seule fois par réponse.res.write(chunk, [encoding], [callback]): Ecrit un morceau de données dans le corps de la réponse. Peut être appelée plusieurs fois.res.end([data], [encoding], [callback]): Termine la réponse. Peut éventuellement envoyer un dernier morceau de données. Cette méthode doit impérativement être appelée pour chaque réponse afin de signaler au client que la transmission est terminée. Oublier `res.end()` laissera la connexion ouverte indéfiniment.
Exemple de routage simple basé sur l'URL et la méthode :
const http = require('http');
const server = http.createServer((req, res) => {
const url = req.url;
const method = req.method;
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
if (url === '/') {
res.statusCode = 200;
res.end('Page d\'accueil');
} else if (url === '/about' && method === 'GET') {
res.statusCode = 200;
res.end('A propos de nous');
} else if (url === '/contact' && method === 'POST') {
// Ici, il faudrait lire le corps de la requête (req.on('data', ...))
res.statusCode = 201; // 201 Created (par exemple)
res.end('Merci pour votre message !');
} else {
res.statusCode = 404; // 404 Not Found
res.end('Page non trouvée');
}
});
server.listen(3000, () => console.log('Serveur démarré sur http://localhost:3000'));
En-tetes (Headers) et codes de statut HTTP
Les en-têtes (headers) HTTP sont des paires clé-valeur qui transportent des métadonnées sur la requête ou la réponse. Ils sont essentiels pour la négociation de contenu, l'authentification, la mise en cache, et bien plus encore.
Côté requête (`req.headers`), vous pouvez lire des informations comme :
host: Le nom de domaine demandé par le client.user-agent: Informations sur le navigateur ou le client qui fait la requête.accept: Les types de contenu que le client accepte (ex: `'text/html, application/json'`).content-type: Le type de média du corps de la requête (important pour POST/PUT, ex: `'application/json'`, `'application/x-www-form-urlencoded'`).authorization: Les informations d'authentification (ex: token).
Côté réponse (`res.setHeader()` ou via `res.writeHead()`), vous définissez des informations pour le client :
Content-Type: Le type de média du corps de la réponse que vous envoyez (ex: `'text/html'`, `'application/json'`, `'image/jpeg'`). C'est crucial pour que le navigateur interprète correctement la réponse.Content-Length: La taille du corps de la réponse en octets. Utile pour le client.Location: Utilisé pour les redirections (avec les codes 3xx). Indique la nouvelle URL.Cache-Control,Expires: Instructions pour la mise en cache par le client ou les proxys.Set-Cookie: Pour définir un cookie dans le navigateur du client.
Les codes de statut HTTP indiquent le résultat de la tentative de traitement de la requête. Ils sont regroupés par centaines :
- 1xx (Information) : Requête reçue, processus en cours (rarement utilisé directement).
- 2xx (Succès) : L'action a été reçue, comprise et acceptée avec succès. Ex:
200 OK(standard),201 Created(ressource créée après POST/PUT),204 No Content(succès mais pas de contenu à renvoyer). - 3xx (Redirection) : D'autres actions sont nécessaires pour compléter la requête. Ex:
301 Moved Permanently(redirection permanente),302 Found(redirection temporaire),304 Not Modified(ressource non modifiée, utilisé avec le cache). - 4xx (Erreur Client) : La requête contient une syntaxe incorrecte ou ne peut pas être satisfaite. Ex:
400 Bad Request(requête malformée),401 Unauthorized(authentification requise),403 Forbidden(accès refusé),404 Not Found(ressource non trouvée). - 5xx (Erreur Serveur) : Le serveur n'a pas réussi à traiter une requête apparemment valide. Ex:
500 Internal Server Error(erreur générique côté serveur),503 Service Unavailable(serveur temporairement indisponible).
Il est essentiel de renvoyer le code de statut approprié pour que les clients (navigateurs, autres services) comprennent le résultat de leur requête.
// ... début du serveur http.createServer ...
if (req.url === '/data') {
const jsonData = JSON.stringify({ message: 'Ceci est du JSON', success: true });
res.writeHead(200, {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(jsonData, 'utf8') // Important pour certains clients
});
res.end(jsonData);
} else if (req.url === '/old-page') {
res.writeHead(301, { // Redirection permanente
'Location': '/new-page'
});
res.end(); // Pas besoin de corps pour une redirection
} else {
res.statusCode = 404;
res.setHeader('Content-Type', 'text/html; charset=utf-8');
res.end('Oops! Page introuvable.
');
}
// ... fin du serveur ...
Redirections et gestion elementaire des erreurs
Une redirection HTTP indique au client qu'il doit refaire sa requête vers une autre URL. Comme vu précédemment, cela se fait en envoyant un code de statut 3xx (généralement 301 pour permanent ou 302 pour temporaire) et un en-tête `Location` contenant la nouvelle URL.
La gestion des erreurs de base consiste à renvoyer des codes de statut appropriés pour les erreurs courantes. Une route non reconnue devrait renvoyer un `404 Not Found`. Si une erreur interne se produit lors du traitement de la requête (par exemple, impossible de se connecter à la base de données), un `500 Internal Server Error` est approprié. Il est recommandé de ne pas envoyer les détails techniques de l'erreur au client en production pour des raisons de sécurité, mais plutôt un message générique.
Exemple combinant redirection et erreurs :
const http = require('http');
const server = http.createServer((req, res) => {
try { // Englober dans un try...catch pour les erreurs serveur
if (req.url === '/ancien-lien') {
res.writeHead(301, { 'Location': '/nouveau-lien' });
return res.end();
}
if (req.url === '/nouveau-lien') {
res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
return res.end('Vous êtes sur la nouvelle page !');
}
// Simuler une erreur serveur
if (req.url === '/erreur') {
throw new Error('Quelque chose a mal tourné !');
}
// Si aucune route ne correspond -> 404
res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
res.end('404 - Ressource non trouvée');
} catch (error) {
console.error('Erreur serveur détectée:', error); // Logger l'erreur côté serveur
res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });
res.end('500 - Erreur interne du serveur');
}
});
server.listen(3000, () => console.log('Serveur démarré...'));
Bien que fonctionnelle, cette approche devient rapidement fastidieuse et difficile à maintenir pour des applications complexes avec de nombreuses routes. C'est là que les frameworks comme Express.js entrent en jeu, en fournissant des abstractions pour le routage, la gestion des erreurs, les middlewares, et bien plus encore, ce que nous explorerons ensuite.