Ми також відчували цю помилку, але ми використовували бібліотеку управління активами (касету). Після широкого дослідження цього питання ми з’ясували, що першопричиною цієї проблеми є поєднання ASP.NET, IIS та Cassette. Я не впевнений, чи це ваша проблема (використовуючи Headers
API, а не Cache
API), але модель, здається, однакова.
Помилка №1
Касета встановлює Vary: Accept-Encoding
заголовок як частину своєї відповіді на пакет, оскільки він може кодувати вміст за допомогою gzip / deflate:
Однак вихідний кеш ASP.NET завжди поверне відповідь, кешований першим. Наприклад, якщо перший запит має Accept-Encoding: gzip
і касета повертає gzipped вміст, кеш-код виводу ASP.NET буде кешувати URL-адресу як Content-Encoding: gzip
. Наступний запит на ту саму URL-адресу, але з іншим прийнятним кодуванням (наприклад Accept-Encoding: deflate
) поверне кешований відповідь з Content-Encoding: gzip
.
Ця помилка викликана Касетою, що використовує HttpResponseBase.Cache
API для встановлення параметрів кеш-виводу (наприклад Cache-Control: public
), але використовує HttpResponseBase.Headers
API для встановлення Vary: Accept-Encoding
заголовка. Проблема полягає в тому, що ASP.NET OutputCacheModule
є НЕ знають заголовки відповіді; він працює лише через Cache
API. Тобто, він розраховує, що розробник використовуватиме невидимо щільно пов'язаний API, а не просто стандартний HTTP.
Помилка №2
Під час використання IIS 7.5 (Windows Server 2008 R2) помилка №1 може спричинити окрему проблему з ядрами IIS та кешами користувачів. Наприклад, щойно пакет успішно кешується Content-Encoding: gzip
, його можна побачити в кеші ядра IIS за допомогою netsh http show cachestate
. Він показує відповідь з 200 кодом статусу та кодуванням вмісту "gzip". Якщо наступний запит має іншу прийнятну систему кодування (наприклад
Accept-Encoding: deflate
) і в If-None-Match
заголовок, відповідний хеш згортка, в запиті на ядрах і призначений для користувача режим кешей IIS буде вважатися промахом . Таким чином, викликаючи обробку запиту Касетою, яка повертає 304:
Однак як тільки ядра та користувальницькі режими IIS обробляють відповідь, вони побачать, що відповідь для URL-адреси змінилася і кеш-пам'ять має бути оновлена. Якщо кеш ядра IIS netsh http show cachestate
ще раз перевіряється , кешована відповідь 200 замінюється на відповідь 304. Усі наступні запити до пакету, незалежно від Accept-Encoding
і If-None-Match
повернуть відповідь 304. Ми побачили руйнівні наслідки цієї помилки, коли всім користувачам було подано 304 для нашого основного сценарію через випадковий запит, який стався несподіваним Accept-Encoding
і If-None-Match
.
Проблема, здається, полягає в тому, що кеші ядра IIS та режиму користувача не можуть змінюватися залежно від Accept-Encoding
заголовка. Як підтвердження цього, використовуючи Cache
API з рішенням нижче, схоже, кеші ядра IIS та режиму користувача завжди пропускаються (використовується лише вихідний кеш ASP.NET). Це можна підтвердити, перевіривши, що netsh http show cachestate
порожнє, із вирішенням нижче. ASP.NET спілкується з працівником IIS безпосередньо, щоб вибірково ввімкнути або вимкнути кеш ядра IIS та кешування режиму користувача на запит.
Ми не змогли відтворити цю помилку на новіших версіях IIS (наприклад, IIS Express 10). Однак, помилка №1 все-таки відтворювалася.
Нашим оригінальним виправленням цієї помилки було відключення кешування ядра / користувачем режиму IIS лише для запитів касети, як і інші згадані. Тим самим ми виявили помилку №1 під час розгортання додаткового шару кешування перед нашими веб-серверами. Причина того, що хак рядка запиту спрацював, полягає в тому, що OutputCacheModule
запис записує пропуск кешу, якщо Cache
API не використовувався для зміни залежно від QueryString
та, якщо запит маєQueryString
.
Обхід
Ми планували відійти від касети в будь-якому випадку, тому замість того, щоб підтримувати свою власну вилку Касети (або намагатися об'єднати PR), ми вирішили використовувати модуль HTTP для вирішення цієї проблеми.
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()
{
}
}
Я сподіваюся, що це комусь допоможе 😄!