Node.js: servire file statici tramite il modulo http

Node.js: servire file statici tramite il modulo http

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 o 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.

Notate la differenza tra la lettura di un file HTML e quella di un’immagine: Node restituisce sempre un flusso di dati, quindi nel caso di file come quelli HTML, CSS o JavaScript convertiamo il flusso in stringa mentre nel caso dell’immagine il flusso deve essere preservato se poi vogliamo servire il file nel modo corretto, ossia in modalità binaria.

Appare chiara da quanto esposto 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à quindi che di fatto con Node si sta programmando da zero un web server HTTP/S.

Torna su