c# - habilitar cache iis
Aproveche el almacenamiento en caché del navegador en IIS(problema de velocidad de la página de google) (3)
Hay varias preguntas sobre cómo aprovechar el almacenamiento en caché del navegador, pero no encontré nada útil sobre cómo hacerlo en una aplicación ASP.NET. La velocidad de páginas de Google dice que este es el mayor problema del rendimiento. Hasta ahora lo hice en mi 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>
El código comentado funciona. Puedo establecer que el encabezado expire sea un tiempo particular en el futuro, pero no pude establecer cacheControlMaxAge
para establecer cuántos días a partir de ahora el contenido estático se almacenaría en caché. No funciona. Mi pregunta es:
¿Cómo puedo hacer eso? Sé que es posible configurar el almacenamiento en caché solo para una carpeta específica, lo que sería una buena solución, pero tampoco funciona. La aplicación está alojada en Windows Server 2012, en IIS8, el grupo de aplicaciones está configurado en clásico.
Después de configurar este código en la configuración web, obtuve una velocidad de 72 páginas (antes era 71). 50 archivos no fueron almacenados en caché. (Ahora 49) Me preguntaba por qué y me acabo de dar cuenta de que un archivo en realidad estaba en la memoria caché (archivo svg). Lamentablemente, los archivos png y jpg no lo eran. Esta es mi 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>
EDITAR: si configuro la fecha de caducidad exacta, el almacenamiento en caché está funcionando, pero no para jpg, gif .... solo para png
EDIT2: Si configuro cacheControlCustom="public"
como aquí:
<clientCache cacheControlCustom="public"
cacheControlMode="UseMaxAge" cacheControlMaxAge="7.00:00:00" />
el almacenamiento en caché está funcionando, pero aún no es para jpegs y gifs; solo funciona para svgs y pngs.
La mayoría de los problemas de caché del navegador se pueden resolver al ver los encabezados de respuesta (se puede hacer en las herramientas de desarrollo chrome de Google).
Ahora, la sección de clientCache
de su archivo web.config
debe establecer el almacenamiento en caché de salida a una edad máxima, como puede ver en la imagen que se muestra a continuación. Estableció la max-age
en 86400
que es 1 día en segundos.
Aquí está el fragmento web.config para esta configuración.
<clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="1.00:00:00" />
Ahora que es genial, el encabezado de respuesta tiene una propiedad de max-age
configurada en el encabezado Cache-Control
. Entonces, el navegador debe almacenar en caché el contenido. Bueno, esto es cierto en su mayoría, pero algunos navegadores requieren que se establezca otra bandera. Específicamente, el indicador public
establecido para el encabezado de control de caché. Esto se puede agregar fácilmente mediante el uso del atributo cacheControlCustom
en web.config
. Aquí hay un ejemplo.
<clientCache cacheControlCustom="public" cacheControlMode="UseMaxAge" cacheControlMaxAge="1.00:00:00" />
Ahora cuando volvemos a intentar la página e inspeccionamos los encabezados.
Ahora, como puede ver en la imagen de arriba, ahora tenemos el valor public, max-age=86400
. Por lo tanto, nuestro navegador tiene todo lo que necesita para almacenar en caché los recursos. Ahora, examinar los encabezados y la pestaña de red de google chrome nos ayudará.
Aquí está la primera solicitud al archivo. Tenga en cuenta que el archivo no está en la memoria caché ...
Ahora naveguemos de regreso a esta página ( NOTA: no actualice la página, hablaremos de eso en un segundo). Verás que la respuesta ahora regresa de la memoria caché (como un círculo).
Ahora qué sucede si actualizo la página usando F5 o usando la característica de actualización del navegador. Espera ... ¿a dónde fue el (from cache)
?
Bueno, en Google Chrome (no estoy seguro acerca de otros navegadores), al usar el botón Actualizar volverá a descargar los recursos estáticos independientemente del encabezado del caché ( inserte la aclaración aquí, por favor ). Eso significa que los recursos se han recuperado y enviado el encabezado de edad máxima.
Ahora, después de toda la explicación anterior, asegúrese de probar cómo está supervisando los encabezados de la caché.
Actualizar
Según sus comentarios, indicó que tiene un controlador genérico ( IHttpHandler
) llamado Image.ashx
con el tipo de contenido de image/jpg
. Ahora puede esperar que el comportamiento predeterminado sea almacenar en caché este controlador. Sin embargo, IIS ve la extensión .ashx
(correctamente) como una secuencia de comandos dinámica y no está sujeta al almacenamiento en caché sin establecer explícitamente los encabezados de la memoria caché en el código mismo.
Ahora, aquí es donde debe tener cuidado, ya que, en general, IHttpHandlers
no debe almacenarse en caché, ya que suelen ofrecer contenido dinámico. Ahora, si es poco probable que ese contenido cambie, puede establecer sus encabezados de caché directamente en el código. Aquí hay un ejemplo de establecer encabezados de caché en IHttpHandlers
usando el contexto de 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"));
Ahora mirando el código estamos configurando algunas propiedades en la propiedad Cache
. Para obtener la respuesta deseada, he establecido las propiedades.
-
context.Response.Cache.SetMaxAge(TimeSpan.FromDays(1));
le dice a la memoria caché de salida que establezca que lamax-age=
parte del encabezado deCache-Control
sea de1
día en el futuro (86400 segundos). -
context.Response.Cache.SetCacheability(HttpCacheability.Public);
le dice al caché de salida que establezca el encabezadoCache-Control
enpublic
. Esto es bastante importante ya que le dice al navegador que guarde en caché para objetar. -
context.Response.Cache.SetSlidingExpiration(true);
le dice a la memoria caché de salida que se asegure de que está configurando lamax-age=
parte del encabezado deCache-Control
correctamente. Sin configurar el caducidad de deslizamiento, el caché de salida de IIS ignorará el encabezado de antigüedad máxima. Juntar esto me da este resultado.
Como .ashx
anteriormente, es posible que no desee almacenar en caché los archivos .ashx
, ya que normalmente entregan contenido dinámico. Sin embargo, si no es probable que ese contenido dinámico cambie en un período determinado, puede usar los métodos anteriores para entregar su archivo .ashx
.
Ahora, junto con el proceso mencionado anteriormente, también puede configurar el componente ETag (ver wiki) de los encabezados de la memoria caché para que el navegador pueda verificar el contenido entregado por una cadena personalizada. La wiki dice:
Un ETag es un identificador opaco asignado por un servidor web a una versión específica de un recurso que se encuentra en una URL. Si alguna vez cambia el contenido del recurso en esa URL, se asigna una
ETag
nueva y diferente.
Entonces, este es realmente un tipo de identificación única para que el navegador identifique el contenido que se está entregando en la respuesta. Al proporcionar este encabezado, el navegador en la próxima recarga enviará un encabezado If-None-Match
con el ETag
de la última respuesta. Podemos modificar nuestro controlador para detectar el encabezado If-None-Match
y compararlo con nuestro propio Etag
generado. Ahora no existe una ciencia exacta para generar ETags
pero una buena regla de oro es entregar un identificador que muy probablemente definirá solo una entidad. En este caso, me gusta usar dos cadenas concatenadas juntas, como.
System.IO.FileInfo file = new System.IO.FileInfo(context.Server.MapPath("~/saveNew.png"));
string eTag = file.Name.GetHashCode().ToString() + file.LastWriteTimeUtc.Ticks.GetHashCode().ToString();
En el fragmento anterior, estamos cargando un archivo de nuestro sistema de archivos (puede obtenerlo desde cualquier lugar). Luego estoy usando el método GetHashCode()
(en todos los objetos) para obtener el código hash entero del objeto. En el ejemplo, selecciono el hash del nombre del archivo, luego la última fecha de escritura. El motivo de la última fecha de escritura es que, en caso de que se cambie el archivo, también se modifique el código hash, lo que hace que las huellas dactilares sean diferentes.
Esto generará un código hash similar a 306894467-210133036
.
Entonces, ¿cómo usamos esto en nuestro controlador? A continuación se muestra la versión recientemente modificada del controlador.
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);
Como puede ver, he cambiado bastante el controlador, sin embargo, notará que generamos el hash Etag
, verifique si hay un encabezado If-None-Match
entrante. Si el hash etag y el encabezado son iguales, le diremos al navegador que el contenido no ha cambiado al devolver el código de estado 304 Not Modified
.
El siguiente fue el mismo controlador, excepto que agregamos el encabezado ETag
llamando:
context.Response.Cache.SetETag(eTag);
Cuando ejecutamos esto en el navegador obtenemos.
Verá en la imagen (como cambié el nombre del archivo) que ahora tenemos todos los componentes de nuestro sistema de caché en su lugar. El ETag
se está entregando como un encabezado, y el navegador está enviando el encabezado de solicitud If-None-Match
para que nuestro manejador pueda responder en consecuencia al cambio del archivo de caché.
Utilizar esta. Esto es trabajo para mi
<staticContent>
<clientCache cacheControlMode="UseExpires" httpExpires="Tue,19 Jan 2038 03:14:07 GMT"/>
</staticContent>
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<staticContent>
<clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="10.00:00:00" />
</staticContent>
</system.webServer>
</configuration>
Usando lo anterior, los archivos de contenido estático se almacenarán en caché durante 10 días en el navegador. Puede encontrar información detallada sobre el elemento <clientCache>
here .
También puede usar el elemento <location>
para definir la configuración de caché para un archivo específico:
<?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>