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 Headers
interfejsu API zamiast Cache
interfejsu API), ale wzorzec wydaje się być taki sam.
Błąd nr 1
Cassette ustawia Vary: Accept-Encoding
nagłó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: gzip
a 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.Cache
interfejsu API do ustawienia wyjściowej pamięci podręcznej (np. Cache-Control: public
), Ale użycie HttpResponseBase.Headers
interfejsu API do ustawienia Vary: Accept-Encoding
nagłówka. Problemem jest to, że ASP.NET OutputCacheModule
jest nie świadomy nagłówków odpowiedzi; działa tylko poprzez Cache
API. 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: gzip
moż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-Match
nagłó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 cachestate
ponownie zaznaczona , odpowiedź w pamięci podręcznej 200 zostanie zastąpiona odpowiedzią 304. Wszystkie kolejne żądania do pakietu, niezależnie od tego Accept-Encoding
i If-None-Match
zwracają 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-Encoding
i 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-Encoding
nagłówka. Jako dowód na to, używając Cache
interfejsu 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 cachestate
jest 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 OutputCacheModule
nagra miss cache jeśli Cache
API 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 😄!