In Node.js, servire file statici partendo da zero è un’operazione che
richiede alcune conoscenze preliminari prima di poter essere messa in
pratica.
A differenza di quanto accade con i web server tradizionali, Node è del
tutto agnostico rispetto al tipo di file servito. Infatti, pur disponendo
del modulo core fs
, con cui è possibile svolgere numerose
operazioni su file e directory, non esiste al momento un modo diretto per
conoscere il tipo MIME di un file.
Le soluzioni esistenti fanno ricorso all’estensione del file, una pratica
non del tutto sicura, o all’analisi del contenuto del file da servire.
Come si capirà dall’esempio proposto, è chiaro che per essere produttivi in
Node non si può fare a meno dell’ecosistema di moduli NPM già disponibili
per i vari task che si intendono svolgere.
Partiamo quindi col definire un gestore per le richieste HTTP:
'use strict';
const http = require('http');
const fs = require('fs');
class RequestHandler {
constructor(request, response) {
this.req = request;
this.res = response;
}
handle(path, method, cb) {
let self = this;
if (this.req.url === path && this.req.method === method) {
cb(self.req, self.res);
}
}
}
In pratica se il percorso e il metodo HTTP corrispondono a quelli usati dal
metodo della classe, viene invocata la funzione di callback che usa gli
oggetti della richiesta e della risposta del modulo http
.
Vogliamo servire un’immagine del logo di Node. Dobbiamo verificare che il
percorso corrisponda ad un file esistente e quindi servirlo leggendone il
contenuto e restituendolo come output binario con il corretto tipo MIME.
const server = http.createServer((req, res) => {
const handler = new RequestHandler(req, res);
handler.handle('/', 'GET', (request, response) => {
let home = fs.readFileSync('./templates/files.html').toString();
response.writeHead(200, {
'Content-Type': 'text/html'
});
response.end(home);
});
handler.handle('/public/images/image.jpg', 'GET', (request, response) => {
let srcPath = '.' + request.url;
if (!fs.existsSync(srcPath)) {
let notFound = fs.readFileSync('./templates/404.html').toString();
response.writeHead(404, {
'Content-Type': 'text/html'
});
response.end(notFound);
} else {
let contents = fs.readFileSync(srcPath);
response.writeHead(200, {
'Content-Type': 'image/jpeg',
'Content-Length': Buffer.byteLength(contents)
});
response.end(contents, 'binary');
}
});
});
server.listen(3000);
Stiamo effettuando la verifica dell’esistenza e la lettura del file in modo
sincrono, ma volendo è preferibile utilizzare i metodi asincroni di Node in
modo da non bloccare l’esecuzione anche se questo ci costa la gestione di
un callback.
Si noti la differenza tra la lettura di un file HTML e quella di
un’immagine. Node restituisce sempre un flusso di dati; di conseguenza, nel caso di
file HTML, CSS o JavaScript, possiamo convertire il flusso in stringa.
Tuttavia, nel caso dell’immagine, il flusso deve essere preservato se poi
vogliamo servire il file nel modo corretto, ossia in modalità binaria.
Conclusione
Appare chiara, da quanto esposto fin qui, la ragione per cui nello sviluppo con
Node.js non si possa prescindere dall’utilizzo di framework o moduli
esistenti se si vuole evitare ogni ritardo nella messa in produzione di un
progetto.
Se anche servire una sola immagine è una procedura non immediata, si capirà
che, di fatto, con Node si sta programmando da zero un web server
HTTP/S.