web-dev-qa-db-fra.com

Tirer parti de la mise en cache du navigateur dans IIS (question google pagespeed)

Il existe plusieurs questions sur l'utilisation de la mise en cache du navigateur, mais je n'ai rien trouvé d'utile sur la procédure à suivre dans une application ASP.NET. Pagespeed de Google indique qu'il s'agit du plus gros problème de performances… .. Jusqu'à présent, je l'avais déjà fait dans mon 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>

Le code commenté fonctionne. Je peux définir l'en-tête "Expire" à un moment particulier à l'avenir, mais je n'ai pas pu configurer cacheControlMaxAge pour définir combien de jours le contenu statique serait mis en cache. Ça ne marche pas. Mes questions sont:

Comment puis-je faire cela? Je sais qu'il est possible de définir la mise en cache uniquement pour un dossier spécifique, ce qui serait une bonne solution, mais cela ne fonctionne pas également. L'application est hébergée sur Windows Server 2012, sur IIS8, le pool d'applications est défini sur classic.

Après avoir défini ce code dans la configuration Web, la vitesse de rotation de page était de 72 (auparavant, elle était de 71). 50 fichiers n'ont pas été mis en cache. (Maintenant 49) Je me demandais pourquoi et je venais de réaliser qu'un fichier était réellement mis en cache (fichier svg). Malheureusement, les fichiers png et jpg n'étaient pas . C'est mon 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: Si je règle la date d'expiration exacte, la mise en cache fonctionne, mais pas pour jpg, gif .... seulement pour png

EDIT2: Si je mets cacheControlCustom="public" comme ici:

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

la mise en cache fonctionne mais toujours pas pour les fichiers JPEG et les gifs; cela ne fonctionne que pour svgs et pngs.

50
Vlado Pandžić

La plupart des problèmes de mise en cache du navigateur peuvent être résolus en affichant les en-têtes de réponse (peut être effectué dans les outils de développement Google chrome).

enter image description here

Maintenant, la section clientCache de votre fichier web.config doit définir la mise en cache de votre sortie sur un âge maximum comme vous le voyez dans l'image ci-dessous a défini le max-age sur 86400 qui correspond à 1 jour. en secondes.

Voici l'extrait web.config pour cette configuration.

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

Maintenant, c’est génial, l’en-tête de réponse a une propriété max-age sur l’en-tête Cache-Control. Donc, le navigateur devrait ​​mettre en cache le contenu. Cela est généralement vrai, mais certains navigateurs exigent la définition d’un autre indicateur. En particulier, l'indicateur public défini pour l'en-tête de contrôle du cache. Ceci peut être facilement ajouté en utilisant l'attribut cacheControlCustom dans le web.config. Voici un exemple.

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

Maintenant, lorsque nous réessayons la page et inspectons les en-têtes.

enter image description here

Comme vous pouvez le voir sur l'image ci-dessus, nous avons maintenant la valeur public, max-age=86400. Donc, notre navigateur a tout ce qu'il faut pour mettre en cache les ressources. En examinant maintenant les en-têtes et l'onglet réseau de Google chrome, cela nous aidera.

Voici la première demande dans le fichier. Notez que le fichier n'est pas mis en cache ... enter image description here

Maintenant, revenons à cette page ( NOTE: n’actualisez pas la page, nous en reparlerons dans une seconde). Vous verrez la réponse maintenant en revenant de cache (comme encerclé).

enter image description here

Maintenant que se passe-t-il si j'actualise la page en utilisant soit F5 ou en utilisant la fonctionnalité d'actualisation du navigateur. Attends .. où est passé le (from cache)enter image description here

Bien dans Google Chrome (vous ne savez pas quels autres navigateurs), le bouton Actualiser permet de télécharger à nouveau les ressources statiques, quel que soit l'en-tête du cache (insérer une clarification ici). Cela signifie que les ressources ont été récupérées et que l'en-tête max age a été envoyé.

Maintenant, après toutes les explications ci-dessus, assurez-vous de tester comment vous surveillez les en-têtes de cache.

Mettre à jour

D'après vos commentaires, vous avez indiqué que vous avez un gestionnaire générique (IHttpHandler) nommé Image.ashx avec le type de contenu image/jpg. Maintenant, vous pouvez vous attendre à ce que le comportement par défaut soit de mettre ce gestionnaire en cache. Cependant, IIS voit l'extension .ashx (correctement) comme un script dynamique et n'est pas soumise à la mise en cache sans définir explicitement les en-têtes de cache dans le code lui-même.

Maintenant, c’est là que vous devez faire attention, car généralement IHttpHandlers devrait en fait ne pas être mis en cache car ils fournissent généralement un contenu dynamique. Maintenant, s'il est peu probable que ce contenu change, vous pouvez définir vos en-têtes de cache directement dans le code. Voici un exemple de définition d'en-têtes de cache dans IHttpHandlers à l'aide du contexte 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"));

En regardant maintenant le code, nous définissons quelques propriétés sur la propriété Cache. Pour obtenir la réponse souhaitée, j'ai défini les propriétés.

  • context.Response.Cache.SetMaxAge(TimeSpan.FromDays(1)); indique au cache de sortie de définir le max-age= de l'en-tête Cache-Control sur 1 jour dans le futur (86 400 secondes).
  • context.Response.Cache.SetCacheability(HttpCacheability.Public); indique au cache de sortie de définir l'en-tête Cache-Control sur public. Ceci est très important car il indique au navigateur de mettre en cache pour objecter.
  • context.Response.Cache.SetSlidingExpiration(true); indique au cache de sortie de s'assurer qu'il définit correctement la partie max-age= de l'en-tête Cache-Control. Sans définir l'expiration glissante, la mise en cache IIS out out ignorera l'en-tête max age. Rassembler cela me donne ce résultat.

output cache from ashx file

Comme je l'ai indiqué ci-dessus, vous ne souhaitez peut-être pas mettre en cache les fichiers .ashx car ils fournissent généralement un contenu dynamique. Toutefois, si ce contenu dynamique n’est pas susceptible de changer au cours d’une période donnée, vous pouvez utiliser les méthodes ci-dessus pour livrer votre fichier .ashx.

Maintenant, en conjonction avec le processus énuméré ci-dessus, vous pouvez également définir le composant ETag (voir wiki) des en-têtes de cache afin que navigateur peut vérifier le contenu livré par une chaîne personnalisée. Le wiki déclare:

Un ETag est un identifiant opaque attribué par un serveur Web à une version spécifique d'une ressource trouvée sur une URL. Si le contenu de la ressource à cette URL change, un nouveau et différent ETag est attribué.

Il s’agit donc d’une sorte d’identification unique permettant au navigateur d’identifier le contenu fourni dans la réponse. En fournissant cet en-tête, le navigateur lors du prochain rechargement enverra un en-tête If-None-Match avec le ETag de la dernière réponse. Nous pouvons modifier notre gestionnaire pour détecter l'en-tête If-None-Match et le comparer à notre propre Etag généré. Il n’existe à présent aucune science exacte permettant de générer ETags, mais une bonne règle empirique consiste à fournir un identifiant qui définira très probablement une seule entité. Dans ce cas, j'aime utiliser deux chaînes concaténées telles que.

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

Dans l'extrait ci-dessus, nous chargeons un fichier à partir de notre système de fichiers (vous pouvez l'obtenir de n'importe où). J'utilise alors la méthode GetHashCode() (sur tous les objets) pour obtenir le code de hachage entier de l'objet. Dans l'exemple, je concatre le hachage du nom de fichier, puis la date de la dernière écriture. La raison de la dernière date d'écriture est dans le cas où le fichier est modifié, le code de hachage est également modifié, ce qui rend les empreintes digitales différentes.

Cela générera un code de hachage semblable à 306894467-210133036.

Alors, comment utilisons-nous cela dans notre gestionnaire. Vous trouverez ci-dessous la version nouvellement modifiée du gestionnaire.

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

Comme vous pouvez le constater, j’ai beaucoup changé de gestionnaire, mais vous remarquerez que nous générons le hash Etag, nous recherchons un en-tête If-None-Match entrant. Si le hachage etag est égal à l'en-tête, nous informons le navigateur que le contenu n'a pas changé en renvoyant le code d'état 304 Not Modified.

Suivant était le même gestionnaire, sauf que nous ajoutons l'en-tête ETag en appelant:

context.Response.Cache.SetETag(eTag);

Lorsque nous lançons ceci dans le navigateur, nous obtenons.

Cache-Control with ETag

Vous verrez sur l'image (comme j'ai changé le nom du fichier) que nous avons maintenant tous les composants de notre système de cache en place. La ETag est livrée sous forme d'en-tête et le navigateur envoie l'en-tête de requête If-None-Match afin que notre gestionnaire puisse répondre en conséquence au fichier de cache modifié.

101
Nico

Utilisez ceci. Ceci fonctionne pour moi.

<staticContent>
<clientCache cacheControlMode="UseExpires" httpExpires="Tue,19 Jan 2038 03:14:07 GMT"/>
</staticContent>
5
Hardik Mandankaa
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
  <system.webServer>
    <staticContent>
      <clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="10.00:00:00" />
    </staticContent>
  </system.webServer>
</configuration>

En utilisant ce qui précède, les fichiers de contenu statiques seront mis en cache pendant 10 jours dans le navigateur. Vous trouverez des informations détaillées sur l’élément <clientCache>ici .

Vous pouvez également utiliser l'élément <location> pour définir les paramètres de cache pour un fichier spécifique:

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