Использование кэширования браузера в 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).
теперь вашего 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" />
теперь, когда мы повторим страницу и проверим заголовки.
теперь, как вы можете видеть на изображении выше, теперь у нас есть значение public, max-age=86400
. Таким образом, наш браузер имеет все необходимое для кэширования ресурсов. Теперь изучение заголовков и сетевой вкладки google chrome поможет нам.
вот первый запрос к файлу.. Примечание файл не кэшируется...
теперь давайте вернемся к этой странице (Примечание: не обновляйте страницу, мы поговорим об этом в второй.) Вы увидите ответ в now returning from cache (как обведено).
теперь что произойдет, если я обновлю страницу, используя либо Ф5 или с помощью функции обновления браузера. Ждать.. где (from cache)
перейти.
Ну в 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. Сложив все это вместе, я получаю такой результат.
как я уже говорил выше, вы не хотите кэшировать .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);
когда мы запускаем это в браузере, мы получаем.
вы увидите из изображения (как я изменил имя файла), что теперь у нас есть все компоненты нашей системы кэша на месте. 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>