Ми також відчували цю помилку, але ми використовували бібліотеку управління активами (касету). Після широкого дослідження цього питання ми з’ясували, що першопричиною цієї проблеми є поєднання ASP.NET, IIS та Cassette. Я не впевнений, чи це ваша проблема (використовуючи HeadersAPI, а не CacheAPI), але модель, здається, однакова.
Помилка №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.CacheAPI для встановлення параметрів кеш-виводу (наприклад Cache-Control: public), але використовує HttpResponseBase.HeadersAPI для встановлення Vary: Accept-Encodingзаголовка. Проблема полягає в тому, що ASP.NET OutputCacheModuleє НЕ знають заголовки відповіді; він працює лише через CacheAPI. Тобто, він розраховує, що розробник використовуватиме невидимо щільно пов'язаний 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заголовка. Як підтвердження цього, використовуючи CacheAPI з рішенням нижче, схоже, кеші ядра IIS та режиму користувача завжди пропускаються (використовується лише вихідний кеш ASP.NET). Це можна підтвердити, перевіривши, що netsh http show cachestateпорожнє, із вирішенням нижче. ASP.NET спілкується з працівником IIS безпосередньо, щоб вибірково ввімкнути або вимкнути кеш ядра IIS та кешування режиму користувача на запит.
Ми не змогли відтворити цю помилку на новіших версіях IIS (наприклад, IIS Express 10). Однак, помилка №1 все-таки відтворювалася.
Нашим оригінальним виправленням цієї помилки було відключення кешування ядра / користувачем режиму IIS лише для запитів касети, як і інші згадані. Тим самим ми виявили помилку №1 під час розгортання додаткового шару кешування перед нашими веб-серверами. Причина того, що хак рядка запиту спрацював, полягає в тому, що OutputCacheModuleзапис записує пропуск кешу, якщо CacheAPI не використовувався для зміни залежно від 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()
{
}
}
Я сподіваюся, що це комусь допоможе 😄!