【问题标题】:Leverage browser caching in IIS (google pagespeed issue)利用 IIS 中的浏览器缓存(谷歌页面速度问题)
【发布时间】:2014-01-31 04:33:38
【问题描述】:

关于利用浏览器缓存有几个问题,但我没有找到任何有用的信息来帮助我在 ASP.NET 应用程序中执行此操作。谷歌的 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 来设置从现在起缓存静态内容的天数。这是行不通的。我的问题是:

我该怎么做? 我知道可以仅为特定文件夹设置缓存,这将是一个很好的解决方案,但它也不起作用。应用程序托管在 Windows Server 2012 上,在 IIS8 上,应用程序池设置为经典。

在网络配置中设置此代码后,我的页面速度为 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:\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>

编辑: 如果我设置了确切的到期日期,缓存会起作用,但不适用于 jpg、gif ......仅适用于 png

EDIT2: 如果我在这里设置cacheControlCustom="public"

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

缓存有效,但仍然不适用于 jpeg 和 gif;它仅适用于 svgs 和 pngs。

【问题讨论】:

  • 是否有任何程序/服务可以修改或修改您的 jpg/gif 文件?

标签: c# asp.net caching iis httphandler


【解决方案1】:

大部分浏览器缓存问题都可以通过查看响应头来解决(可以在谷歌浏览器开发者工具中完成)。

现在您的web.config 文件的clientCache 部分应将您的输出缓存设置为最长期限,如下图所示,已将max-age 设置为86400,即1 天(以秒为单位)。

这是此设置的 web.config sn-p。

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

现在太好了,响应标头在 Cache-Control 标头上设置了 max-age 属性。所以浏览器应该缓存内容。好吧,这基本上是正确的,但有些浏览器需要设置另一个标志。特别是为缓存控制标头设置的public 标志。这可以通过使用web.config 中的cacheControlCustom 属性轻松添加。这是一个例子。

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

现在当我们重试页面并检查标题时。

现在您可以从上图中看到,我们现在有了值public, max-age=86400。所以我们的浏览器拥有缓存资源所需的一切。现在检查谷歌浏览器的标题和网络选项卡将对我们有所帮助。

这是对文件的第一个请求。请注意,文件未缓存...

现在让我们导航回该页面(注意:不要刷新页面,我们将在稍后讨论)。您将看到现在从缓存返回的响应(如圆圈所示)。

现在,如果我使用 F5 或使用浏览器刷新功能刷新页面会发生什么。等等..(from cache) 去哪儿了。

在谷歌浏览器(不确定其他浏览器)中使用刷新按钮将重新下载静态资源,而不管缓存标头如何(请在此处插入说明)。这意味着资源已被重新检索并发送了最大年龄标头。

在完成上述所有解释后,请务必测试如何监控缓存标头。

更新

根据您所说的 cmets,您有一个名为 Image.ashx 的通用处理程序 (IHttpHandler),其内容类型为 image/jpg。现在您可能期望默认行为是缓存此处理程序。但是,IIS 将扩展 .ashx(正确)视为动态脚本,并且如果没有在代码本身中明确设置缓存标头,则不受缓存​​的影响。

现在这是您需要小心的地方,因为通常IHttpHandlers 实际上不应被缓存,因为它们通常会提供动态内容。现在,如果该内容不太可能更改,您可以直接在代码中设置缓存标头。这是使用Response 上下文在IHttpHandlers 中设置缓存头的示例。

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)); 告诉输出缓存将Cache-Control 标头的max-age= 部分设置为未来一天(86400 秒)的1
  • context.Response.Cache.SetCacheability(HttpCacheability.Public); 告诉输出缓存将Cache-Control 标头设置为public。这非常重要,因为它告诉浏览器缓存到对象。
  • context.Response.Cache.SetSlidingExpiration(true); 告诉输出缓存确保它正确设置了Cache-Control 标头的max-age= 部分。如果不设置滑动过期,IIS 输出缓存将忽略 max age 标头。把这些放在一起就得到了这个结果。

如上所述,您可能不想缓存 .ashx 文件,因为它们通常会提供动态内容。但是,如果该动态内容在给定时间段内不太可能发生变化,您可以使用上述方法来传递您的 .ashx 文件。

现在结合上面列出的过程,您还可以设置缓存标头的 ETag(参见 wiki)组件,以便浏览器可以验证自定义字符串传递的内容。维基指出:

ETag 是一个不透明的标识符,由 Web 服务器分配给特定的 在 URL 中找到的资源的版本。如果资源内容在那 URL 不断变化,分配了一个新的不同的ETag

所以这实际上是浏览器用来识别响应中传递的内容的某种唯一标识。通过提供此标头,浏览器在下一次重新加载时将通过 If-None-Match 标头发送最后一个响应中的 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();

在上面的 sn-p 中,我们从我们的文件系统加载一个文件(你可以从任何地方得到它)。然后我使用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 哈希,检查传入的 If-None-Match 标头。如果 etag hash 和 header 相等,那么我们通过返回状态码 304 Not Modified 告诉浏览器内容没有改变。

除了我们通过调用添加 ETag 标头之外,下一个是相同的处理程序:

context.Response.Cache.SetETag(eTag);

当我们在浏览器中运行它时,我们得到了。

您将从图像中看到(正如我确实更改了文件名),我们现在拥有缓存系统的所有组件。 ETag 作为标头传递,浏览器正在发送请求标头If-None-Match,因此我们的处理程序可以相应地响应缓存文件的更改。

【讨论】:

  • 是否有任何程序/服务可以修改或修改您的 jpg/gif 文件?
  • 您是指第三方程序吗?但我确信 IIS 通常不会“接触”静态文件。在这种情况下,您必须为静态类型设置缓存配置文件以设置 ETagLast-Modified 标头。我可以在另一个问题上写一篇完整的文章,但非常直截了当。在 IIS 中查看Output Caching
  • 对不起,这是对问题的评论,而不是您的答案。我不小心在这里发帖。
  • 有一些图像处理程序 Image.ashx 但我可以在他的代码中看到 this:ctx.Response.ContentType = "image/jpeg";
  • @impeRAtoR 我在通用处理程序、etag 和更多缓存工作上添加了相当长的部分。如果可能,请查看并接受。
【解决方案2】:

使用这个。这对我有用。

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

【讨论】:

    【解决方案3】:
    <?xml version="1.0" encoding="UTF-8"?>
    <configuration>
      <system.webServer>
        <staticContent>
          <clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="10.00:00:00" />
        </staticContent>
      </system.webServer>
    </configuration>
    

    使用上述方法,静态内容文件将在浏览器中缓存 10 天。有关&lt;clientCache&gt; 元素的详细信息,请参见here

    您还可以使用&lt;location&gt; 元素来定义特定文件的缓存设置:

    <?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>
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-18
      • 2012-06-19
      • 2017-08-23
      • 2014-07-08
      • 2020-03-07
      • 1970-01-01
      相关资源
      最近更新 更多