También estábamos experimentando este error pero estábamos usando una biblioteca de administración de activos (Cassette). Después de una extensa investigación de este problema, descubrimos que la causa raíz de este problema es con una combinación de ASP.NET, IIS y Cassette. No estoy seguro de si este es su problema (usando la Headers
API en lugar de la Cache
API), pero el patrón parece ser el mismo.
Bug # 1
Cassette establece el Vary: Accept-Encoding
encabezado como parte de su respuesta a un paquete ya que puede codificar el contenido con gzip / deflate:
Sin embargo, el caché de salida de ASP.NET siempre devolverá la respuesta que se almacenó en caché primero. Por ejemplo, si la primera solicitud tiene Accept-Encoding: gzip
y Cassette devuelve contenido comprimido, la caché de salida de ASP.NET almacenará en caché la URL como Content-Encoding: gzip
. La siguiente solicitud a la misma URL pero con una codificación aceptable diferente (por ejemplo Accept-Encoding: deflate
) devolverá la respuesta almacenada en caché Content-Encoding: gzip
.
Este error se debe a que Cassette usa la HttpResponseBase.Cache
API para establecer la configuración de caché de salida (por ejemplo Cache-Control: public
) pero usa la HttpResponseBase.Headers
API para establecer el Vary: Accept-Encoding
encabezado. El problema es que el ASP.NET OutputCacheModule
es no consciente de las cabeceras de respuesta; solo funciona a través de la Cache
API. Es decir, espera que el desarrollador use una API invisiblemente acoplada en lugar de solo HTTP estándar.
Bug # 2
Cuando se usa IIS 7.5 (Windows Server 2008 R2), el error n. ° 1 puede causar un problema separado con el kernel IIS y las memorias caché del usuario. Por ejemplo, una vez que un paquete se almacena en caché con éxito Content-Encoding: gzip
, es posible verlo en el caché del kernel IIS con netsh http show cachestate
. Muestra una respuesta con código de estado 200 y codificación de contenido de "gzip". Si la siguiente solicitud tiene una codificación aceptable diferente (p Accept-Encoding: deflate
. Ej.
) Y un If-None-Match
encabezado que coincide con el hash del paquete, la solicitud en el kernel de IIS y las cachés de modo de usuario se considerarán como incorrectas . Por lo tanto, haciendo que Cassette maneje la solicitud, que devuelve un 304:
Sin embargo, una vez que el kernel y los modos de usuario de IIS procesen la respuesta, verán que la respuesta para la URL ha cambiado y la caché debe actualizarse. Si se netsh http show cachestate
vuelve a verificar el caché del kernel IIS , la respuesta 200 en caché se reemplaza por una respuesta 304. Todas las solicitudes posteriores al paquete, independientemente de Accept-Encoding
y If-None-Match
devolverán una respuesta 304. Vimos los efectos devastadores de este error donde todos los usuarios recibieron un 304 para nuestro script central debido a una solicitud aleatoria que tuvo un inesperado Accept-Encoding
y If-None-Match
.
El problema parece ser que el caché de IIS y las memorias caché de modo de usuario no pueden variar según el Accept-Encoding
encabezado. Como prueba de esto, al usar la Cache
API con la solución a continuación, las memorias caché del núcleo IIS y del modo de usuario parecen omitirse siempre (solo se usa la memoria caché de salida ASP.NET). Esto se puede confirmar comprobando que netsh http show cachestate
está vacío con la solución a continuación. ASP.NET se comunica con el trabajador de IIS directamente para habilitar o deshabilitar selectivamente el kernel de IIS y las cachés en modo de usuario por solicitud.
No pudimos reproducir este error en versiones más nuevas de IIS (por ejemplo, IIS Express 10). Sin embargo, el error # 1 todavía era reproducible.
Nuestra solución original para este error fue deshabilitar el almacenamiento en caché del modo IIS del kernel / usuario solo para solicitudes de Cassette como se mencionó anteriormente. Al hacerlo, descubrimos el error n. ° 1 al implementar una capa adicional de almacenamiento en caché frente a nuestros servidores web. La razón por la que funcionó el hack de cadena de consulta es porque OutputCacheModule
registrará un error de caché si la Cache
API no se ha utilizado para variar en función de QueryString
y si la solicitud tiene unQueryString
.
Solución alterna
De todos modos, hemos planeado alejarnos de Cassette, por lo que en lugar de mantener nuestra propia bifurcación de Cassette (o tratar de fusionar un PR), optamos por usar un módulo HTTP para solucionar este problema.
public class FixCassetteContentEncodingOutputCacheBugModule : IHttpModule
{
public void Init(HttpApplication context)
{
context.PostRequestHandlerExecute += Context_PostRequestHandlerExecute;
}
private void Context_PostRequestHandlerExecute(object sender, EventArgs e)
{
var httpContext = HttpContext.Current;
if (httpContext == null)
{
return;
}
var request = httpContext.Request;
var response = httpContext.Response;
if (request.HttpMethod != "GET")
{
return;
}
var path = request.Path;
if (!path.StartsWith("/cassette.axd", StringComparison.InvariantCultureIgnoreCase))
{
return;
}
if (response.Headers["Vary"] == "Accept-Encoding")
{
httpContext.Response.Cache.VaryByHeaders.SetHeaders(new[] { "Accept-Encoding" });
}
}
public void Dispose()
{
}
}
Espero que esto ayude a alguien 😄!