Использование кэширования браузера в IIS (проблема с Google pagespeed)

есть несколько вопросов об использовании кэширования браузера, но я не нашел ничего полезного для того, как это сделать в ASP.NET применение. Google Pagespeed говорит, что это самая большая проблема с производительностью. До сих пор я делал это в моем web.config:

<system.webServer>
  <staticContent>
    <!--<clientCache cacheControlMode="UseExpires"
            httpExpires="Fri, 24 Jan 2014 03:14:07 GMT" /> -->
    <clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="7.24:00:00" />
  </staticContent>
</system.webServer>

закомментированный код работает. Я могу установить заголовок expire в определенное время в будущем, но я не смог установить cacheControlMaxAge чтобы установить, сколько дней будет кэшироваться статическое содержимое. Это не работает. Мой вопросы:

как я могу это сделать? Я знаю, что можно установить кэширование только для определенной папки, которая была бы хорошим решением, но она также не работает. Приложение размещается на Windows Server 2012, на IIS8 пул приложений установлен в classic.

после того, как я установил этот код в web config, я получил pagespeed 72 (раньше был 71). 50 файлы не кэшируются. (Теперь 49) мне было интересно, почему, и я только что понял, что один файл был фактически кэширован (svg-файл). К сожалению png и jpg файлов не было. Это мой web.config

<?xml version="1.0" encoding="utf-8"?>

<configuration>
  <configSections>
    <section name="exceptionManagement" type="Microsoft.ApplicationBlocks.ExceptionManagement.ExceptionManagerSectionHandler,Microsoft.ApplicationBlocks.ExceptionManagement" />
    <section name="jsonSerialization"     type="System.Web.Configuration.ScriptingJsonSerializationSection, System.Web.Extensions,   Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E34" requirePermission="false" allowDefinition="Everywhere" />
    <sectionGroup name="elmah">
      <section name="security" requirePermission="false" type="Elmah.SecuritySectionHandler, Elmah"    />
      <section name="errorLog" requirePermission="false" type="Elmah.ErrorLogSectionHandler, Elmah"    />
      <section name="errorMail" requirePermission="false" type="Elmah.ErrorMailSectionHandler, Elmah" />
      <section name="errorFilter" requirePermission="false" type="Elmah.ErrorFilterSectionHandler, Elmah" />
    </sectionGroup>
  </configSections>

  <exceptionManagement mode="off">
    <publisher mode="off" assembly="Exception"  type="blabla.ExceptionHandler.ExceptionDBPublisher"  connString="server=188......;database=blabla;uid=blabla;pwd=blabla; " />
  </exceptionManagement>
  <location path="." inheritInChildApplications="false">
    <system.web>
      <httpHandlers>
        <add verb="GET,HEAD" path="ScriptResource.axd"  type="System.Web.Handlers.ScriptResourceHandler,System.Web.Extensions, Version=1.0.61025.0,  Culture=neutral, PublicKeyToken=31bf3856ad364e34" validate="false" />
        <add verb="GET" path="Image.ashx" type="blabla.WebComponents.ImageHandler, blabla/>"
        <add verb="*" path="*.aspx" type="System.Web.UI.PageHandlerFactory" />
        <add verb="*" path="*.jpg" type="System.Web.StaticFileHandler" />
        <add verb="GET" path="*.js" type="System.Web.StaticFileHandler" />
        <add verb="*" path="*.gif" type="System.Web.StaticFileHandler" />
        <add verb="GET" path="*.css" type="System.Web.StaticFileHandler" />
      </httpHandlers>
      <compilation defaultLanguage="c#" targetFramework="4.5.1" />
      <trace enabled="false" requestLimit="100" pageOutput="true" traceMode="SortByTime" localOnly="true"/>
      <authentication mode="Forms">
        <forms loginUrl="~/user/login.aspx">
          <credentials passwordFormat="Clear">
            <user name="blabla" password="blabla" />
          </credentials>
        </forms>
      </authentication>
      <authorization>
        <allow users="*" />
      </authorization>
      <sessionState mode="InProc" stateConnectionString="tcpip=127.0.0.1:42424" sqlConnectionString="data source=127.0.0.1;Trusted_Connection=yes" cookieless="false" timeout="20" />
      <globalization requestEncoding="utf-8" responseEncoding="utf-8" culture="en-GB" uiCulture="en-GB" />
      <xhtmlConformance mode="Transitional" />
      <pages controlRenderingCompatibilityVersion="4.5" clientIDMode="AutoID">
        <namespaces>

        </namespaces>
        <controls>
          <add assembly="Microsoft.AspNet.Web.Optimization.WebForms" namespace="Microsoft.AspNet.Web.Optimization.WebForms" tagPrefix="webopt" />
        </controls>
      </pages>
      <webServices>
        <protocols>
          <add name="HttpGet" />
          <add name="HttpPost" />
        </protocols>
      </webServices>
    </system.web>
  </location>
  <appSettings>

  </appSettings>
  <connectionStrings>

  </connectionStrings>
  <system.web.extensions>
    <scripting>
      <webServices>
        <jsonSerialization maxJsonLength="200000" />
      </webServices>
    </scripting>
  </system.web.extensions>
  <startup>
    <supportedRuntime version="v2.0.50727" />
    <supportedRuntime version="v1.1.4122" />
    <supportedRuntime version="v1.0.3705" />
  </startup>
  <system.webServer>


    <rewrite>
      <providers>
        <provider name="ReplacingProvider" type="ReplacingProvider, ReplacingProvider, Version=1.0.0.0, Culture=neutral, PublicKeyToken=5ab632b1f332b247">
          <settings>
            <add key="OldChar" value="_" />
            <add key="NewChar" value="-" />
          </settings>
        </provider>
        <provider name="FileMap" type="DbProvider, Microsoft.Web.Iis.Rewrite.Providers, Version=7.1.761.0, Culture=neutral, PublicKeyToken=0525b0627da60a5e">
          <settings>
            <add key="ConnectionString" value="server=;database=blabla;uid=blabla;pwd=blabla;App=blabla"/>
            <add key="StoredProcedure" value="Search.GetRewriteUrl"/>
            <add key="CacheMinutesInterval" value="0"/>
          </settings>
        </provider>
      </providers>
      <rewriteMaps configSource="rewritemaps.config" />
      <rules configSource="rewriterules.config" />
    </rewrite>
    <modules>
      <remove name="ScriptModule" />
      <add name="ScriptModule" preCondition="managedHandler" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3456AD264E35" />
      <add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah" preCondition="managedHandler" />
      <add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" preCondition="managedHandler" />
      <add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah" preCondition="managedHandler" />
    </modules>
    <handlers>
      <add name="Web-JPG" path="*.jpg" verb="GET,HEAD,POST" modules="IsapiModule" scriptProcessor="C:WindowsMicrosoft.NETFramework64v4.0.30319aspnet_isapi.dll" resourceType="Unspecified" preCondition="classicMode,runtimeVersionv4.0,bitness64" />
      <add name="Web-CSS" path="*.css" verb="GET,HEAD,POST" modules="IsapiModule" scriptProcessor="C:WindowsMicrosoft.NETFramework64v4.0.30319aspnet_isapi.dll" resourceType="Unspecified" preCondition="classicMode,runtimeVersionv4.0,bitness64" />
      <add name="Web-GIF" path="*.gif" verb="GET,HEAD,POST" modules="IsapiModule" scriptProcessor="C:WindowsMicrosoft.NETFramework64v4.0.30319aspnet_isapi.dll" resourceType="Unspecified" preCondition="classicMode,runtimeVersionv4.0,bitness64" />
      <add name="Web-JS" path="*.js" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="C:WindowsMicrosoft.NETFramework64v4.0.30319aspnet_isapi.dll" resourceType="Unspecified" preCondition="classicMode,runtimeVersionv4.0,bitness64" />
    </handlers>
    <validation validateIntegratedModeConfiguration="false" />
    <httpErrors errorMode="DetailedLocalOnly" existingResponse="Auto">
      <remove statusCode="404" subStatusCode="-1"/>
      <remove statusCode="500" subStatusCode="-1"/>
      <error statusCode="404" path="error404.htm" responseMode="File"/>
      <error statusCode="500" path="error.htm" responseMode="File"/>
    </httpErrors>
  </system.webServer>
  <system.serviceModel>
    <bindings>
      <basicHttpBinding>
        <binding name="soapBinding_AdriagateService" closeTimeout="00:01:00" openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00" allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard" maxBufferPoolSize="2147483647" maxBufferSize="2147483647" maxReceivedMessageSize="2147483647" textEncoding="utf-8" transferMode="Buffered" useDefaultWebProxy="true" messageEncoding="Text">
          <readerQuotas maxDepth="2147483647" maxStringContentLength="2147483647" maxArrayLength="2147483647" maxBytesPerRead="2147483647" maxNameTableCharCount="2147483647" />
          <security mode="None" />
        </binding>
      </basicHttpBinding>
      <netTcpBinding>
        <binding name="NetTcpBinding_ITravellerService" closeTimeout="00:10:00" openTimeout="00:10:00" sendTimeout="00:10:00" maxReceivedMessageSize="2147483647" maxBufferPoolSize="2147483647">
          <readerQuotas maxDepth="2147483647" maxStringContentLength="2147483647" maxArrayLength="2147483647" maxBytesPerRead="2147483647" maxNameTableCharCount="2147483647" />
          <security mode="None" />
        </binding>
      </netTcpBinding>
    </bindings>
    <client>
      <endpoint address="blabla" bindingConfiguration="soapBinding_blabla" contract="" Address="blabla" name="blabla" />
        <endpoint address="blabla" binding="basicHttpBinding" bindingConfiguration="soapBinding_IImagesService"
          contract="ImagesService.IImagesService" name="soapBinding_IImagesService"/>
        <identity>
          <servicePrincipalName value="blabla"/>
        </identity>
      </endpoint>
    </client>
  </system.serviceModel>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      <dependentAssembly>
        <assemblyIdentity name="WebGrease" publicKeyToken="31bf3856ad364e35" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-1.5.2.14234" newVersion="1.5.2.14234" />
      </dependentAssembly>
      <dependentAssembly>
        <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
        <bindingRedirect oldVersion="0.0.0.0-4.5.0.0" newVersion="4.5.0.0" />
      </dependentAssembly>
    </assemblyBinding>
  </runtime>
  <system.web>
    <httpModules>
      <add name="ErrorLog" type="Elmah.ErrorLogModule, Elmah" />
      <add name="ErrorMail" type="Elmah.ErrorMailModule, Elmah" />
      <add name="ErrorFilter" type="Elmah.ErrorFilterModule, Elmah" />
    </httpModules>
  </system.web>
  <elmah>
    <security allowRemoteAccess="false" />
  </elmah>
  <location path="elmah.axd" inheritInChildApplications="false">
    <system.web>
      <httpHandlers>
        <add verb="POST,GET,HEAD" path="elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah" />
      </httpHandlers>

    </system.web>
    <system.webServer>
      <handlers>
        <add name="ELMAH" verb="POST,GET,HEAD" path="elmah.axd" type="Elmah.ErrorLogPageFactory, Elmah" preCondition="integratedMode" />
      </handlers>
    </system.webServer>
  </location>
</configuration>

EDIT: Если я установил точную дату истечения срока действия, кэширование работает, но не для jpg, gif....только для png

EDIT2: Если я установлю cacheControlCustom="public" как здесь:

<clientCache cacheControlCustom="public" 
cacheControlMode="UseMaxAge" cacheControlMaxAge="7.00:00:00" /> 

кэширование работает, но еще не для JPEG и gifs; он работает только для svgs и pngs.

3 ответов


большинство проблем кэширования браузера можно решить, просмотрев заголовки ответов (можно сделать в Google chrome developer tools).

enter image description here

теперь вашего web.config файл должен установить кэширование вывода на максимальный возраст, как вы видите на рисунке ниже, установил max-age to 86400 что 1 день в секундах.

здесь сети.фрагмент конфигурации для этой установки.

<clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="1.00:00:00" />

это великолепно, заголовок ответа имеет max-age свойство установлено на . Итак, браузер должны кэширование содержимого. Ну, это в основном верно, но некоторые браузеры требуют установки другого флага. В частности,public флаг установлен для заголовка элемента управления кэшем. Это можно легко добавить с помощью на web.config. Вот пример.

<clientCache cacheControlCustom="public" cacheControlMode="UseMaxAge" cacheControlMaxAge="1.00:00:00" />

теперь, когда мы повторим страницу и проверим заголовки.

enter image description here

теперь, как вы можете видеть на изображении выше, теперь у нас есть значение public, max-age=86400. Таким образом, наш браузер имеет все необходимое для кэширования ресурсов. Теперь изучение заголовков и сетевой вкладки google chrome поможет нам.

вот первый запрос к файлу.. Примечание файл не кэшируется... enter image description here

теперь давайте вернемся к этой странице (Примечание: не обновляйте страницу, мы поговорим об этом в второй.) Вы увидите ответ в now returning from cache (как обведено).

enter image description here

теперь что произойдет, если я обновлю страницу, используя либо Ф5 или с помощью функции обновления браузера. Ждать.. где (from cache) перейти. enter image description here

Ну в Google Chrome (не уверен в других браузерах) с помощью кнопки обновления будет повторно загрузить статические ресурсы независимо от заголовка кэша (вставить здесь разъяснение пожалуйста!--62-->). Это означает, что ресурсы были повторно извлечены и отправлен заголовок max age.

теперь после всего объяснения выше, обязательно проверьте как вы отслеживаете заголовки кэша.

обновление

на основе ваших комментариев Вы заявили, что у вас есть общий обработчик (IHttpHandler) С типом контента image/jpg. Теперь вы можете ожидать, что поведение по умолчанию будет кэшировать этот обработчик. Однако IIS видит расширение .ashx (правильно) как динамический скрипт и не подлежит кэшированию без явной установки заголовков кэша в самом коде.

теперь это где вам нужно быть осторожным, как обычно IHttpHandlers не следует кэшировать, поскольку они обычно доставляют динамическое содержимое. Теперь, если это содержимое вряд ли изменится, вы можете установить заголовки кэша непосредственно в коде. Вот пример установки заголовков кэша в IHttpHandlers С помощью Response контекст.

context.Response.ContentType = "image/jpg";

context.Response.Cache.SetMaxAge(TimeSpan.FromDays(1));
context.Response.Cache.SetCacheability(HttpCacheability.Public);
context.Response.Cache.SetSlidingExpiration(true);

context.Response.TransmitFile(context.Server.MapPath("~/out.jpg"));

теперь, глядя на код, мы устанавливаем несколько свойств на Cache собственность. Чтобы получить желаемый ответ, я установил свойства.

  • context.Response.Cache.SetMaxAge(TimeSpan.FromDays(1)); сообщает кэш out put для установки max-age= часть на 1 день (86400 секунд).
  • context.Response.Cache.SetCacheability(HttpCacheability.Public); сообщает кэш out put для установки до public. Это очень важно, поскольку он говорит браузеру кэшировать возражать.
  • context.Response.Cache.SetSlidingExpiration(true); сообщает выходному кэшу, чтобы убедиться, что он устанавливает max-age= часть правильно. Без установки скользящего срока действия кэширование IIS out put игнорирует заголовок max age. Сложив все это вместе, я получаю такой результат.

output cache from ashx file

как я уже говорил выше, вы не хотите кэшировать .ashx файлы, так как они обычно доставляют динамическое содержание. Однако, если это динамическое содержимое вряд ли изменение в данный период вы можете использовать методы выше, чтобы доставить Ваш .

теперь в сочетании с процессом, перечисленным выше, вы также можете установить ETag (см. wiki) компонент заголовков кэша, чтобы браузер мог проверить содержимое, доставляемое пользовательской строкой. В вики говорится:

ETag-это непрозрачный идентификатор, назначенный веб-сервером определенному версия ресурса, найденного по URL-адресу. Если содержимое ресурса при этом URL всегда меняется, новый и другой ETag назначена.

таким образом, это действительно своего рода уникальная идентификация для браузера, чтобы идентифицировать содержимое, доставляемое в ответе. Предоставляя этот заголовок, браузер при следующей перезагрузке отправит С ETag из последнего ответа. Мы можем изменить наш обработчик, чтобы обнаружить If-None-Match заголовок и сравните его с нашим собственным сгенерированным Etag. Теперь там нет точной науки для генерации ETags но хорошим эмпирическим правилом является предоставление идентификатора, который, скорее всего, определит только одну сущность. В этом случае мне нравится использовать две строки, соединенные вместе, например.

System.IO.FileInfo file = new System.IO.FileInfo(context.Server.MapPath("~/saveNew.png"));
string eTag = file.Name.GetHashCode().ToString() + file.LastWriteTimeUtc.Ticks.GetHashCode().ToString();

в приведенном выше фрагменте мы загружаем файл из нашей файловой системы (вы можете получить это из любого места). Затем я использую GetHashCode() метод (для всех объектов) для получения целочисленного хэш-кода объекта. В примере я объединяю хэш имени файла, затем последняя дата записи. Причина последней даты записи заключается в том, что в случае изменения файла хэш-код также изменяется, что делает отпечатки пальцев разные.

это создаст хэш-код, подобный 306894467-210133036.

Итак, как мы используем это в нашем обработчике. Ниже приведена недавно измененная версия обработчика.

System.IO.FileInfo file = new System.IO.FileInfo(context.Server.MapPath("~/out.png"));
string eTag = file.Name.GetHashCode().ToString() + file.LastWriteTimeUtc.Ticks.GetHashCode().ToString();
var browserETag = context.Request.Headers["If-None-Match"];

context.Response.ClearHeaders();
if(browserETag == eTag)
{
    context.Response.Status = "304 Not Modified";
    context.Response.End();
    return;
}
context.Response.ContentType = "image/jpg";
context.Response.Cache.SetMaxAge(TimeSpan.FromDays(1));
context.Response.Cache.SetCacheability(HttpCacheability.Public);
context.Response.Cache.SetSlidingExpiration(true);
context.Response.Cache.SetETag(eTag);
context.Response.TransmitFile(file.FullName);

как вы можете видеть, я изменил довольно много обработчика, однако вы заметите, что мы генерируем Etag хэш, проверьте входящий . Если хэш etag и заголовок равны, мы сообщаем браузеру, что содержимое не изменилось, возвращая код состояния 304 Not Modified.

Далее был тот же обработчик, за исключением добавления ETag заголовок по телефонам:

context.Response.Cache.SetETag(eTag);

когда мы запускаем это в браузере, мы получаем.

Cache-Control with ETag

вы увидите из изображения (как я изменил имя файла), что теперь у нас есть все компоненты нашей системы кэша на месте. The ETag поставляется в качестве заголовка, и браузер посылает запрос заголовок If-None-Match таким образом, наш обработчик может соответствующим образом реагировать на изменение файла кэша.


использовать это. Это работает для меня.

<staticContent>
<clientCache cacheControlMode="UseExpires" httpExpires="Tue,19 Jan 2038 03:14:07 GMT"/>
</staticContent>

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <system.webServer>
    <staticContent>
      <clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="10.00:00:00" />
    </staticContent>
  </system.webServer>
</configuration>

используя выше, статические файлы содержимого будут кэшироваться в течение 10 дней в браузере. Подробная информация о <clientCache> элемент можно найти здесь.

вы также можете использовать <location> элемент для определения параметров кэша для конкретного файла:

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <location path="path/to/file">
    <system.webServer>
      <staticContent>
        <clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="10.00:00:00" />
      </staticContent>
    </system.webServer>
  </location>
</configuration>