Мы также сталкивались с этой ошибкой, но мы использовали библиотеку управления активами (кассету). После тщательного изучения этой проблемы мы обнаружили, что основной причиной этой проблемы является сочетание ASP.NET, IIS и кассеты. Я не уверен, что это ваша проблема (использование Headers
API, а не Cache
API), но шаблон, кажется, тот же.
Ошибка № 1
Кассета устанавливает Vary: Accept-Encoding
заголовок как часть своего ответа на пакет, поскольку она может кодировать содержимое с помощью gzip / deflate:
Однако кэш вывода ASP.NET всегда будет возвращать ответ, который был кэширован первым. Например, если первый запрос имеет, Accept-Encoding: gzip
а Cassette возвращает сжатый контент, кэш вывода ASP.NET будет кэшировать URL как Content-Encoding: gzip
. Следующий запрос к тому же URL, но с другой приемлемой кодировкой (например Accept-Encoding: deflate
) вернет кешированный ответ с Content-Encoding: gzip
.
Эта ошибка вызвана тем, что Cassette использует 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()
{
}
}
Я надеюсь, что это помогает кому-то 😄!