Ultima Modifica / Se Modificato Da
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: chunkedLast-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 GMTComunque, 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.