Problema de Chrome que no devuelve estado 304 en situaciones Not-Modified
Análisis de la causa y solución del problema donde Chrome no devuelve 304 Not Modified al usar Cache-Control y ETag para caché.
Situación del problema
Durante el desarrollo de un proyecto paralelo que proporciona listas de imágenes específicas a los usuarios, el tamaño de las listas de imágenes servidas era grande y los cambios (
Insert,Update,Delete) eran poco frecuentes con muchas operaciones de lectura, lo que requería caché.
Por lo tanto, después de configurar la caché local, implementé lógica para recibirHttpStatus Not-Modifieda través de validación conEtagcuando los datos en caché expiran usando valores deCache-ControlyEtag.
Este artículo trata sobre la situación en el navegador Chrome donde, a pesar de que el valorEtagera el mismo durante la revalidación debido a la expiración de caché, devolvía200en lugar de304.
Entorno y stack de la situación
- Versión del navegador Chrome - versión 96 (diciembre 2021)
spring boot 2.7.1,jdk11curl- Solicitud directa desde terminal en lugar del entorno del navegador para verificar la situaciónwireshark- Captura de paquetes intercambiados con el servidor para verificar valores de respuesta y headers de solicitud
Causa del problema
1269602 - chromium - Un proyecto de código abierto para ayudar a avanzar la web. - Monorail
Este problema fue un bug del navegador Chrome que existía hasta la versión 96, confirmado en los issues de bugs de Chromium.
El laptop usado fue comprado alrededor de marzo de 2022 y tenía Chrome instalado (descuidado), con la opción de actualización automática de Chrome deshabilitada y estaba en la versión 96 que era la última versión en ese momento, causando este problema.
Hay comentarios diciendo que ocurrió hasta la versión 105.. según otros artículos parece que el problema aún existe actualmente..
¿Qué pasa en Chrome actual después de actualizar?
Después de que ocurrió el problema anterior y actualizar Chrome, confirmé que funciona normalmente en la última versión.
Recreación de la situación
Código API del servidor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* Al solicitar a esta api, recibirá una lista de imágenes.
* Para recreación de pruebas, se estableció un valor de ejemplo en etag y no-cache en el header de respuesta.
* En implementación real, usar hash de recurso o versión y max-age=60, must-revalidate
* Después de la primera solicitud, poner el valor etag recibido en if-none-match para comparar y devolver 304 si son iguales
*
* @param pageNo
* @param pageSize
* @param blockSize
* @return
*/
@GetMapping("/backoffice/templates/api")
public ResponseEntity<List<ImageTemplateDto>> getImageTemplatesRest(
@RequestHeader(value = "If-None-Match", required = false) String requestETag,
@RequestParam(required = false, defaultValue = "1") int pageNo,
@RequestParam(required = false, defaultValue = "7") int pageSize,
@RequestParam(required = false, defaultValue = "3") int blockSize) {
try {
List<ImageTemplateDto> imageTemplateDtoList = imageTemplateService.getList(pageNo, pageSize, blockSize);
String etag = "example-etag";
return ResponseEntity
.ok()
.eTag(etag)
.header(HttpHeaders.CACHE_CONTROL, "no-cache")
.body(imageTemplateDtoList.stream()
.limit(pageSize)
.collect(Collectors.toList()));
} catch (InterruptedException interruptedException) {
return null;
}
}
- La configuración actual de
Cache-Controlesno-cache, por lo que siempre valida en el servidor. - El valor de
etagestá hardcodeado, así que desde la segunda solicitud en adelante debe devolver304 status. - Además, como es
not-modified, el servidor no envía el valorbodyal cliente (navegador), por lo que el tamaño del body recibido será diferente (más pequeño) que la primera solicitud.
En Chrome (versión 96)
- Verificando el tamaño recibido, significa que el navegador usó recursos cacheados sin recibirlos del servidor
- Claramente if-none-match está en el header de solicitud
Safari
- También desde la segunda solicitud, devuelve 304 correctamente sin recibir el recurso original del servidor
Edge
- Edge también devuelve 304 correctamente desde la segunda solicitud sin recibir el recurso original del servidor
¿Es confiable el navegador?
Causa
Claramente el servidor piensa que dio 304 y el navegador que recibió la respuesta nos la muestra. Entonces ¿por qué pueden ser diferentes?
Resumiendo el proceso cuando hacemos clic en un enlace en el navegador y vemos el sitio:
- Solicitud URL y búsqueda DNS: Cuando el usuario ingresa una URL o hace clic en un enlace, el navegador verifica la caché local y, si es necesario, realiza una búsqueda DNS para convertir el dominio a dirección IP
- Conexión al servidor y configuración de seguridad: El navegador establece conexión TCP con el servidor, y en caso de HTTPS, realiza handshake SSL/TLS
- Solicitud y respuesta HTTP: Después de la conexión, envía solicitud HTTP al servidor, y el servidor la procesa y devuelve respuesta HTTP.
- Procesamiento de recursos: Luego el navegador analiza y procesa los recursos recibidos como HTML, CSS, JavaScript, etc.
- Renderizado: El navegador genera el árbol de renderizado basándose en los recursos procesados, calcula el layout y pinta en pantalla.
Es decir, aunque cada navegador generalmente intenta seguir estándares web (W3C, IETF, etc.), tienen su propia interpretación e implementación, por lo que el renderizado puede diferir entre navegadores en versiones específicas y tecnologías
- Chrome podría estar correcto (aunque es poco probable) o la
spring applicationpodría estar devolviendo200en lugar de304desde el principio y otros navegadores (edge, safari) simplemente lo muestran como 304.
Verificar con curl
Por lo tanto, debemos verificar si Chrome está mal, si Spring está mal, o si otros navegadores están mal, enviando solicitudes directamente al servidor a través de curl y verificando las respuestas
curl es una herramienta independiente separada del navegador, por lo que puede comunicarse con el servidor y recibir respuestas independientemente del comportamiento del navegador
Resultado
- Como se muestra en la imagen de abajo, al agregar
If-None-Matchal header, podemos confirmar que devuelve 304. - Es decir, el recurso recibido del servidor antes del proceso de renderizado del navegador es 304 correctamente, y Spring no es el problema
¿Es curl confiable?
- Desde 1997, realmente muchas personas lo han usado y la comunidad es activa, por lo que tiene alta confiabilidad ya que el código es revisado por muchos ojos como característica de proyectos de código abierto
- El hecho de que el lanzamiento reciente (8.4) fue a principios de octubre de 2023 significa que los desarrolladores participan continuamente en el proyecto
- Por supuesto, todavía hay vulnerabilidades y si preguntas si es completamente confiable, creo que no. Porque hay muchos casos de bugs relacionados con respuestas HTTP como resultado de su larga historia
Casos de bugs relacionados con respuestas HTTP de curl
curl - HTTP Proxy deny use after free - CVE-2022-43552
curl - HTTP headers eat all memory - CVE-2023-38039
curl - HTTP multi-header compression denial of service - CVE-2023-23916
¿Cuál es la forma más confiable?
Para esta situación problemática elegí wireshark para verificar solicitudes/respuestas con el servidor,
1. Siendo un proyecto de código abierto, es fácilmente verificable
2. Como es posible el análisis de paquetes de red, puede capturar y analizar todos los paquetes transmitidos en la red.
Con estas razones, proceso de verificación después de instalar wireshark
- Primero, como el entorno de recreación es local, solo verificar paquetes loopback
- Luego aplico filtro http para verificar solicitudes/respuestas de API. Confirmo que solicitud y respuesta están registradas
- También verifico que la respuesta vino exactamente como 304










