wake-up-neo.com

Browser-Caching nutzen IIS (Google Pagespeed Ausgabe)

Es gibt mehrere Fragen zur Verwendung des Browser-Caching, aber ich habe in einer ASP.NET-Anwendung nichts Nützliches gefunden. Google Pagespeed besagt, dass dies das größte Problem der Leistung ist ..__ Bisher habe ich dies in meiner web.config getan:

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

Kommentierter Code funktioniert. Ich kann den Expire-Header zu einem bestimmten Zeitpunkt in der Zukunft festlegen, aber ich konnte cacheControlMaxAge nicht festlegen, um festzulegen, wie viele Tage ab jetzt statischer Inhalt zwischengespeichert wird. Es funktioniert nicht. Meine Fragen sind:

Wie kann ich das tun? Ich weiß, dass es möglich ist, das Caching nur für bestimmte Ordner festzulegen, was eine gute Lösung wäre, aber es funktioniert auch nicht. Die Anwendung wird auf Windows Server 2012 gehostet. Auf IIS8 ist der Anwendungspool auf klassisch gesetzt.

Nachdem ich diesen Code in der Web-Konfiguration eingestellt hatte, erhielt ich eine Seitengeschwindigkeit von 72 (vorher war 71). 50 Dateien wurden nicht zwischengespeichert. (Jetzt 49) Ich habe mich gefragt, warum, und mir wurde gerade klar, dass eine Datei tatsächlich zwischengespeichert wurde (SVG-Datei). Leider waren png- und jpg-Dateien nicht .. _. Dies ist meine 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>

EDIT: Wenn ich ein genaues Ablaufdatum setze, funktioniert das Caching, aber nicht für jpg, gif .... nur für png

EDIT2: Wenn ich cacheControlCustom="public" wie hier eingestellt habe:

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

caching funktioniert aber noch nicht für JPEG und GIF; es funktioniert nur für svgs und pngs.

50
Vlado Pandžić

Die meisten Probleme beim Zwischenspeichern des Browsers können durch Anzeigen der Antwortheader behoben werden (dies ist in den Google Chrome-Entwicklertools möglich).

enter image description here

Jetzt sollte im Abschnitt clientCacheIhrer web.config-Datei das Zwischenspeichern Ihrer Ausgabe auf ein maximales Alter festgelegt werden, wie Sie in der Abbildung unten sehen, und der max-age muss auf 86400 gesetzt werden. Dies entspricht 1 Tag in Sekunden.

Hier ist das web.config-Snippet für dieses Setup.

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

Nun, das ist großartig, der Antwortheader hat eine max-age -Eigenschaft, die für den Cache-Control -Header festgelegt ist. Daher sollte der Browser den Inhalt zwischenspeichern . Nun, das ist größtenteils der Fall, aber bei einigen Browsern muss ein anderes Flag gesetzt werden. Insbesondere das Flag publicname__, das für den Cache-Steuerelement-Header festgelegt ist. Dies kann einfach durch Verwendung des Attributs cacheControlCustomim web.config hinzugefügt werden. Hier ist ein Beispiel.

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

Jetzt, wenn wir die Seite wiederholen und die Überschriften überprüfen.

enter image description here

Wie Sie aus dem obigen Bild sehen können, haben wir jetzt den Wert public, max-age=86400. Unser Browser hat also alles, um die Ressourcen zwischenzuspeichern. Wenn Sie nun die Überschriften und den Tab "Netzwerk" von Google Chrome überprüfen, können Sie uns dabei helfen.

Hier ist die erste Anforderung an die Datei. Beachten Sie, dass die Datei nicht zwischengespeichert wird. enter image description here

Jetzt können Sie zu dieser Seite zurückkehren ( HINWEIS: Aktualisieren Sie die Seite nicht, wir werden gleich darauf eingehen.) Sie werden sehen, dass die Antwort jetzt aus dem Cache zurückkehrt (wie eingekreist).

enter image description here

Was passiert nun, wenn ich die Seite mit einem der beiden aktualisiere? F5 oder mit der Browser-Aktualisierungsfunktion. Warten Sie .. wo ist der (from cache) hingegangen. enter image description here

Wenn Sie in Google Chrome (bei anderen Browsern nicht sicher) die Schaltfläche "Aktualisieren" verwenden, werden die statischen Ressourcen ungeachtet des Cache-Headers erneut heruntergeladen ( Bitte hier eine Erläuterung einfügen ). Das bedeutet, dass die Ressourcen erneut abgerufen und der Header für das maximale Alter gesendet wurde.

Testen Sie nach all den obigen Erläuterungen , wie Sie die Cache-Header überwachen.

Update

Basierend auf Ihren Kommentaren haben Sie angegeben, dass Sie einen generischen Handler (IHttpHandlername__) mit dem Namen Image.ashx und dem Inhaltstyp image/jpg haben. Nun können Sie davon ausgehen, dass das Standardverhalten darin besteht, diesen Handler zwischenzuspeichern. Allerdings sieht IIS die Erweiterung .ashx (korrekt) als dynamisches Skript und unterliegt keiner Zwischenspeicherung, ohne die Cache-Header explizit im Code selbst festzulegen.

Hier müssen Sie jetzt vorsichtig sein, da IHttpHandlersnormalerweise nicht zwischengespeichert werden sollte, da sie normalerweise dynamischen Inhalt liefern. Wenn sich dieser Inhalt wahrscheinlich nicht ändert, können Sie die Cache-Header direkt im Code festlegen. Hier ist ein Beispiel für das Setzen von Cache-Headern in IHttpHandlersim Kontext Responsename__.

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"));

Wenn wir uns nun den Code ansehen, setzen wir einige Eigenschaften für die Eigenschaft Cachename__. Um die gewünschte Antwort zu erhalten, habe ich die Eigenschaften eingestellt.

  • context.Response.Cache.SetMaxAge(TimeSpan.FromDays(1)); weist den Ausgabecache an, den max-age=-Teil des Cache-Control-Headers auf 1-Tag in der Zukunft festzulegen (86400 Sekunden).
  • context.Response.Cache.SetCacheability(HttpCacheability.Public); weist den Ausgabecache an, den Header Cache-Control auf publiczu setzen. Dies ist sehr wichtig, da der Browser angewiesen wird, das Objekt zwischenzuspeichern.
  • context.Response.Cache.SetSlidingExpiration(true); weist den Ausgabecache an, sicherzustellen, dass der Teil max-age= des Headers Cache-Control ordnungsgemäß festgelegt wird. Ohne das Festlegen des gleitenden Ablaufs ignoriert das IIS ausgegebene Caching den Header für das maximale Alter. Wenn ich das zusammensetze, bekomme ich das Ergebnis.

output cache from ashx file

Wie bereits erwähnt, möchten Sie die .ashx-Dateien möglicherweise nicht zwischenspeichern, da sie normalerweise dynamischen Inhalt liefern. Wenn sich dieser dynamische Inhalt jedoch in einem bestimmten Zeitraum wahrscheinlich nicht ändert, können Sie die oben genannten Methoden verwenden, um Ihre .ashx-Datei zu übermitteln.

Jetzt können Sie in Verbindung mit dem oben aufgeführten Vorgang auch die ETag (siehe Wiki) -Komponente der Cache-Header so einstellen, dass die Der Browser kann den von einer benutzerdefinierten Zeichenfolge bereitgestellten Inhalt überprüfen. Im Wiki heißt es:

Ein ETag ist eine undurchsichtige Kennung, die von einem Webserver einer bestimmten Version einer Ressource zugewiesen wird, die sich unter einer URL befindet. Wenn sich der Ressourceninhalt unter dieser URL jemals ändert, wird ein neuer und anderer ETagzugewiesen.

Das ist also wirklich eine Art eindeutige Identifikation für den Browser, um den Inhalt zu identifizieren, der in der Antwort geliefert wird. Wenn Sie diesen Header angeben, sendet der Browser beim nächsten erneuten Laden einen If-None-Match-Header mit dem ETagaus der letzten Antwort. Wir können unseren Handler so ändern, dass er den Header If-None-Match erkennt und ihn mit unserem eigenen generierten Namen für Etag vergleicht. Jetzt gibt es keine exakte Wissenschaft, um ETagszu generieren, aber eine gute Faustregel ist, einen Bezeichner zu liefern, der höchstwahrscheinlich nur eine Entität definiert. In diesem Fall verwende ich gerne zwei miteinander verkettete Zeichenfolgen wie z.

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

Im obigen Snippet laden wir eine Datei aus unserem Dateisystem (Sie können diese von überall herunterladen). Ich benutze dann die Methode GetHashCode() (für alle Objekte), um den ganzzahligen Hash-Code des Objekts abzurufen. Im Beispiel konzentriere ich mich auf den Hash des Dateinamens und dann auf das Datum des letzten Schreibens. Der Grund für das letzte Schreibdatum ist, dass bei einer Änderung der Datei auch der Hash-Code geändert wird, wodurch sich die Fingerabdrücke unterscheiden.

Dadurch wird ein Hash-Code generiert, der 306894467-210133036 ähnelt.

Wie verwenden wir das in unserem Handler? Unten ist die neu modifizierte Version des Handlers.

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);

Wie Sie sehen, habe ich eine ganze Reihe von Änderungen am Handler vorgenommen. Sie werden jedoch feststellen, dass wir den Hash Etaggenerieren und nach einem eingehenden Header If-None-Match suchen. Wenn der etag-Hash und der Header gleich sind, teilen wir dem Browser mit, dass sich der Inhalt nicht geändert hat, indem wir den Statuscode 304 Not Modified zurückgeben.

Als Nächstes wurde derselbe Handler verwendet, außer dass wir den Header ETaghinzufügen, indem wir Folgendes aufrufen:

context.Response.Cache.SetETag(eTag);

Wenn wir dies im Browser ausführen, erhalten wir.

Cache-Control with ETag

Sie werden aus dem Bild (als ich den Dateinamen geändert habe) sehen, dass wir jetzt alle Komponenten unseres Cache-Systems an Ort und Stelle haben. Der ETagwird als Header übergeben, und der Browser sendet den Anforderungsheader If-None-Match, damit unser Handler entsprechend auf geänderte Cachedateien reagieren kann.

101
Nico

Benutze das. Das funktioniert für mich.

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

Unter Verwendung der obigen Informationen werden statische Inhaltsdateien 10 Tage im Browser zwischengespeichert. Detaillierte Informationen zum <clientCache>-Element finden Sie hier .

Sie können das <location>-Element auch verwenden, um die Cache-Einstellungen für eine bestimmte Datei zu definieren:

<?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>
0
Rohan Khude