Peticiones condicionales en HTTP
HTTP tiene un concepto de peticiones condicionales, donde el resultado, e incluso el éxito de una petición, se puede cambiar comparando los recursos afectados con el valor de un validador. Dichas peticiones pueden ser útiles para validar el contenido de un caché, y evitar un control inútil, para verificar la integridad de un documento, como al reanudar una descarga, o al evitar perder actualizaciones al cargar o modificar un documento en el servidor.
Principios
Las peticiones condicionales HTTP son peticiones que se ejecutan de manera diferente, dependiendo del valor de encabezados específicos. Estos encabezados definen una condición previa, y el resultado de la petición será diferente si la condición previa coincide o no.
Los diferentes comportamientos están definidos por el método de petición utilizado y por el conjunto de encabezados utilizados para una condición previa:
- para métodos seguros, como
GET
, que generalmente intenta recuperar un documento, la petición condicional se puede usar para devolver el documento, solo si es relevante. Por lo tanto, esto ahorra ancho de banda. - para métodos no seguros, como
PUT
, que generalmente carga un documento, la petición condicional se puede usar para cargar el documento, solo si el original en el que se basa es el mismo que el almacenado en el servidor.
Validadores
Todos los encabezados condicionales intentan verificar si el recurso almacenado en el servidor coincide con una versión específica. Para lograr esto, las peticiones condicionales deben indicar la versión del recurso. Como la comparación de todo el recurso byte a byte es impracticable, y no siempre lo que se desea, la petición transmite un valor que describe la versión. Tales valores se llaman validadores y son de dos tipos:
- la fecha de la última modificación del documento, la fecha last-modified.
- una cadena opaca, que identifica de forma única cada versión, llamada etiqueta de entidad, o etag.
Comparar versiones del mismo recurso es un poco complicado: según el contexto, hay dos tipos de controles de igualdad:
- Validación fuerte, se utiliza cuando se espera una igualdad byte a byte, por ejemplo, al reanudar una descarga.
- Validación débil, se utiliza cuando el agente de usuario solo necesita determinar si los dos recursos tienen el mismo contenido. Incluso si son pequeñas diferencias, como diferentes anuncios, o un pie de página con una fecha diferente.
El tipo de validación es independiente del validador utilizado. Ambos Last-Modified
y ETag
permiten ambos tipos de validación, aunque la complejidad para implementarlo en el lado del servidor puede variar. HTTP utiliza la validación fuerte de forma predeterminada, y especifica cuándo se puede usar una validación débil.
Validación fuerte
La validación sólida consiste en garantizar que el recurso es, byte a byte, idéntico al que se compara. Esto es obligatorio para algunos encabezados condicionales, y el predeterminado para los demás. La validación sólida es muy estricta y puede ser difícil garantizarla a nivel del servidor, pero garantiza que no se pierdan datos en ningún momento, a veces a expensas del rendimiento.
Es bastante difícil tener un identificador único para una validación fuerte con Last-Modified
. A menudo, esto se hace usando una ETag
con el hash MD5 del recurso (o un derivado).
Validación débil
La validación débil difiere de la validación fuerte, ya que considera dos versiones del documento como idénticas si el contenido es equivalente. Por ejemplo, una página que diferiría de otra solo por una fecha diferente en su pie de página, o una publicidad diferente, se consideraría idéntica a la otra con validación débil. Estas dos versiones iguales se consideran diferentes cuando se usa una validación fuerte. Construir un sistema de etags que cree una validación débil puede ser complejo, ya que implica conocer la importancia de los diferentes elementos de una página, pero es muy útil para optimizar el rendimiento del caché.
Encabezados condicionales
Varios encabezados HTTP, llamados encabezados condicionales, conducen a peticiones condicionales. Estos son:
If-Match
-
Tiene éxito si la
ETag
del recurso remoto es igual a una que se encuentra en este encabezado. Por defecto, a menos que el etag tenga el prefijo'W/'
, realiza una validación fuerte. If-None-Match
-
Tiene éxito si la
ETag
del recurso remoto es diferente a cada una de las enumeradas en este encabezado. Por defecto, a menos que el etag tenga el prefijo'W/'
, realiza una validación fuerte. If-Modified-Since
-
Tiene éxito si la fecha
Last-Modified
del recurso remoto es más reciente que la dada en este encabezado. If-Unmodified-Since
-
Tiene éxito si la fecha
Last-Modified
del recurso remoto es más antigua que la dada en este encabezado. If-Range
-
Similar a
If-Match
, oIf-Unmodified-Since
, pero sólo puede tener una etag, o una fecha. Si falla, la petición de rango falla, y en lugar de una respuesta206
Partial Content
, se envía un200
OK
con el recurso completo.
Casos de uso
Actualización de caché
El caso de uso más común para las peticiones condicionales es la actualización de un caché. Con un caché vacío, o sin un caché, el recurso solicitado se devuelve con un estado 200
OK
.
Junto con el recurso, los validadores se envían en los encabezados. En este ejemplo, ambos Last-Modified
y ETag
son enviados, pero igualmente podría haber sido solo uno de ellos. Estos validadores se almacenan en caché con el recurso (como todos los encabezados) y se utilizarán para elaborar peticiones condicionales, una vez que el caché se vuelva obsoleto.
Mientras la memoria caché no esté obsoleta, no se emitirá ninguna petición. Pero una vez se haya vuelto obsoleta, esto se controla principalmente por el encabezado Cache-Control
, el cliente no usa el valor en caché directamente, pero emite una petición condicional. El valor del validador se utiliza como parámetro de los encabezados If-Modified-Since
y If-Match
.
Si el recurso no ha cambiado, el servidor envía una respuesta 304
Not Modified
. Esto hace que la caché se actualice nuevamente, y el cliente usa el recurso almacenado en caché. Aunque hay una respuesta/petición de ida y vuelta que consume algunos recursos, esto es más eficiente que transmitir de nuevo todo el recurso a través del cable.
Si el recurso ha cambiado, el servidor simplemente envía una respuesta 200
OK
, con la nueva versión del recurso, como si la petición no fuera condicional y el cliente usara este nuevo recurso (y lo almacena en caché).
Además de la configuración de los validadores en el lado del servidor, este mecanismo es transparente: todos los navegadores administran una memoria caché y envían dichas peticiones condicionales sin que los desarrolladores web realicen ningún trabajo especial.
Integridad de una descarga parcial
La descarga parcial de archivos es una funcionalidad de HTTP que permite reanudar operaciones previas, ahorrando tiempo y ancho de banda, manteniendo la información ya obtenida:
Un servidor que admite descargas parciales transmite esto enviando el encabezado Accept-Ranges
. Una vez que esto sucede, el cliente puede reanudar una descarga enviando un encabezado Ranges
con los rangos ausentes:
El principio es simple, pero hay un problema potencial: si el recurso descargado se modificó entre ambas descargas, los rangos obtenidos corresponderán a dos versiones diferentes del recurso y el documento final estará corrupto.
Para evitar esto, se utilizan peticiones condicionales. Para los rangos, hay dos formas de hacer esto. El más flexible hace uso de If-Modified-Since
y If-Match
y el servidor devuelve un error si la precondición falla, entonces el cliente reinicia la descarga desde el principio:
Incluso si este método funciona, agrega un intercambio adicional de respuesta / petición cuando el documento ha sido cambiado. Esto altera el rendimiento, y HTTP tiene un encabezado específico para evitar este escenario: If-Range
:
Esta solución es más eficiente, pero ligeramente menos flexible, ya que solo se puede usar una etag en la condición. Rara vez se necesita flexibilidad adicional.
Evitar el problema de actualización perdida con bloqueo optimista
Una operación común en aplicaciones web es actualizar un documento remoto. Esto es muy común en cualquier sistema de archivos o aplicaciones de control de origen, pero cualquier aplicación que permita almacenar recursos remotos necesita tal mecanismo. Los sitios web comunes, como los wikis y otros CMS, tienen tal necesidad.
Con el método PUT
eres capaz de implementarlo. El cliente primero lee los archivos originales, los modifica y finalmente los envía al servidor:
Desafortunadamente, las cosas se vuelven un poco inexactas cuando tenemos en cuenta la concurrencia. Mientras un cliente modifica localmente su nueva copia del recurso, un segundo cliente puede obtener el mismo recurso y hacer lo mismo con su copia. Lo que sucede a continuación es muy desafortunado: cuando se devuelven al servidor, las modificaciones del primer cliente son descartadas por la inserción del siguiente cliente, ya que este segundo cliente desconoce los cambios del primer cliente en el recurso. La decisión sobre quién gana, no se comunica a la otra parte. De qué cliente se deberán mantener los cambios, variará con la velocidad a la que se realicen, esto depende del rendimiento de los clientes, del servidor e incluso de la edición humana del documento en el cliente. El ganador cambiará de una vez a la siguiente. Esta es una condición de carrera y conduce a comportamientos problemáticos, que son difíciles de detectar y depurar:
No hay manera de lidiar con este problema sin molestar a uno de los dos clientes. Sin embargo, se deben evitar las actualizaciones perdidas y las condiciones de la carrera. Queremos resultados predecibles y esperamos que se notifique a los clientes cuando se rechacen sus cambios.
Las peticiones condicionales permiten implementar el algoritmo de bloqueo optimista (utilizado por la mayoría de las wikis o sistemas de control de fuente). El concepto es permitir que todos los clientes obtengan copias del recurso, luego permitirles modificarlo localmente, controlando la concurrencia al permitir que el primer cliente envíe una actualización. Todas las actualizaciones posteriores, basadas en la versión ahora obsoleta del recurso, se rechazan:
Esto se implementa utilizando el encabezado If-Match
o If-Unmodified-Since
. Si la etag no coincide con el archivo original, o si el archivo ha sido modificado desde que se obtuvo, el cambio simplemente se rechaza con un error 412
Precondition Failed
. Depende entonces del cliente lidiar con el error: ya sea notificando al usuario que vuelva a comenzar (esta vez en la versión más reciente) o mostrándole al usuario una diferencia entre ambas versiones, Ayudándoles a decidir qué cambios desean mantener.
Tratar con la primera subida de un recurso
La primera subida de un recurso es un caso similar al anterior. Como cualquier actualización de un recurso, está sujeta a una condición de carrera si dos clientes intentan realizarla en tiempos similares. Para evitar esto, se pueden utilizar peticiones condicionales: añadiendo el encabezado If-None-Match
con el valor especial '*'
, representando cualquier etag. La petición sólo tendrá éxito si el recurso no existía antes:
If-None-Match
solo funcionará con servidores compatibles con HTTP/1.1 (y posteriores). Si no está seguro de que el servidor sea compatible, primero debe emitir una petición HEAD
al recurso para comprobarlo.
Conclusión
Las peticiones condicionales son una característica clave de HTTP y permiten la creación de aplicaciones eficientes y complejas. Para almacenar en caché o reanudar las descargas, el único trabajo requerido para los webmasters es configurar el servidor correctamente, establecer etags correctas en algunos entornos puede ser complicado. Una vez logrado, el navegador atenderá las peticiones condicionales esperadas.
Para los mecanismos de bloqueo, ocurre lo contrario: los desarrolladores web deben emitir una petición con los encabezados adecuados, mientras que los webmasters pueden confiar en la aplicación para realizar las comprobaciones correspondientes.
En ambos casos está claro, las peticiones condicionales son una característica fundamental de la Web.