Dieser Fehler trat ebenfalls auf, wir verwendeten jedoch eine Asset Management-Bibliothek (Kassette). Nach einer umfassenden Untersuchung dieses Problems haben wir festgestellt, dass die Hauptursache für dieses Problem in einer Kombination aus ASP.NET, IIS und Kassette liegt. Ich bin nicht sicher, ob dies Ihr Problem ist (Verwendung der Headers
API anstelle der Cache
API), aber das Muster scheint dasselbe zu sein.
Fehler Nr. 1
Cassette setzt den Vary: Accept-Encoding
Header als Teil seiner Antwort auf ein Bundle, da er den Inhalt mit gzip / deflate codieren kann:
Der ASP.NET-Ausgabecache gibt jedoch immer die Antwort zurück, die zuerst zwischengespeichert wurde. Wenn beispielsweise die erste Anforderung hat Accept-Encoding: gzip
und Kassette komprimierten Inhalt zurückgibt, speichert der ASP.NET-Ausgabecache die URL als Content-Encoding: gzip
. Die nächste Anforderung an dieselbe URL, jedoch mit einer anderen akzeptablen Codierung (z. B. Accept-Encoding: deflate
), gibt die zwischengespeicherte Antwort mit zurück Content-Encoding: gzip
.
Dieser Fehler wird dadurch verursacht, dass Cassette die HttpResponseBase.Cache
API verwendet, um die Einstellungen für den Ausgabecache festzulegen (z. B. Cache-Control: public
), aber die HttpResponseBase.Headers
API verwendet, um den Vary: Accept-Encoding
Header festzulegen. Das Problem ist , dass die ASP.NET OutputCacheModule
ist nicht bewusst , Response - Header; Es funktioniert nur über die Cache
API. Das heißt, es wird erwartet, dass der Entwickler eine unsichtbar eng gekoppelte API verwendet und nicht nur Standard-HTTP.
Fehler # 2
Bei Verwendung von IIS 7.5 (Windows Server 2008 R2) kann Fehler 1 ein separates Problem mit dem IIS-Kernel und den Benutzercaches verursachen. Wenn ein Bundle beispielsweise erfolgreich zwischengespeichert wurde Content-Encoding: gzip
, kann es im IIS-Kernel-Cache mit angezeigt werden netsh http show cachestate
. Es zeigt eine Antwort mit 200 Statuscode und Inhaltscodierung von "gzip". Wenn die nächste Anforderung eine andere akzeptable Codierung (z. B.
Accept-Encoding: deflate
) und einen If-None-Match
Header hat, der mit dem Hash des Bundles übereinstimmt, wird die Anforderung in den Kernel- und Benutzermodus-Caches von IIS als Fehlschlag betrachtet . Dadurch wird die Anforderung von der Kassette verarbeitet, die eine 304 zurückgibt:
Sobald jedoch der Kernel- und der Benutzermodus von IIS die Antwort verarbeiten, sehen sie, dass sich die Antwort für die URL geändert hat und der Cache aktualisiert werden sollte. Wenn der IIS-Kernel-Cache netsh http show cachestate
erneut überprüft wird, wird die zwischengespeicherte 200-Antwort durch eine 304-Antwort ersetzt. Alle nachfolgenden Anforderungen an das Bundle, unabhängig von Accept-Encoding
und If-None-Match
geben eine 304-Antwort zurück. Wir haben die verheerenden Auswirkungen dieses Fehlers gesehen, bei dem allen Benutzern aufgrund einer zufälligen Anfrage mit einem unerwarteten Accept-Encoding
und ein 304 für unser Kernskript zugestellt wurde If-None-Match
.
Das Problem scheint zu sein, dass der Cache des IIS-Kernels und des Benutzermodus je nach Accept-Encoding
Header nicht variieren kann . Als Beweis dafür Cache
scheinen bei Verwendung der API mit der folgenden Problemumgehung die IIS-Kernel- und Benutzermodus-Caches immer übersprungen zu werden (nur der ASP.NET-Ausgabecache wird verwendet). Dies kann bestätigt werden, indem überprüft wird, ob netsh http show cachestate
die folgende Problemumgehung leer ist. ASP.NET kommuniziert direkt mit dem IIS-Worker, um den Cache des IIS-Kernels und des Benutzermodus pro Anforderung selektiv zu aktivieren oder zu deaktivieren.
Wir konnten diesen Fehler auf neueren Versionen von IIS (z. B. IIS Express 10) nicht reproduzieren. Fehler Nr. 1 war jedoch immer noch reproduzierbar.
Unsere ursprüngliche Lösung für diesen Fehler bestand darin, das Zwischenspeichern des IIS-Kernels / Benutzermodus nur für Kassettenanforderungen zu deaktivieren, wie andere erwähnt haben. Auf diese Weise haben wir Fehler Nr. 1 beim Bereitstellen einer zusätzlichen Caching-Ebene vor unseren Webservern entdeckt. Der Grund, warum der Abfragezeichenfolgen-Hack funktioniert hat, ist, dass der OutputCacheModule
einen Cache-Fehler aufzeichnet, wenn die Cache
API nicht verwendet wurde, um basierend auf dem zu variieren, QueryString
und wenn die Anforderung eine hatQueryString
.
Problemumgehung
Wir hatten sowieso vor, uns von der Kassette zu entfernen. Anstatt unsere eigene Kassettengabel beizubehalten (oder zu versuchen, eine PR zusammenzuführen), haben wir uns für die Verwendung eines HTTP-Moduls entschieden, um dieses Problem zu umgehen.
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()
{
}
}
Ich hoffe das hilft jemandem 😄!