Ultima Modifica / Se Modificato Da
Scritto da Francesco Di Donato 16 novembre 2023 4 minuti di lettura
Manteniamo tutto semplice - NodeJS, senza dipendenze. Costruiamo insieme alcuni endpoint, ognuno con header diversi, e scopriamo come si comporta il browser in base agli header ricevuti.
Vai direttamente all’endpoint /no-headers o dai un’occhiata (molto veloce) al server più semplice che ci sia.
import { createServer } from "http";
import noHeaders from "./src/index.mjs";
createServer((req, res) => {
switch (req.url) {
case "/no-headers":
return noHeaders(req, res);
}
}).listen(8000, "127.0.0.1", () =>
console.info("Esposto su http://127.0.0.1:8000")
);
import fs from "fs/promises";
import path from "path";
export function to(promise) {
return promise.then((res) => [res, null]).catch((err) => [null, err]);
}
export async function getView(name) {
const filepath = path.resolve(
process.cwd(),
"src",
"views",
name + ".html"
);
return await to(fs.readFile(filepath, "utf-8"));
}
export async function getViewStats(name) {
const filepath = path.resolve(process.cwd(), "src", "views", name + ".html");
return await to(fs.stat(filepath));
}
Aggiungi un file HTML in src/views/index.html. Il suo contenuto è irrilevante.
No Headers - Endpoint
Legge semplicemente il file e lo invia all’utente. A parte il Content-Type, non viene aggiunto alcun header relativo alla cache.
import { getView } from "./utils.mjs";
export default async (req, res) => {
res.setHeader("Content-Type", "text/html");
const [html, err] = await getView("index");
if (err) {
res.writeHead(500).end("Errore interno del server");
return;
}
res.writeHead(200).end(html);
};
Avvia il server (node index.mjs), apri /no-headers, e controlla la scheda strumenti per sviluppatori > rete. Abilita preserva log e premi aggiorna alcune volte.

Apri uno qualsiasi di essi e controlla gli Header di risposta - non c’è nulla relativo alla cache e il browser obbedisce.
HTTP/1.1 200 OK
Content-Type: text/html
Date: <data>
Connection: keep-alive
Keep-Alive: timeout=5
Transfer-Encoding: chunked
Last-Modified 🔗 - Endpoint
Crea un nuovo endpoint (da registrare all’url /last-modified). Legge il tempo di modifica del file (mtime) e lo aggiunge formattato come UTC sotto l’header Last-Modified.
import { getView, getViewStats } from "./utils.mjs";
export default async (req, res) => {
res.setHeader("Content-Type", "text/html");
const [stats, errStats] = await getViewStats("index");
if (errStats) {
res.writeHead(500).end("Errore interno del server");
return;
}
const lastModified = new Date(stats.mtime);
res.setHeader("Last-Modified", lastModified.toUTCString());
const [html, errGet] = await getView("index");
if (errGet) {
res.writeHead(500).end("Errore interno del server");
return;
}
res.writeHead(200).end(html);
};
Infatti, tra gli header di risposta a /last-modified, trovi:
HTTP/1.1 200 OK
Last-Modified: Gio, 15 Nov 2023 19:18:46 GMT
Comunque, se aggiorni la pagina, l’intera risorsa viene ancora scaricata.
Tuttavia qualcosa è cambiato - il browser ha trovato Last-Modified, quindi riutilizza il valore per l’header di richiesta If-Modified-Since. Il server riceve quel valore e, se la condizione non è vera (non modificato da allora), restituisce lo stato 304 Not Modified.
import { getView, getViewStats } from "./utils.mjs";
export default async (req, res) => {
res.setHeader("Content-Type", "text/html");
const [stats, _] = await getViewStats("index");
const lastModified = new Date(stats.mtime);
lastModified.setMilliseconds(0); // IMPORTANTE
res.setHeader("Last-Modified", lastModified.toUTCString());
const ifModifiedSince = new Headers(req.headers).get("If-Modified-Since");
if (
ifModifiedSince &&
new Date(ifModifiedSince).getTime() >= lastModified.getTime()
) {
res.writeHead(304).end();
return;
}
// Questo viene fatto SOLO SE non era un 304!
const [html, _] = await getView("index");
res.writeHead(200, headers).end(html);
};
Secondo la specifica Last-Modified 🔗
Nota:
- L’header di risposta
Last-Modifiedviene sempre aggiunto, anche nel caso di304 Not Modified. - L’header di richiesta
if-modified-sincepotrebbe non essere presente - succede sicuramente al primo chiamata da un nuovo client.
Soprattutto, le date HTTP sono sempre espresse in GMT, mai in ora locale 🔗.
Quando si formatta una data usando toUTCString, potresti notare che la stringa risultante perde informazioni sui millisecondi. Tuttavia mtime mantiene una precisione di millisecondi - potrebbe avere qualche millisecondo in più rispetto al valore ricevuto dal client, che, dopo la formattazione, perde quei millisecondi.
Per garantire un confronto valido tra i due valori, diventa necessario rimuovere i millisecondi da mtime prima di effettuare il confronto.
lastModified.setMilliseconds(0);
Infine, richiedi la risorsa più volte.
Ora, vai a aggiornare il file HTML. Poi chiedi al browser di aggiornare e aspettati di ricevere una risposta 200 OK.

È fondamentale riconoscere che la risposta 304 è costantemente più leggera della risposta 200. Oltre a ridurre il carico dati, contribuisce a un diminuzione del carico sul server. Questa ottimizzazione va oltre la semplice lettura di file HTML e può applicarsi a qualsiasi operazione complessa o che richiede molte risorse.
Last-Modifiedè un header di caching debole, poiché il browser applica una euristica per determinare se recuperare l’elemento dalla cache oppure no. Le euristiche variano tra i browser.