From 181db1835582f86eb846d8117ca8342671891bcd Mon Sep 17 00:00:00 2001 From: Sebastien Ros Date: Thu, 10 Mar 2016 14:52:08 -0800 Subject: [PATCH] Adding correct ETag support --- .../Filters/OutputCacheFilter.cs | 42 +++++++++++++------ .../Orchard.OutputCache/Models/CacheItem.cs | 3 +- .../Services/DefaultCacheStorageProvider.cs | 1 + 3 files changed, 32 insertions(+), 14 deletions(-) diff --git a/src/Orchard.Web/Modules/Orchard.OutputCache/Filters/OutputCacheFilter.cs b/src/Orchard.Web/Modules/Orchard.OutputCache/Filters/OutputCacheFilter.cs index 56be73b56..2f312dd01 100644 --- a/src/Orchard.Web/Modules/Orchard.OutputCache/Filters/OutputCacheFilter.cs +++ b/src/Orchard.Web/Modules/Orchard.OutputCache/Filters/OutputCacheFilter.cs @@ -207,6 +207,15 @@ namespace Orchard.OutputCache.Filters { var response = filterContext.HttpContext.Response; var captureStream = new CaptureStream(response.Filter); response.Filter = captureStream; + + // Add ETag header for the newly created item + var etag = Guid.NewGuid().ToString("n"); + if (HttpRuntime.UsingIntegratedPipeline) { + if (response.Headers.Get("ETag") == null) { + response.Headers["ETag"] = etag; + } + } + captureStream.Captured += (output) => { try { // Since this is a callback any call to injected dependencies can result in an Autofac exception: "Instances @@ -227,12 +236,12 @@ namespace Orchard.OutputCache.Filters { Url = filterContext.HttpContext.Request.Url.AbsolutePath, Tenant = scope.Resolve().Name, StatusCode = response.StatusCode, - Tags = new[] { _invariantCacheKey }.Union(contentItemIds).ToArray() + Tags = new[] { _invariantCacheKey }.Union(contentItemIds).ToArray(), + ETag = etag }; // Write the rendered item to the cache. var cacheStorageProvider = scope.Resolve(); - cacheStorageProvider.Remove(_cacheKey); cacheStorageProvider.Set(_cacheKey, cacheItem); Logger.Debug("Item '{0}' was written to cache.", _cacheKey); @@ -465,6 +474,7 @@ namespace Orchard.OutputCache.Filters { private void ServeCachedItem(ActionExecutingContext filterContext, CacheItem cacheItem) { var response = filterContext.HttpContext.Response; + var request = filterContext.HttpContext.Request; // Fix for missing charset in response headers response.Charset = response.Charset; @@ -474,12 +484,27 @@ namespace Orchard.OutputCache.Filters { response.AddHeader("X-Cached-On", cacheItem.CachedOnUtc.ToString("r")); response.AddHeader("X-Cached-Until", cacheItem.ValidUntilUtc.ToString("r")); } - + // Shorcut action execution. filterContext.Result = new FileContentResult(cacheItem.Output, cacheItem.ContentType); - response.StatusCode = cacheItem.StatusCode; + // Add ETag header + if (HttpRuntime.UsingIntegratedPipeline && response.Headers.Get("ETag") == null) { + response.Headers["ETag"] = cacheItem.ETag; + } + + // Check ETag in request + // https://www.w3.org/2005/MWI/BPWG/techs/CachingWithETag.html + var etag = request.Headers["If-None-Match"]; + if (!String.IsNullOrEmpty(etag)) { + if (String.Equals(etag, cacheItem.ETag, StringComparison.Ordinal)) { + // ETag matches the cached item, we return a 304 + filterContext.Result = new HttpStatusCodeResult(HttpStatusCode.NotModified); + return; + } + } + ApplyCacheControl(response); } @@ -509,15 +534,6 @@ namespace Orchard.OutputCache.Filters { // response.DisableKernelCache(); // response.Cache.SetOmitVaryStar(true); - // An ETag is a string that uniquely identifies a specific version of a component. - // We use the cache item to detect if it's a new one. - if (HttpRuntime.UsingIntegratedPipeline) { - if (response.Headers.Get("ETag") == null) { - // What is the point of GetHashCode() of a newly generated item? /DanielStolt - response.Cache.SetETag(new CacheItem().GetHashCode().ToString(CultureInfo.InvariantCulture)); - } - } - if (CacheSettings.VaryByQueryStringParameters == null) { response.Cache.VaryByParams["*"] = true; } diff --git a/src/Orchard.Web/Modules/Orchard.OutputCache/Models/CacheItem.cs b/src/Orchard.Web/Modules/Orchard.OutputCache/Models/CacheItem.cs index 29243df6f..aeeff8af0 100644 --- a/src/Orchard.Web/Modules/Orchard.OutputCache/Models/CacheItem.cs +++ b/src/Orchard.Web/Modules/Orchard.OutputCache/Models/CacheItem.cs @@ -4,7 +4,7 @@ namespace Orchard.OutputCache.Models { [Serializable] public class CacheItem { // used for serialization compatibility - public static readonly string Version = "1"; + public static readonly string Version = "2"; public DateTime CachedOnUtc { get; set; } public int Duration { get; set; } @@ -18,6 +18,7 @@ namespace Orchard.OutputCache.Models { public string Tenant { get; set; } public int StatusCode { get; set; } public string[] Tags { get; set; } + public string ETag { get; set; } public int ValidFor { get { return Duration; } diff --git a/src/Orchard.Web/Modules/Orchard.OutputCache/Services/DefaultCacheStorageProvider.cs b/src/Orchard.Web/Modules/Orchard.OutputCache/Services/DefaultCacheStorageProvider.cs index a74a3adea..23d1b7ed9 100644 --- a/src/Orchard.Web/Modules/Orchard.OutputCache/Services/DefaultCacheStorageProvider.cs +++ b/src/Orchard.Web/Modules/Orchard.OutputCache/Services/DefaultCacheStorageProvider.cs @@ -16,6 +16,7 @@ namespace Orchard.OutputCache.Services { } public void Set(string key, CacheItem cacheItem) { + _workContext.HttpContext.Cache.Remove(key); _workContext.HttpContext.Cache.Add( key, cacheItem,