HTTP 412 Precondition Failed: conditional request

Cos'e HTTP 412 Precondition Failed

Il codice HTTP 412 Precondition Failed indica che una o più condizioni espresse negli header della richiesta non sono soddisfatte dallo stato corrente della risorsa. E il meccanismo principale per prevenire lost update e conflitti di concorrenza nelle API REST.

Header di precondizione

I principali header che possono generare un 412:

  • If-Match: la risorsa deve avere un'ETag specifico.
  • If-None-Match: la risorsa non deve avere quell'ETag (usato anche con * per "non esiste").
  • If-Unmodified-Since: la risorsa non deve essere stata modificata da una certa data.
  • If-Modified-Since: usato in GET, su altri metodi puo generare 412.

Scenario: lost update problem

Senza precondizioni, due utenti possono sovrascriversi a vicenda:

  1. Alice fa GET /article/1 e riceve testo "v1" con ETag "abc".
  2. Bob fa GET /article/1 e riceve "v1" con ETag "abc".
  3. Alice fa PUT /article/1 con testo "v2-alice". Server salva. ETag diventa "def".
  4. Bob fa PUT /article/1 con testo "v2-bob". Server salva. La modifica di Alice e persa.

Con precondizioni:

  1. Bob fa PUT /article/1 con If-Match: "abc".
  2. L'ETag corrente e "def", non "abc". Server risponde 412.
  3. Bob deve rifare GET, mergiare le modifiche, e tentare di nuovo.

Esempio pratico

PUT /api/documents/42 HTTP/1.1
If-Match: "doc42-v17"
Content-Type: application/json

{"title": "Nuovo titolo"}

# Se l'ETag corrente del documento e ancora "doc42-v17":
HTTP/1.1 200 OK
ETag: "doc42-v18"

# Se qualcuno ha modificato il documento nel frattempo:
HTTP/1.1 412 Precondition Failed
Content-Type: application/json
ETag: "doc42-v19"

{"error": "Document has been modified since you last read it"}

If-Match: * (creazione idempotente)

Un caso speciale: If-None-Match: * significa "esegui solo se la risorsa non esiste". Utile per PUT idempotenti di creazione:

PUT /api/users/alice HTTP/1.1
If-None-Match: *
Content-Type: application/json

{"email": "alice@example.com"}

# Se /api/users/alice esiste già:
HTTP/1.1 412 Precondition Failed

Implementazione server-side

Pseudo-codice di una API REST che supporta 412:

def update_document(doc_id, data, if_match_header):
    doc = db.get(doc_id)
    if not doc:
        return 404
    current_etag = compute_etag(doc)
    if if_match_header and if_match_header != current_etag:
        return 412
    doc.update(data)
    db.save(doc)
    return 200, new_etag

If-Unmodified-Since

Alternativa basata su timestamp:

PUT /api/articles/15 HTTP/1.1
If-Unmodified-Since: Mon, 29 Jun 2026 09:00:00 GMT

Se l'articolo e stato modificato dopo le 9:00, server risponde 412. Granularita al secondo: ETag e più preciso.

412 vs 409 Conflict

  • 412: la precondizione esplicita in header non e soddisfatta.
  • 409: la richiesta confligge con lo stato corrente per ragioni semantiche più generali.

Usa 412 quando il client invia header condizionali; 409 quando il conflitto e implicito (es. transizioni di stato illegali).

412 vs 428 Precondition Required

Il 428 e l'opposto del 412: il server richiede che il client invii precondizioni e rifiuta richieste senza. Tipico in API critiche che vogliono forzare l'optimistic concurrency.

Gestione client

Quando ricevi un 412, hai due strategie:

  • Retry con merge: rifai GET, applica le tue modifiche al contenuto aggiornato, ritenta PUT con nuovo ETag.
  • User intervention: mostra all'utente che la risorsa e cambiata, offri possibilità di vedere la nuova versione e decidere cosa fare.

Optimistic concurrency control

Il 412 e la spina dorsale dell'optimistic concurrency control: invece di lockare risorse pessimisticamente, si lavora ottimisticamente e si verifica al momento della scrittura. Scala meglio su sistemi distribuiti rispetto al pessimistic locking.

Caching CDN e 412

I CDN devono passare gli header If-Match/If-None-Match al backend per le richieste non-GET. Verifica la configurazione del tuo edge: alcuni li strippano per default su POST/PUT.

Edit conflict UX patterns

Quando il client riceve 412, deve mostrare all'utente una UI di risoluzione conflitti. Pattern comuni: diff side-by-side, three-way merge (versione locale, server, base comune), opzione "sovrascrivi comunque" con conferma esplicita. Google Docs e Notion gestiscono questi conflitti in modo trasparente per l'utente.

412 in API mobile

App mobile offline-first sincronizzano modifiche quando torna connessione. Se nel frattempo altri dispositivi hanno modificato gli stessi dati, il server risponde 412. L'app mobile applica strategia di merge: prevale l'ultimo modificato, prevale local con conferma, mostra differenze, ecc.

Strong vs weak validators

Strong ETag (es. SHA del contenuto) garantisce identità esatta. Weak ETag (W/) permette piccole differenze (es. timestamp di compressione). Per 412 conditional, usa sempre strong: vuoi rejection precisa di ogni minima modifica.

Database-level optimistic locking

Sotto il cofano, il 412 si appoggia spesso a database optimistic locking via colonna version o updated_at. Esempio SQL: UPDATE docs SET title=?, version=version+1 WHERE id=? AND version=?. Se rows affected = 0, qualcuno ha modificato nel frattempo: emetti 412.

Performance considerations

Computing ETag costoso (hash di body grande) puo essere cached. Strategy: memorizzare ETag in DB insieme al contenuto, ricomputare solo on update. Per risorse derivate (es. JSON serializzato da entity), ETag puo essere hash deterministico della rappresentazione finale.

Esempio REST API CRUD completo

// GET fornisce ETag
GET /api/articles/42
HTTP/1.1 200 OK
ETag: "abc"
{"title": "Original", "content": "..."}

// PUT con If-Match
PUT /api/articles/42
If-Match: "abc"
{"title": "Updated"}

HTTP/1.1 200 OK
ETag: "def"

Sequenza canonica: GET per leggere stato attuale e ottenere ETag, PUT con If-Match per modificare condizionalmente. Senza ETag fresco, qualsiasi modifica e a rischio lost update.

412 in workflow approvativi

In sistemi di workflow (approvazione documenti, ticket helpdesk), gli stati cambiano da utenti diversi. 412 protegge da race condition: due manager che approvano contemporaneamente, solo il primo successa, il secondo riceve 412 e vede il stato aggiornato.

If-Match: * vs specifico

If-Match: * indica "la risorsa deve esistere", ma non specifica quale versione. Utile per DELETE idempotente: cancella solo se esiste (404 altrimenti). If-Match con ETag specifico e più strict: cancella solo se la versione e quella che mi aspetto.

Hai bisogno di aiuto?

Se il tuo sito mostra errori HTTP, il team di G Tech Group puo aiutarti. Contattaci tramite il modulo di contatto.

Hai trovato utile quest'articolo?