利用IIS中的浏览器caching(google pagespeed问题)
有几个关于利用浏览器caching的问题,但是我没有发现在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>
评论代码的作品。 我可以设置过期的标题为将来某个特定的时间,但我无法设置cacheControlMaxAge
来设置从现在静态内容将被caching多less天。 这是行不通的。 我的问题是:
我怎样才能做到这一点? 我知道可以设置caching只为特定的文件夹,这将是一个很好的解决scheme,但它不工作。 应用程序托pipe在Windows Server 2012上,在IIS8上,应用程序池设置为经典。
当我在webconfiguration中设置了这个代码后,我的速度达到了72(之前是71)。 50个文件没有被caching。 (现在49)我想知道为什么,我只是意识到,一个文件实际上caching(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:\Windows\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" resourceType="Unspecified" preCondition="classicMode,runtimeVersionv4.0,bitness64" /> <add name="Web-CSS" path="*.css" verb="GET,HEAD,POST" modules="IsapiModule" scriptProcessor="C:\Windows\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" resourceType="Unspecified" preCondition="classicMode,runtimeVersionv4.0,bitness64" /> <add name="Web-GIF" path="*.gif" verb="GET,HEAD,POST" modules="IsapiModule" scriptProcessor="C:\Windows\Microsoft.NET\Framework64\v4.0.30319\aspnet_isapi.dll" resourceType="Unspecified" preCondition="classicMode,runtimeVersionv4.0,bitness64" /> <add name="Web-JS" path="*.js" verb="GET,HEAD,POST,DEBUG" modules="IsapiModule" scriptProcessor="C:\Windows\Microsoft.NET\Framework64\v4.0.30319\aspnet_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>
编辑:如果我设置确切的失效date,caching工作,但不是为JPG,GIF ….只为PNG
编辑2:如果我设置cacheControlCustom="public"
像这里:
<clientCache cacheControlCustom="public" cacheControlMode="UseMaxAge" cacheControlMaxAge="7.00:00:00" />
caching正在工作,但仍然不是jpeg和gif; 它只适用于SVG和PNG。
大多数浏览器caching问题可以通过查看响应头来解决(可以在Google Chrome开发者工具中完成)。
现在, web.config
文件的clientCache
部分应该将输出caching设置为最大年龄,如下图所示将max-age
设置为86400
,即以秒为单位的1天。
这里是这个设置的web.config片段。
<clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="1.00:00:00" />
现在好了,响应头在Cache-Control
头上设置了max-age
属性。 所以浏览器应该caching内容。 那么这大多是真实的,但一些浏览器需要另一个标志设置。 特别是为caching控制头设置的public
标志。 这可以通过使用web.config
的cacheControlCustom
属性轻松添加。 这是一个例子。
<clientCache cacheControlCustom="public" cacheControlMode="UseMaxAge" cacheControlMaxAge="1.00:00:00" />
现在,当我们重试页面,并检查标题。
现在从上面的图片可以看出,我们现在具有public, max-age=86400
的值public, max-age=86400
。 所以我们的浏览器只需要caching资源。 现在检查谷歌浏览器的标题和networking标签将帮助我们。
这是对文件的第一个请求..注意文件没有被caching…
现在让我们回到这个页面( 注意:不要刷新页面,我们将在一秒钟内讨论)。 您将看到现在从caching返回的响应(如圈出)。
现在如果使用F5刷新页面或使用浏览器刷新function,会发生什么情况。 等等.. (from cache)
去哪儿了。
那么在谷歌浏览器(不知道其他浏览器),使用刷新button将重新下载静态资源,无论caching头( 请插入此处请澄清 )。 这意味着资源已经被重新检索,并且最大年龄头被发送。
在上述所有解释之后,请务必testing您如何监控caching标头。
更新
根据你的意见,你说你有一个名为Image.ashx
的通用处理程序( IHttpHandler
),其内容types为image/jpg
。 现在你可能会认为默认行为是caching这个处理程序。 但是,IIS将扩展名.ashx
(正确)视为dynamic脚本,并且不需要在代码本身中明确设置caching标头的情况下进行caching。
现在,这是你需要小心的地方,因为通常IHttpHandlers
实际上不应该caching,因为它们通常提供dynamic内容。 现在如果这个内容不太可能改变,你可以直接在代码中设置你的caching头。 以下是使用Response
上下文在IHttpHandlers
中设置caching头的示例。
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));
告诉输出caching将Cache-Control
头部的max-age=
部分设置为将来1
天(86400秒)。 -
context.Response.Cache.SetCacheability(HttpCacheability.Public);
告诉输出caching将Cache-Control
头设置为public
。 这是非常重要的,因为它告诉浏览器caching对象。 -
context.Response.Cache.SetSlidingExpiration(true);
告诉输出caching,以确保正确设置了Cache-Control
头的max-age=
部分。 如果不设置滑动过期,IIS输出caching将忽略最大年龄标头。 把它放在一起给了我这个结果。
如上所述,您可能不想caching.ashx
文件,因为它们通常提供dynamic内容。 但是,如果这个dynamic内容在给定的时间内不可能发生变化,那么您可以使用上面的方法来提供您的.ashx
文件。
现在结合上面列出的stream程,您还可以设置caching标头的ETag (请参阅Wiki)组件,以便浏览器可以validation由自定义string传送的内容。 该维基指出:
ETag是由Web服务器分配给在URL处find的特定资源版本的不透明标识符。 如果该URL的资源内容发生变化,则会分配一个新的不同的
ETag
。
所以这对于浏览器来说确实是一些独特的标识,以识别响应中传递的内容。 通过提供这个头,下一次重新加载的浏览器将通过最后一个响应中的ETag
发送一个If-None-Match
头。 我们可以修改我们的处理程序来检测If-None-Match
头,并将其与我们自己生成的Etag
进行比较。 现在没有确切的科学来生成ETags
但是一个好的经验法则是提供一个很可能只定义一个实体的标识符。 在这种情况下,我喜欢使用两个串联在一起的string,如。
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()
方法(在所有对象上)获取对象的整数哈希码。 在这个例子中,我把文件名的哈希值,然后是最后一次写入date。 上次写入date的原因是在文件被改变的情况下,哈希码也被改变,从而使得指纹不同。
这将生成一个类似于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
哈希,检查一个传入的If-None-Match
头。 如果etag散列和标头相等,那么我们通过返回状态码304 Not Modified
告诉浏览器内容没有改变。
接下来是相同的处理程序,除了我们通过调用添加ETag
头:
context.Response.Cache.SetETag(eTag);
当我们在浏览器中运行它时,我们得到了。
你会从图像中看到(就像我改变了文件名),我们现在已经有了我们caching系统的所有组件。 ETag
被作为一个头传递,浏览器正在发送请求头If-None-Match
所以我们的处理程序可以响应caching文件的变化。
用这个。 这对我来说很有用。
<staticContent> <clientCache cacheControlMode="UseExpires" httpExpires="Tue,19 Jan 2038 03:14:07 GMT"/> </staticContent>