Vai al contenuto principale

[Medium] 📄 HTTP Caching

1. Cos'è l'HTTP caching e perché è importante?

Cos'è l'HTTP caching? Perché è importante?

L'HTTP caching è una tecnica che memorizza temporaneamente le risposte HTTP nei client (browser) o nei server intermedi, in modo che le richieste successive possano riutilizzare i dati memorizzati nella cache invece di contattare nuovamente il server di origine.

Cache vs archiviazione temporanea (Temporary Storage)

Nel contesto tecnico, questi due termini vengono spesso confusi, ma rappresentano scopi diversi.

Cache

Definizione: una copia memorizzata utilizzata per l'ottimizzazione delle prestazioni, focalizzata sul riutilizzo e sull'accesso più veloce.

Caratteristiche:

  • ✅ Obiettivo: miglioramento delle prestazioni
  • ✅ I dati sono destinati al riutilizzo
  • ✅ Strategia esplicita di scadenza/rivalidazione
  • ✅ Solitamente una copia dei dati originali

Esempi:

// HTTP Cache - cache delle risposte API
Cache-Control: max-age=3600 // cache per 1 ora

// Memory Cache - cache del risultato di un calcolo
const cache = new Map();
function fibonacci(n) {
if (cache.has(n)) return cache.get(n); // riutilizza la cache
const result = /* calcolo */;
cache.set(n, result);
return result;
}

Archiviazione temporanea (Temporary Storage)

Definizione: dati memorizzati per esigenze temporanee del flusso di lavoro, con enfasi sul ciclo di vita breve.

Caratteristiche:

  • ✅ Obiettivo: conservazione temporanea
  • ✅ Il riutilizzo è facoltativo
  • ✅ Solitamente ciclo di vita più breve
  • ✅ Può contenere stato intermedio

Esempi:

// sessionStorage - dati temporanei del form
sessionStorage.setItem('formData', JSON.stringify(form));

// percorso temporaneo del file caricato
const tempFile = await uploadToTemp(file);
await processFile(tempFile);
await deleteTempFile(tempFile);

Confronto

CaratteristicaCacheArchiviazione temporanea
Obiettivo principalePrestazioniConservazione temporanea
RiutilizzoSì, spesso ripetutoNon garantito
Ciclo di vitaGuidato dalla policySolitamente breve
Uso tipicoHTTP cache, memory cachesessionStorage, file temporanei
Termine ingleseCacheTemp / Temporary / Buffer

Distinzione pratica

// ===== Scenari di cache =====

// 1) HTTP cache: riutilizza la risposta API
fetch('/api/users').then((response) => response.json());
fetch('/api/users').then((response) => response.json());

// 2) cache di memoizzazione
const memoize = (fn) => {
const cache = new Map();
return (...args) => {
const key = JSON.stringify(args);
if (cache.has(key)) return cache.get(key);
const result = fn(...args);
cache.set(key, result);
return result;
};
};

// ===== Scenari di archiviazione temporanea =====

// 1) salva bozza al momento dell'uscita
window.addEventListener('beforeunload', () => {
sessionStorage.setItem('formDraft', JSON.stringify(formData));
});

// 2) elaborazione temporanea del caricamento
async function handleUpload(file) {
const tempPath = await uploadToTempStorage(file);
const processed = await processFile(tempPath);
await deleteTempFile(tempPath);
return processed;
}

// 3) risultati intermedi temporanei
const tempResults = [];
for (const item of items) {
tempResults.push(process(item));
}
const final = combine(tempResults);

Nello sviluppo web

// HTTP Cache (lunga durata, riutilizzabile)
Cache-Control: public, max-age=31536000, immutable

// sessionStorage (temporaneo, limitato alla tab)
sessionStorage.setItem('tempData', data)

// localStorage (archiviazione persistente, non principalmente uno strato di ottimizzazione cache)
localStorage.setItem('userPreferences', prefs)

Perché questa distinzione è importante

  1. Decisioni di progettazione:
    • Serve ottimizzazione delle prestazioni -> cache
    • Serve persistenza temporanea -> archiviazione temporanea
  2. Gestione delle risorse:
    • La cache si concentra su hit rate e strategia di scadenza
    • L'archiviazione temporanea si concentra su tempistica di pulizia e limiti di dimensione
  3. Chiarezza nei colloqui:
    • Domanda sulle prestazioni -> discuti strategia di caching
    • Domanda sui dati temporanei -> discuti strategia di archiviazione temporanea

Questo articolo si concentra principalmente sulla cache, in particolare sull'HTTP caching.

Vantaggi del caching

  1. Meno richieste di rete
  2. Minor carico sul server
  3. Caricamento delle pagine più veloce
  4. Minor utilizzo di banda
  5. Migliore esperienza utente

Livelli di cache del browser

┌─────────────────────────────────────┐
│ Livelli di cache del browser │
├─────────────────────────────────────┤
│ 1. Memory Cache │
│ - Più veloce, capacità ridotta │
│ - Cancellata con fine tab/sess. │
├─────────────────────────────────────┤
│ 2. Disk Cache │
│ - Più lenta, capacità maggiore │
│ - Archiviazione persistente │
├─────────────────────────────────────┤
│ 3. Service Worker Cache │
│ - Completamente controllata │
│ dall'app │
│ - Abilita il comportamento │
│ offline │
└─────────────────────────────────────┘

2. Quali sono le strategie di HTTP caching?

Quali strategie di caching esistono in HTTP?

Categorie di strategie

Strategie di HTTP Caching
├── Strong Cache (Cache forte)
│ ├── Cache-Control
│ └── Expires
└── Validation Cache (Cache di validazione)
├── Last-Modified / If-Modified-Since
└── ETag / If-None-Match

1. Cache forte (strong cache)

Comportamento: il browser serve direttamente dalla cache locale senza inviare richieste al server di origine.

Cache-Control (HTTP/1.1)

Cache-Control: max-age=3600

Direttive comuni:

// 1) max-age: durata di freschezza in secondi
Cache-Control: max-age=3600

// 2) no-cache: permetti il caching ma richiedi la rivalidazione prima del riutilizzo
Cache-Control: no-cache

// 3) no-store: non cachare affatto
Cache-Control: no-store

// 4) public: cachabile da browser/CDN/proxy
Cache-Control: public, max-age=31536000

// 5) private: solo cache del browser
Cache-Control: private, max-age=3600

// 6) immutable: il contenuto non cambierà durante la durata di freschezza
Cache-Control: public, max-age=31536000, immutable

// 7) must-revalidate: una volta scaduto, deve rivalidare
Cache-Control: max-age=3600, must-revalidate

Expires (HTTP/1.0, legacy)

Expires: Wed, 21 Oct 2025 07:28:00 GMT

Problemi:

  • Usa tempo assoluto
  • Dipende dalla correttezza dell'orologio del client
  • Sostanzialmente sostituito da Cache-Control

2. Cache di validazione (validation cache)

Comportamento: il browser chiede al server se la risorsa è cambiata.

Last-Modified / If-Modified-Since

# prima risposta
Last-Modified: Wed, 21 Oct 2024 07:28:00 GMT

# richiesta successiva
If-Modified-Since: Wed, 21 Oct 2024 07:28:00 GMT

Flusso:

  1. Prima richiesta: il server invia Last-Modified
  2. Richiesta successiva: il browser invia If-Modified-Since
  3. Non modificato: il server restituisce 304 Not Modified
  4. Modificato: il server restituisce 200 OK + nuovo body

ETag / If-None-Match

# prima risposta
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"

# richiesta successiva
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"

Vantaggi:

  • Più preciso di Last-Modified
  • Basato sul contenuto (hash o token di versione)
  • Può rilevare cambiamenti non visibili nei timestamp al secondo

Last-Modified vs ETag

CaratteristicaLast-ModifiedETag
PrecisioneAl secondoToken/hash del contenuto, più preciso
CostoInferiorePotrebbe richiedere calcolo aggiuntivo
Adatto perFile statici generaliControllo preciso della validazione
PrioritàInferioreSuperiore (ETag preferito quando entrambi presenti)

3. Come funziona il caching del browser?

Qual è il flusso di lavoro della cache del browser?

Flusso completo

┌──────────────────────────────────────────────┐
│ Flusso di richiesta risorse del browser │
└──────────────────────────────────────────────┘

1. Controlla Memory Cache

┌───────┴────────┐
│ Cache hit? │
└───────┬────────┘
Sì │ No

2. Controlla Disk Cache

┌───────┴────────┐
│ Cache hit? │
└───────┬────────┘
Sì │ No

3. Controlla Service Worker

┌───────┴────────┐
│ Cache hit? │
└───────┬────────┘
Sì │ No

4. Controlla freschezza

┌───────┴────────┐
│ È scaduto? │
└───────┬────────┘
Sì │ No

5. Rivalidazione con il server

┌───────┴────────┐
│ Modificato? │
└───────┬────────┘
Sì │ No (304)

6. Richiedi nuovo contenuto

┌───────┴────────┐
│ Restituisci │
│ 200 con body │
└────────────────┘

Esempio pratico

// prima richiesta
GET /api/data.json
Response:
200 OK
Cache-Control: max-age=3600
ETag: "abc123"

{ data: "..." }

// entro 1 ora
// cache forte hit -> uso locale, nessuna richiesta
// stato mostrato nei devtools: from disk cache o from memory cache

// dopo 1 ora
GET /api/data.json
If-None-Match: "abc123"

// non modificato
Response:
304 Not Modified

// modificato
Response:
200 OK
ETag: "def456"

{ data: "new data" }

4. Quali sono le strategie di caching comuni?

Strategie di caching pratiche comuni

1. Asset statici a lunga durata

// HTML: nessuna cache lunga, sempre validare
Cache-Control: no-cache

// CSS/JS con hash: cache lunga immutabile
Cache-Control: public, max-age=31536000, immutable
// filename: main.abc123.js

Principio:

  • L'HTML dovrebbe rimanere fresco per riferire gli hash degli asset più recenti
  • Gli asset statici con hash possono essere memorizzati a lungo nella cache
  • Cambio del contenuto -> cambio del filename -> nuovo download

2. Risorse aggiornate frequentemente

// Dati API: cache breve + rivalidazione
Cache-Control: max-age=60, must-revalidate
ETag: "abc123"

3. Strategie per le immagini

// avatar utente: cache media
Cache-Control: public, max-age=86400

// logo/icone: cache più lunga
Cache-Control: public, max-age=2592000

// immagini dinamiche: validazione
Cache-Control: no-cache
ETag: "image-hash"

4. Policy suggerite per tipo di risorsa

const cachingStrategies = {
html: 'Cache-Control: no-cache',
staticWithHash: 'Cache-Control: public, max-age=31536000, immutable',
staticAssets: 'Cache-Control: public, max-age=2592000',
apiData: 'Cache-Control: private, max-age=60',
userData: 'Cache-Control: private, no-cache',
sensitive: 'Cache-Control: no-store',
};

5. Service Worker caching

Service Worker caching

Il Service Worker offre il pieno controllo sul caching a runtime e sul comportamento offline.

Uso di base

// registra il Service Worker
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js');
}
// sw.js
const CACHE_NAME = 'my-app-v1';
const urlsToCache = [
'/',
'/styles/main.css',
'/scripts/main.js',
'/images/logo.png',
];

// install: precache degli asset statici
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
return cache.addAll(urlsToCache);
})
);
});

// fetch: esempio cache-first
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request);
})
);
});

// activate: pulizia delle vecchie cache
self.addEventListener('activate', (event) => {
event.waitUntil(
caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames.map((cacheName) => {
if (cacheName !== CACHE_NAME) {
return caches.delete(cacheName);
}
})
);
})
);
});

Strategie SW comuni

1. Cache First

// migliore per asset statici
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((response) => {
return response || fetch(event.request);
})
);
});

2. Network First

// migliore per richieste API
self.addEventListener('fetch', (event) => {
event.respondWith(
fetch(event.request)
.then((response) => {
const responseClone = response.clone();
caches.open(CACHE_NAME).then((cache) => {
cache.put(event.request, responseClone);
});
return response;
})
.catch(() => {
return caches.match(event.request);
})
);
});

3. Stale While Revalidate

// migliore per risposta veloce + aggiornamento in background
self.addEventListener('fetch', (event) => {
event.respondWith(
caches.match(event.request).then((cachedResponse) => {
const fetchPromise = fetch(event.request).then((networkResponse) => {
caches.open(CACHE_NAME).then((cache) => {
cache.put(event.request, networkResponse.clone());
});
return networkResponse;
});

return cachedResponse || fetchPromise;
})
);
});

6. Come implementare il cache busting?

Come implementare il cache busting?

Il cache busting assicura che gli utenti scarichino gli asset più recenti quando il contenuto cambia.

Metodo 1: hashing del filename (raccomandato)

// con Webpack/Vite
// output: main.abc123.js

// webpack.config.js
module.exports = {
output: {
filename: '[name].[contenthash].js',
},
};
<script src="/js/main.abc123.js"></script>

Pro:

  • ✅ Il nuovo filename forza il download
  • ✅ I vecchi file rimangono cachabili
  • ✅ Best practice del settore

Metodo 2: versione tramite query

<script src="/js/main.js?v=1.2.3"></script>
<link rel="stylesheet" href="/css/style.css?v=1.2.3" />

Contro:

  • ❌ Alcuni CDN/proxy trattano il caching con query string in modo diverso
  • ❌ Manutenzione manuale della versione

Metodo 3: timestamp

// comune solo in sviluppo
const timestamp = Date.now();
const script = document.createElement('script');
script.src = `/js/main.js?t=${timestamp}`;
document.body.appendChild(script);

Caso d'uso:

  • bypass della cache in sviluppo
  • non ideale per la produzione

7. Domande comuni di colloquio sul caching

Domande comuni di colloquio sul caching

Domanda 1: Come impedire che l'HTML venga memorizzato nella cache?

Clicca per vedere la risposta
Cache-Control: no-cache, no-store, must-revalidate
Pragma: no-cache
Expires: 0

oppure meta tag HTML:

<meta
http-equiv="Cache-Control"
content="no-cache, no-store, must-revalidate"
/>
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Expires" content="0" />

Domanda 2: Perché usare ETag invece del solo Last-Modified?

Clicca per vedere la risposta

Vantaggi di ETag:

  1. Più preciso
  2. Validazione basata sul contenuto
  3. Evita casi limite dei timestamp (re-deploy con lo stesso contenuto)
  4. Migliore nei sistemi distribuiti con orologi non sincronizzati

Esempio:

// contenuto invariato, tempo di deploy cambiato
// Last-Modified cambia, ma il contenuto è identico

ETag: 'hash-of-abc'; // stabile se il contenuto non è cambiato

Domanda 3: differenza tra from disk cache e from memory cache?

Clicca per vedere la risposta
CaratteristicaMemory CacheDisk Cache
ArchiviazioneRAMDisco
VelocitàMolto velocePiù lento
CapacitàInferioreMaggiore
PersistenzaSolitamente brevePersistente
PrioritàSuperioreInferiore

Ordine di caricamento tipico (concettuale):

1. Memory Cache
2. Service Worker Cache
3. Disk Cache
4. Rivalidazione / Network

Domanda 4: come forzare il browser a ricaricare le risorse?

Clicca per vedere la risposta

Sviluppo:

// Hard Reload
// Disabilita la cache nei DevTools

const script = document.createElement('script');
script.src = `/js/main.js?t=${Date.now()}`;

Produzione:

// filename con hash (migliore)
main.abc123.js

// query di versione
<script src="/js/main.js?v=2.0.0"></script>

// policy di cache
Cache-Control: no-cache
Cache-Control: no-store

Domanda 5: come implementare la cache offline in una PWA?

Clicca per vedere la risposta
// sw.js
const CACHE_NAME = 'pwa-v1';
const OFFLINE_URL = '/offline.html';

self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME).then((cache) => {
return cache.addAll([
OFFLINE_URL,
'/styles/offline.css',
'/images/offline-icon.png',
]);
})
);
});

self.addEventListener('fetch', (event) => {
if (event.request.mode === 'navigate') {
event.respondWith(
fetch(event.request).catch(() => {
return caches.match(OFFLINE_URL);
})
);
}
});

Una strategia PWA completa spesso combina:

// 1) asset statici: cache-first
// 2) API: network-first
// 3) immagini: cache-first
// 4) navigazione HTML: network-first + fallback offline

8. Best practice

Best practice

✅ Raccomandato

// 1. HTML: nessuna cache lunga per garantire il documento di ingresso più recente
Cache-Control: no-cache

// 2. CSS/JS con hash: cache lunga immutabile
// esempio filename: main.abc123.js
Cache-Control: public, max-age=31536000, immutable

// 3. Immagini: cache media/lunga
Cache-Control: public, max-age=2592000

// 4. Dati API: cache breve + validazione
Cache-Control: private, max-age=60
ETag: "api-response-hash"

// 5. Service Worker per il supporto offline

❌ Da evitare

// male: cache lunga per il documento HTML di ingresso
Cache-Control: max-age=31536000

// male: affidarsi solo a Expires
Expires: Wed, 21 Oct 2025 07:28:00 GMT

// male: nessun header di cache esplicito

// male: stessa policy per tutti i tipi di risorsa
Cache-Control: max-age=3600

Albero decisionale della strategia di cache

È un asset statico?
├─ Sì -> il filename ha un hash?
│ ├─ Sì -> cache lunga immutabile (max-age=31536000, immutable)
│ └─ No -> cache media/lunga (es. max-age=2592000)
└─ No -> È HTML?
├─ Sì -> no-cache
└─ No -> È un'API?
├─ Sì -> cache breve + validazione (max-age=60 + ETag)
└─ No -> decidi in base alla frequenza di aggiornamento

Riferimenti