IIS 7 возвращает 304 вместо 200
У меня странная проблема с IIS 7.
Иногда кажется, что возвращается 304 вместо 200.
Вот пример запроса, полученного с помощью Fiddler:
(Обратите внимание, что запрошенный файл еще не находится в кэше моего браузера.)
GET https://[mysite]/Content/js/jquery.form.js HTTP/1.1 Accept: */* Referer: https://[mysite]/Welcome/News Accept-Language: sv-SE User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; InfoPath.2; OfficeLiveConnector.1.4; OfficeLivePatch.1.3; .NET4.0C; .NET4.0E) Accept-Encoding: gzip, deflate Host: [mysite] Connection: Keep-Alive Cache-Control: no-cache Cookie: ...
Обратите внимание, что в запросе нет If-Modified-Since или If-None-Match.
Но все же ответ таков:
HTTP/1.1 304 Not Modified Cache-Control: public Expires: Tue, 02 Mar 2010 06:26:08 GMT Last-Modified: Mon, 22 Feb 2010 21:58:44 GMT ETag: "1CAB40A337D4200" Server: Microsoft-IIS/7.5 X-Powered-By: ASP.NET Date: Mon, 01 Mar 2010 17:06:34 GMT
Кто-нибудь знает, что здесь может быть не так?
Я использую IIS 7 на Windows Web Server 2008 R2.
РЕДАКТИРОВАТЬ:
Я нашел обходной путь, включил кэширование, а затем отключил его на уровне расширения.
<configuration>
<system.webServer>
<caching enabled="true" enableKernelCache="true">
<profiles>
<add extension=".png" policy="DisableCache" kernelCachePolicy="DisableCache" />
<add extension=".gif" policy="DisableCache" kernelCachePolicy="DisableCache" />
<add extension=".js" policy="DisableCache" kernelCachePolicy="DisableCache" />
<add extension=".css" policy="DisableCache" kernelCachePolicy="DisableCache" />
</profiles>
</caching>
<staticContent>
<clientCache cacheControlMode="NoControl" />
</staticContent>
</system.webServer>
</configuration>
3 ответа
Согласно разделу 14.9 спецификации HTTP1.1, no-cache
директива для заголовка Cache-Control только навязывается исходным сервером, что означает, что IIS игнорирует заголовок в вашем запросе.
Директивы управления кэшем можно разбить на следующие общие категории:
- Restrictions on what are cacheable; these may only be imposed
сервером происхождения.
Раздел 14.9.1 определяет public
, private
, а также no-cache
в качестве директив, ограничивающих то, что кешируется, что может быть наложено только сервером.
Если вы не хотите, чтобы ваш файл.js кэшировался, вам нужно либо установить no-cache
директива в приложении (т. е. код ASP.NET), или вам нужно изменить Cache-Control
заголовок в запросе на использование no-store
директива вместо no-cache
,
РЕДАКТИРОВАТЬ:
На основании вашего комментария - да, я предположил, что вы не хотите, чтобы файл кэшировался. Таким образом, 304 может появиться в результате того, что файл находится в одном из внутренних кэшей IIS. Посмотрите на это:
Некоторое время у меня была такая же проблема, и все кэширование отключено... Однако в какой-то момент я установил модуль сжатия для IIS7, который по умолчанию включил сжатие статических файлов на моих существующих сайтах. Я отключил все сжатие для затронутых участков, и теперь они, кажется, работают нормально на ощупь.
Мы также сталкивались с этой ошибкой, но мы использовали библиотеку управления активами (кассету). После тщательного изучения этой проблемы мы обнаружили, что основной причиной этой проблемы является сочетание ASP.NET, IIS и кассеты. Я не уверен, что это ваша проблема (используя Headers
API, а не Cache
API), но картина, кажется, такая же.
Ошибка № 1
Кассета устанавливает Vary: Accept-Encoding
заголовок как часть его ответа на пакет, так как он может кодировать содержимое с помощью gzip / deflate:
- BundleRequestHandler: https://github.com/andrewdavey/cassette/blob/a2d9870eb0dc3585ef3dd542287595bc6162a21a/src/Cassette.Aspnet/BundleRequestHandler.cs#L78
- HttpResponseUtil: https://github.com/andrewdavey/cassette/blob/a2d9870eb0dc3585ef3dd542287595bc6162a21a/src/Cassette.Aspnet/HttpResponseUtil.cs#L45
Однако кэш вывода ASP.NET всегда будет возвращать ответ, который был кэширован первым. Например, если первый запрос имеет Accept-Encoding: gzip
и кассета возвращает сжатый контент, выходной кеш 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:
- BundleRequestHandler: https://github.com/andrewdavey/cassette/blob/a2d9870eb0dc3585ef3dd542287595bc6162a21a/src/Cassette.Aspnet/BundleRequestHandler.cs#L44
Однако, как только ядро и пользовательские режимы 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()
{
}
}
Я надеюсь, что это помогает кому-то!