diff --git a/src/Orchard.Web/Modules/Orchard.OutputCache/Models/CacheItem.cs b/src/Orchard.Web/Modules/Orchard.OutputCache/Models/CacheItem.cs index dd0c1d0b3..555a19d6c 100644 --- a/src/Orchard.Web/Modules/Orchard.OutputCache/Models/CacheItem.cs +++ b/src/Orchard.Web/Modules/Orchard.OutputCache/Models/CacheItem.cs @@ -3,6 +3,9 @@ namespace Orchard.OutputCache.Models { [Serializable] public class CacheItem { + // used for serialization compatibility + public static readonly string Version = "1"; + public DateTime CachedOnUtc { get; set; } public int Duration { get; set; } public int GraceTime { get; set; } diff --git a/src/Orchard.Web/Modules/Orchard.Redis/OutputCache/RedisOutputCacheStorageProvider.cs b/src/Orchard.Web/Modules/Orchard.Redis/OutputCache/RedisOutputCacheStorageProvider.cs index daa6ae902..8b1edbc3d 100644 --- a/src/Orchard.Web/Modules/Orchard.Redis/OutputCache/RedisOutputCacheStorageProvider.cs +++ b/src/Orchard.Web/Modules/Orchard.Redis/OutputCache/RedisOutputCacheStorageProvider.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Newtonsoft.Json; using Orchard.Environment.Configuration; using Orchard.Environment.Extensions; using Orchard.Logging; @@ -10,11 +9,23 @@ using Orchard.OutputCache.Models; using Orchard.OutputCache.Services; using Orchard.Redis.Extensions; using StackExchange.Redis; +using System.Runtime.Serialization.Formatters.Binary; +using System.IO; +using System.IO.Compression; namespace Orchard.Redis.OutputCache { [OrchardFeature("Orchard.Redis.OutputCache")] [OrchardSuppressDependency("Orchard.OutputCache.Services.DefaultCacheStorageProvider")] + /// + /// This implementation stores a instance to a Redis server. + /// The item is serialized using a and GZipped. We rely + /// on compression at this level of the implementation as other output cache providers + /// might not want to rely on it, or transform the data to binary. The content is compressed + /// as HTML pages can be consequent, like several hundreds of KB, and the network be clogged. + /// To prevent versioning issues with serialized data, the Redis keys contain the + /// property. + /// public class RedisOutputCacheStorageProvider : IOutputCacheStorageProvider { private readonly ShellSettings _shellSettings; @@ -43,12 +54,19 @@ namespace Orchard.Redis.OutputCache { } public void Set(string key, CacheItem cacheItem) { + if(cacheItem == null) { + throw new ArgumentNullException("cacheItem"); + } + if (cacheItem.ValidFor <= 0) { return; } - var value = JsonConvert.SerializeObject(cacheItem); - Database.StringSet(GetLocalizedKey(key), value, TimeSpan.FromSeconds(cacheItem.ValidFor)); + using (var decompressedStream = Serialize(cacheItem)) { + using (var compressedStream = Compress(decompressedStream)) { + Database.StringSet(GetLocalizedKey(key), compressedStream.ToArray(), TimeSpan.FromSeconds(cacheItem.ValidFor)); + } + } } public void Remove(string key) { @@ -60,12 +78,21 @@ namespace Orchard.Redis.OutputCache { } public CacheItem GetCacheItem(string key) { - string value = Database.StringGet(GetLocalizedKey(key)); - if (String.IsNullOrEmpty(value)) { + var value = Database.StringGet(GetLocalizedKey(key)); + + if (value.IsNullOrEmpty) { return null; } - return JsonConvert.DeserializeObject(value); + using (var compressedStream = new MemoryStream(value)) { + if(compressedStream.Length == 0) { + return null; + } + + using(var decompressedStream = Decompress(compressedStream)) { + return Deserialize(decompressedStream); + } + } } public IEnumerable GetCacheItems(int skip, int count) { @@ -88,7 +115,7 @@ namespace Orchard.Redis.OutputCache { /// The key to localized. /// A localized key based on the tenant name. private string GetLocalizedKey(string key) { - return _shellSettings.Name + ":OutputCache:" + key; + return _shellSettings.Name + ":OC:" + CacheItem.Version + ":" + key; } /// @@ -111,5 +138,37 @@ namespace Orchard.Redis.OutputCache { return _keysCache; } + + private static MemoryStream Serialize(CacheItem item) { + BinaryFormatter binaryFormatter = new BinaryFormatter(); + var memoryStream = new MemoryStream(); + binaryFormatter.Serialize(memoryStream, item); + memoryStream.Seek(0, SeekOrigin.Begin); + return memoryStream; + } + + private static CacheItem Deserialize(Stream stream) { + BinaryFormatter binaryFormatter = new BinaryFormatter(); + var result = (CacheItem)binaryFormatter.Deserialize(stream); + return result; + } + + private static MemoryStream Compress(Stream stream) { + var compressedStream = new MemoryStream(); + using (var compressionStream = new GZipStream(compressedStream, CompressionMode.Compress)) { + stream.CopyTo(compressionStream); + return compressedStream; + } + } + + private static Stream Decompress(Stream stream) { + var decompressedStream = new MemoryStream(); + using (GZipStream decompressionStream = new GZipStream(stream, CompressionMode.Decompress)) { + decompressionStream.CopyTo(decompressedStream); + decompressedStream.Seek(0, SeekOrigin.Begin); + return decompressedStream; + } + + } } } \ No newline at end of file