web-dev-qa-db-ja.com

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では、アプリケーションプールはクラシックに設定されます。

このコードをweb configに設定した後、ページ速度が72になりました(以前は71でした)。 50個のファイルがキャッシュされませんでした。 (現在49)なぜだろうかと思って、1つのファイルが実際にキャッシュされていることに気づきました(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" /> 

キャッシングは機能していますが、stillはjpegおよびgifではありません。 svgとpngでのみ機能します。

50
Vlado Pandžić

ブラウザーのキャッシュの問題のほとんどは、応答ヘッダーを表示することで解決できます(Google chrome開発者ツールで実行できます)。

enter image description here

これで、_web.config_ファイルのclientCacheセクションは、以下の画像に示すように、出力キャッシュを最大年齢に設定する必要があります。これは_max-age_を_86400_(1日)すぐに。

このセットアップのweb.configスニペットを次に示します。

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

これで、応答ヘッダーの_max-age_ヘッダーに_Cache-Control_プロパティが設定されました。そのため、ブラウザはコンテンツをキャッシュする必要があります。まあ、これはほとんど本当ですが、一部のブラウザは別のフラグを設定する必要があります。特に、キャッシュ制御ヘッダーに設定されたpublicフラグ。これは、_web.config_のcacheControlCustom属性を使用して簡単に追加できます。以下に例を示します。

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

ページを再試行して、ヘッダーを検査します。

enter image description here

上の画像からわかるように、値_public, max-age=86400_があります。したがって、ブラウザにはリソースをキャッシュするために必要なものがすべて揃っています。 google chrome=のヘッダーとネットワークタブを確認してください。

ファイルへの最初のリクエストです。ファイルがキャッシュされていないことに注意してください... enter image description here

次に、このページに戻ります(注:ページを更新しないでください。すぐに説明します)。キャッシュから戻る際の応答が表示されます(丸で囲んだ部分)。

enter image description here

どちらかを使用してページを更新するとどうなりますか F5 またはブラウザの更新機能を使用します。待ってください。_(from cache)_はどこに行きましたか。 enter image description here

Googleでは、Chrome(他のブラウザについてはわかりません)更新ボタンを使用すると、キャッシュヘッダーに関係なく静的リソースが再ダウンロードされます(ここに説明を挿入してください)。これは、リソースが再取得され、max ageヘッダーが送信されたことを意味します。

上記のすべての説明を終えたら、キャッシュヘッダーを監視していることをテストしてくださいhow

更新

コメントに基づいて、コンテンツタイプが_Image.ashx_である_image/jpg_という名前の汎用ハンドラー(IHttpHandler)があると述べました。これで、デフォルトの動作はこのハンドラーをキャッシュすることになると思われるかもしれません。ただし、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));は、_max-age=_ヘッダーの_Cache-Control_部分を将来(86400秒)__1_に設定するよう出力キャッシュに指示します。
  • context.Response.Cache.SetCacheability(HttpCacheability.Public);は、出力キャッシュに_Cache-Control_ヘッダーをpublicに設定するよう指示します。これは、ブラウザにオブジェクトにキャッシュするよう指示するため、非常に重要です。
  • context.Response.Cache.SetSlidingExpiration(true);は、出力キャッシュに、_max-age=_ヘッダーの_Cache-Control_部分が適切に設定されていることを確認するよう指示します。スライド式の有効期限を設定しないと、IIS出力キャッシュはmax ageヘッダーを無視します。これをまとめると、この結果が得られます。

output cache from ashx file

上で述べたように、_.ashx_ファイルは通常動的コンテンツを配信するため、キャッシュしたくない場合があります。ただし、その動的コンテンツが一定期間内に変更される可能性が低い場合は、上記の方法を使用して_.ashx_ファイルを配信できます。

上記のプロセスと併せて、キャッシュヘッダーのETag(wikiを参照)コンポーネントを設定することもできます。ブラウザは、配信されているコンテンツをカスタム文字列で検証できます。 wikiの状態:

ETagは、URLで見つかったリソースの特定のバージョンにWebサーバーによって割り当てられる不透明な識別子です。そのURLのリソースコンテンツが変更されると、新しく異なるETagが割り当てられます。

したがって、これは実際には、ブラウザーが応答で配信されるコンテンツを識別するための何らかの一意の識別です。このヘッダーを提供することにより、ブラウザは次のリロード時に最後の応答からのETagを含む_If-None-Match_ヘッダーを送信します。ハンドラーを変更して_If-None-Match_ヘッダーを検出し、独自に生成されたEtagと比較できます。 ETagsを生成するための正確な科学はありませんが、1つのエンティティのみを定義する可能性が高い識別子を提供するのが良い経験則です。この場合、次のように連結された2つの文字列を使用します。

_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ハッシュを生成し、_If-None-Match_ヘッダーの受信を確認します。 etagハッシュとヘッダーが等しい場合、ステータスコード_304 Not Modified_を返すことにより、コンテンツが変更されていないことをブラウザに伝えます。

次は、次の呼び出しによってETagヘッダーを追加する以外は同じハンドラーです。

_context.Response.Cache.SetETag(eTag);
_

ブラウザでこれを実行すると、取得されます。

Cache-Control with ETag

イメージから(ファイル名を変更したように)、キャッシュシステムのすべてのコンポーネントが適切に配置されていることがわかります。 ETagはヘッダーとして配信され、ブラウザはリクエストヘッダー_If-None-Match_を送信しているため、ハンドラーは変更されたキャッシュファイルに応じて応答できます。

103
Nico

これを使って。これは私のために動作します。

<staticContent>
<clientCache cacheControlMode="UseExpires" httpExpires="Tue,19 Jan 2038 03:14:07 GMT"/>
</staticContent>
5
<?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>要素に関する詳細情報は、 here にあります。

<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>
1
Rohan Khude