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-Modified
viene sempre aggiunto, anche nel caso di304 Not Modified
. - L’header di richiesta
if-modified-since
potrebbe 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.