Richieste Condizionali HTTP

HTTP ha il concetto delle richieste condizionali, dove il risultato, e addirittura il successi di una richiesta, può essere cambiato comparando la risorsa coinvolta con il valore di un validator. Questo tipo di richieste può essere utile per validare il contenuto di una cache, risparmiando un controllo inutile, per verificare l'integrità di un documento, per esempio quando si fa ripartire un download, o quando si previene la perdita di update, quando si fa l'upload o la modifica di un documento su un server.

Principi

Le richieste condizionali HTTP sono richieste che sono eseguite differnetemente, in base al valore di specifici headers. Questi headers definiscono una precondizione, e il risultato della richiesta cambierà se la precondizione è soddisfatta o no .

I diversi comportamenti sono definiti dal metodo di richiesta usato, e dagli headers usati per  una precondizione:

  • per metodi safe, per esempio GET, che di solito prova a fare un ferch del documento, la richiesta condizionale può essere usata per rimandare indietro il documento, solo se è di rilievo. Questo quindi fa risparmiare bandwidth.
  • per metodi unsafe, per esempio PUT, che di solito fa l'upload di un documento, a richiesta condizionale può essere usata per fare l'upload del documento, solo se l'originale è su qui è basato è lo stesso che è contenuto nel server.

Validatori

Tutti gli header condizionali provano a controllare se la risorsa contenuta nel sever corrisponde a una versione specifica. Per fare questo,la richiesta condizionale deve indicare la versione della risorsa. Visto che comparare l'intera risorsa byte per byte è impraticabile, è non sempre è quello che si vuole, la richiesta trasmette un valore che descrive la  versione. Questi valore sono chiamati validatori,  e possono essere di due tipi:

  • la data dell'ultima modifica del documento, the last-modified date.
  • una stringa opaca, che identifica univocamente ogni versione, chiamata entity tag, o la etag.

Comparare le versioni della stessa risorsa è un po difficile: a seconda del contesto, ci possono essere due tipi di equality checks:

  • La validazione forte è usata quando ci si aspetta l'eguaglianza byte per byte, per esempio quando si fa ripartire un download
  • La validazione debole èser-agent only needs to determine if the two resources have the same content. This is even if they are minor differences; like different ads, or a footer with a different date.

Il tipo di validazione è indipendente dal validatore usato. Sia Last-Modified che ETag permettono entrambi i tipi di validazione, anche se la complessità per implentarli nel lato server può variare. HTTP usa la validazione forte di default, e specifica quando la validazione debole può essere usata.

Validazione forte

La validazione forte consiste nel garantire che la risorsa è, byte per byte, uguale a quella a qui è comparata. Questo è mandatoria per alcuni header condizionali, e il default per altri. La validazione forte è molto stringente e può essere difficile da garantire a livello server, ma garantisce che non ci siano perdite di dati, a volte a spese delle peroformace.

E' abbastanza difficile avere un identificatore unico per la validazione forte con Last-Modified. Spesso questo è fatto usando un ETag con il MD5 hash della risorsa (o una derivata).

Validazione debole

La validazione debole è diversa dalla validazione forte, perchè considera due versioni del documento uguali se il loro contenuto è uguale. Per esempio,una pagina risulterebbe diversa da un altra solo con una data diversa nel footer, o un advertising diverso, verrebbe considerata uguale con la validazione debole. Queste due versioni sono considerate diverse quando si usa la validazione forte. Creare un sistema di etags che crea validazione debole può essere complesso, perchè involve sapere l'importaza dei diversi elementi della pagina, ma è molto utile per ottimizzare le performance della cache.

Headers condizionali

Diversi HTTP header, chiamati header condizionali, portano a richieste condizionali. Queste sono:

If-Match
Ha sucesso se l'ETag della risorsa remota è uguale a quello incluso in questo header. Di default, a meno che l'etag sia preceduto con 'W/', compie una validazione forte.
If-None-Match
Ha successo se l'ETag of the distant resource is different to each listed in this header. By default, unless the etag is prefixed with 'W/', it performs a strong validation.
If-Modified-Since
Ha successo se la data Last-Modified della risorsa remota è più recente .
If-Unmodified-Since
Ha successo se la data Last-Modified della risorsa remota è più vecchia o la stessa dell'header.
If-Range
Simile a If-Match, o If-Unmodified-Since, ma può avere solo un singolo etag, o una data. Se fallisce, la richiesta range fallisce, e invece di una risposta 206 Partial Content, un 200 OK è mandato con la risorsa completa.

Casi d'uso

Cache update

L'uso più comune per le richieste condizionali è aggiornare la cache. Con una cache vuota, o senza una cache, la risorsa richiesta è mandata iindietro con uno stato di 200 OK.

The request issued when the cache is empty triggers the resource to be downloaded, with both validator value sent as headers. The cache is then filled.

Insieme con la risorsa, i validatori sono mandato con gli header. In questo esempuo, entrambi Last-ModifiedETag sono mandati, ma sarebbe stato lo stesso anche se solo uno dei due fosse stato inviato. Questi validatori sono memorizzati nella cache insieme alla risorsa (come tutti gli headers) e saranno usati per creare le richieste condizionali, una volta che la cache diventa obsoleta.

Finche la cache non è obsoleta, non vengono emesse richieste. Ma una volta che diventa obsoleta, questo è controllato principalmente dall'header Cache-Control, il client non usa il valore nella cache direttamente ma richiede una conditional request. Il valore del validatore è usato come parametro delgli header If-Modified-SinceIf-Match.

Se la risorsa non è cambiata, il server ritorna una risposta 304 Not Modified. Questo rende la cache nuova, e il client usa la risorsa contenuta nella cache. Anche se c'è un ciclo di richiesta/risposta che consuma alcune risorse, questo è più efficiente di trasmettere di nuovo l'intera risorsa  attraverso la rete.

With a stale cache, the conditional request is sent. The server can determine if the resource changed, and, as in this case, decide not to send it again as it is the same.

Se la risorsa è cambiata, il server invia semplicemente un 200}. OK risposta, con la nuova versione della risorsa, come se la richiesta non fosse condizionata e il cliente usa questa nuova risorsa (e la mette in cache).

In the case where the resource was changed, it is sent back as if the request wasn't conditional.

Oltre all'impostazione dei validatori sul lato server, questo meccanismo è trasparente: tutti i browser gestiscono una cache e inviano tali richieste condizionali senza alcun lavoro speciale da parte degli sviluppatori Web.

Integrità di un download parziale

Il download parziale dei file è una funzionalità di HTTP che permette di riprendere le operazioni precedenti, risparmiando larghezza di banda e tempo, mantenendo le informazioni già ottenute:

A download has been stopped and only partial content has been retrieved.

Un server che supporta il download parziale lo trasmette inviando l'intestazione Accept-Ranges. Una volta che ciò accade, il client può riprendere il download inviando un'intestazione Ranges} con gli intervalli mancanti:

The client resumes the requests by indicating the range he needs and preconditions checking the validators of the partially obtained request.

Il principio è semplice, ma c'è un potenziale problema: se la risorsa scaricata è stata modificata tra i due download, gli intervalli ottenuti corrisponderanno a due diverse versioni della risorsa e il documento finale sarà corrotto.

Per evitare che ciò avvenga, vengono utilizzate richieste condizionate. Per le gamme, ci sono due modi per farlo. Quello più flessibile utilizza If-Unmodified-Since} e If-Match e il server restituisce un errore se la precondizione fallisce; il client quindi riavvia il download dall'inizio:

When the partially downloaded resource has been modified, the preconditions will fail and the resource will have to be downloaded again completely.

Anche se questo metodo funziona, aggiunge un ulteriore scambio di risposte/richieste quando il documento è stato modificato. Questo compromette le prestazioni, e HTTP ha un'intestazione specifica per evitare questo scenario: If-Range:

The If-Range headers allows the server to directly send back the complete resource if it has been modified, no need to send a 412 error and wait for the client to re-initiate the download.

Questa soluzione è più efficiente, ma leggermente meno flessibile, in quanto è possibile utilizzare un solo etag nella condizione. Raramente è necessaria una tale flessibilità aggiuntiva.

Evitare il problema dell'update perso con un blocco ottimista

Un'operazione comune nelle applicazioni Web è l'aggiornamento di un documento remoto. Questo è molto comune in qualsiasi file system o applicazione di controllo dei sorgenti, ma qualsiasi applicazione che permetta di memorizzare risorse remote necessita di un tale meccanismo. I siti Web comuni, come i wiki e altri CMS, hanno tale necessità.

Con il metodo PUT si è in grado di implementarlo. Il client prima legge i file originali, li modifica e infine li spinge sul server:

Updating a file with a PUT is very simple when concurrency is not involved.

Purtroppo le cose diventano un po' imprecise non appena si tiene conto della concorrenza. Mentre un cliente sta modificando localmente la sua nuova copia della risorsa, un secondo cliente può recuperare la stessa risorsa e fare lo stesso sulla sua copia. Quello che succede dopo è molto spiacevole: quando si impegnano nuovamente sul server, le modifiche del primo client vengono scartate dal push del client successivo, in quanto questo secondo client non è a conoscenza delle modifiche apportate alla risorsa dal primo client. La decisione su chi vince non viene comunicata all'altra parte. Quali modifiche del client devono essere mantenute, variano in funzione della velocità con cui vengono effettuate; questo dipende dalle prestazioni dei client, del server e anche dell'operatore che modifica il documento presso il client. Il vincitore cambierà da una volta all'altra. Questo è un race condition e porta a comportamenti problematici, difficili da rilevare e da fare il debug:

When several clients update the same resource in parallel, we are facing a race condition: the slowest win, and the others don't even know they lost. Problematic!

Non c'è modo di affrontare questo problema senza infastidire uno dei due client. Tuttavia, gli aggiornamenti persi e le condizioni di gara sono da evitare. Vogliamo risultati prevedibili e ci aspettiamo che i clienti siano avvisati quando le loro modifiche vengono respinte.

Le richieste condizionali permettono di implementare l'algoritmo di blocco ottimistico (utilizzato dalla maggior parte dei wiki o dai sistemi di controllo delle sorgenti). Il concetto è quello di permettere a tutti i client di ottenere copie della risorsa, quindi lasciarli modificare localmente, controllando la concorrenza permettendo al primo client di presentare un aggiornamento. Tutti gli aggiornamenti successivi, basati sulla versione ormai obsoleta della risorsa, vengono respinti:

Conditional requests allow to implement optimistic locking: now the quickest wins, and the others get an error.

Questo è implementato utilizzando le intestazioni If-Match} o If-Unmodified-Since}. Se l'etag non corrisponde al file originale, o se il file è stato modificato da quando è stato ottenuto, la modifica viene semplicemente rifiutata con un 412Precondition Failed error. petta poi al cliente affrontare l'errore: o notificando all'utente di ricominciare da capo (questa volta sulla versione più recente), o mostrando all'utente una diff di entrambe le versioni, aiutandolo a decidere quali modifiche desidera mantenere.

Gestire il primo upload di una risorsa

Il primo caricamento di una risorsa è un caso limite del precedente. Come ogni aggiornamento di una risorsa, è soggetto a una condizione di gara se due clienti cercano di eseguire in tempi simili.Per evitare questo, le richieste condizionate possono essere utilizzate: aggiungendo If-None-Match con il valore speciale di '*', che rappresenta un qualsiasi etag. La richiesta avrà successo, solo se la risorsa non esisteva prima:

Like for a regular upload, the first upload of a resource is subject to a race condition: If-None-Match can prevent it.

If-None-Match funzionerà solo con server conformi a HTTP/1.1 (e successivi). Se non si è sicuri che il server sarà conforme, è necessario prima emettere una richiesta HEAD alla risorsa per verificarlo.

Conclusione

Le richieste condizionali sono una caratteristica chiave di HTTP, e permettono la construzione di efficienti e complicate applicazioni. Per il caching o la ripresa dei download, l'unico lavoro richiesto per i webmaster è quello di configurare correttamente il server; impostare gli etags corretti in alcuni ambienti può essere complicato. Una volta raggiunto, il browser servirà le richieste condizionali previste.

Per i meccanismi di chiusura, è l'opposto: Gli sviluppatori web devono emettere una richiesta con le intestazioni appropriate, mentre i webmaster possono per lo più fare affidamento sull'applicazione per effettuare i controlli per loro.

In entrambi i casi è chiaro, le richieste condizionali sono una caratteristica fondamentale del Web.