Ten błąd również występował, ale korzystaliśmy z biblioteki zarządzania zasobami (kasety). Po dogłębnym zbadaniu tego problemu stwierdziliśmy, że główną przyczyną tego problemu jest kombinacja ASP.NET, IIS i kasety. Nie jestem pewien, czy to jest twój problem (używanie Headersinterfejsu API zamiast Cacheinterfejsu API), ale wzorzec wydaje się być taki sam.
Błąd nr 1
Cassette ustawia Vary: Accept-Encodingnagłówek jako część swojej odpowiedzi na pakiet, ponieważ może kodować zawartość za pomocą gzip / deflate:
Jednak wyjściowa pamięć podręczna ASP.NET zawsze zwróci odpowiedź, która była najpierw buforowana. Na przykład, jeśli pierwsze żądanie ma, Accept-Encoding: gzipa kaseta zwraca zawartość spakowaną gzip, wyjściowa pamięć podręczna ASP.NET buforuje adres URL jako Content-Encoding: gzip. Następne żądanie do tego samego adresu URL, ale z innym dopuszczalnym kodowaniem (np. Accept-Encoding: deflate), Zwróci buforowaną odpowiedź za pomocą Content-Encoding: gzip.
Ten błąd jest spowodowany przez ustawienie kasety przy użyciu HttpResponseBase.Cacheinterfejsu API do ustawienia wyjściowej pamięci podręcznej (np. Cache-Control: public), Ale użycie HttpResponseBase.Headersinterfejsu API do ustawienia Vary: Accept-Encodingnagłówka. Problemem jest to, że ASP.NET OutputCacheModulejest nie świadomy nagłówków odpowiedzi; działa tylko poprzez CacheAPI. Oznacza to, że oczekuje od programisty użycia niewidocznie ściśle powiązanego interfejsu API, a nie tylko standardowego protokołu HTTP.
Błąd nr 2
Podczas korzystania z IIS 7.5 (Windows Server 2008 R2) błąd nr 1 może powodować osobny problem z jądrem IIS i pamięciami podręcznymi użytkowników. Na przykład po udanym buforowaniu pakietu Content-Encoding: gzipmożna go zobaczyć w pamięci podręcznej jądra IIS za pomocą netsh http show cachestate. Pokazuje odpowiedź z kodem stanu 200 i kodowaniem zawartości „gzip”. Jeśli kolejna prośba ma inną dopuszczalną kodowania (np
Accept-Encoding: deflate) i jest If-None-Matchnagłówek, który pasuje do mieszania wiązki, tym prośba do jądra i trybu użytkownika skrytek IIS będą uważane za chybienie . W ten sposób powodując, że żądanie jest obsługiwane przez kasetę, która zwraca 304:
Jednak gdy jądro i tryby użytkownika IIS przetworzą odpowiedź, zobaczą, że odpowiedź na adres URL uległa zmianie i pamięć podręczna powinna zostać zaktualizowana. Jeśli pamięć podręczna jądra IIS zostanie netsh http show cachestateponownie zaznaczona , odpowiedź w pamięci podręcznej 200 zostanie zastąpiona odpowiedzią 304. Wszystkie kolejne żądania do pakietu, niezależnie od tego Accept-Encodingi If-None-Matchzwracają odpowiedź 304. Widzieliśmy druzgocące skutki tego błędu, w którym wszyscy użytkownicy otrzymali 304 dla naszego głównego skryptu z powodu losowego żądania, które miało nieoczekiwane Accept-Encodingi If-None-Match.
Problem polega na tym, że jądra usług IIS i pamięci podręczne trybu użytkownika nie mogą się różnić w zależności od Accept-Encodingnagłówka. Jako dowód na to, używając Cacheinterfejsu API z poniższym obejściem, wydaje się, że pamięci podręczne jądra IIS i trybu użytkownika są zawsze pomijane (używana jest tylko wyjściowa pamięć podręczna ASP.NET). Można to potwierdzić, sprawdzając, czy pole netsh http show cachestatejest puste, korzystając z poniższego obejścia. Program ASP.NET komunikuje się bezpośrednio z procesorem roboczym IIS w celu selektywnego włączania lub wyłączania pamięci podręcznej jądra IIS i trybu użytkownika na żądanie.
Nie byliśmy w stanie odtworzyć tego błędu w nowszych wersjach IIS (np. IIS Express 10). Jednak błąd nr 1 był nadal powtarzalny.
Naszym oryginalnym rozwiązaniem tego błędu było wyłączenie buforowania trybu jądra / trybu użytkownika IIS tylko dla żądań kasety, jak wspomniano inni. W ten sposób odkryliśmy błąd nr 1 podczas wdrażania dodatkowej warstwy buforowania przed naszymi serwerami WWW. Powodem, że siekać ciąg kwerendy pracował dlatego, że OutputCacheModulenagra miss cache jeśli CacheAPI nie został wykorzystany do zależą od QueryString a jeżeli wniosek maQueryString .
Obejście
W każdym razie planowaliśmy odejść od Cassette, więc zamiast utrzymywać własne rozwidlenie kasety (lub próbować scalić PR), zdecydowaliśmy się użyć modułu HTTP do obejścia tego problemu.
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()
{
}
}
Mam nadzieję, że to komuś pomoże 😄!