From 724d873d3f189538126ef7904dce7b74a3ce0c23 Mon Sep 17 00:00:00 2001 From: Bo Bendtsen Date: Fri, 18 Nov 2016 11:29:42 +0100 Subject: [PATCH] Shared Max Age support added. Optional parameter to specify the "s-maxage" header. Fixes #186 --- src/WebApi.OutputCache.Core/Time/CacheTime.cs | 2 ++ src/WebApi.OutputCache.Core/Time/ShortTime.cs | 11 ++++++++-- .../CacheOutputAttribute.cs | 21 ++++++++++++++++++- .../ClientSideTests.cs | 16 ++++++++++++++ .../TestControllers/SampleController.cs | 7 +++++++ 5 files changed, 54 insertions(+), 3 deletions(-) diff --git a/src/WebApi.OutputCache.Core/Time/CacheTime.cs b/src/WebApi.OutputCache.Core/Time/CacheTime.cs index 54241bb..3138477 100644 --- a/src/WebApi.OutputCache.Core/Time/CacheTime.cs +++ b/src/WebApi.OutputCache.Core/Time/CacheTime.cs @@ -7,6 +7,8 @@ public class CacheTime // client cache length in seconds public TimeSpan ClientTimeSpan { get; set; } + public TimeSpan? SharedTimeSpan { get; set; } + public DateTimeOffset AbsoluteExpiration { get; set; } } } \ No newline at end of file diff --git a/src/WebApi.OutputCache.Core/Time/ShortTime.cs b/src/WebApi.OutputCache.Core/Time/ShortTime.cs index 58671c0..0a91dd3 100644 --- a/src/WebApi.OutputCache.Core/Time/ShortTime.cs +++ b/src/WebApi.OutputCache.Core/Time/ShortTime.cs @@ -6,8 +6,9 @@ public class ShortTime : IModelQuery { private readonly int serverTimeInSeconds; private readonly int clientTimeInSeconds; + private readonly int? sharedTimeInSecounds; - public ShortTime(int serverTimeInSeconds, int clientTimeInSeconds) + public ShortTime(int serverTimeInSeconds, int clientTimeInSeconds, int? sharedTimeInSecounds) { if (serverTimeInSeconds < 0) serverTimeInSeconds = 0; @@ -18,6 +19,11 @@ public ShortTime(int serverTimeInSeconds, int clientTimeInSeconds) clientTimeInSeconds = 0; this.clientTimeInSeconds = clientTimeInSeconds; + + if (sharedTimeInSecounds.HasValue && sharedTimeInSecounds.Value < 0) + sharedTimeInSecounds = 0; + + this.sharedTimeInSecounds = sharedTimeInSecounds; } public CacheTime Execute(DateTime model) @@ -25,7 +31,8 @@ public CacheTime Execute(DateTime model) var cacheTime = new CacheTime { AbsoluteExpiration = model.AddSeconds(serverTimeInSeconds), - ClientTimeSpan = TimeSpan.FromSeconds(clientTimeInSeconds) + ClientTimeSpan = TimeSpan.FromSeconds(clientTimeInSeconds), + SharedTimeSpan = sharedTimeInSecounds.HasValue ? (TimeSpan?) TimeSpan.FromSeconds(sharedTimeInSecounds.Value) : null }; return cacheTime; diff --git a/src/WebApi.OutputCache.V2/CacheOutputAttribute.cs b/src/WebApi.OutputCache.V2/CacheOutputAttribute.cs index ee6fbca..a45743e 100644 --- a/src/WebApi.OutputCache.V2/CacheOutputAttribute.cs +++ b/src/WebApi.OutputCache.V2/CacheOutputAttribute.cs @@ -5,6 +5,7 @@ using System.Net.Http.Formatting; using System.Net.Http.Headers; using System.Runtime.ExceptionServices; +using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -48,6 +49,23 @@ public class CacheOutputAttribute : ActionFilterAttribute /// public int ClientTimeSpan { get; set; } + + private int? _sharedTimeSpan = null; + + /// + /// Corresponds to CacheControl Shared MaxAge HTTP header (in seconds) + /// + public int SharedTimeSpan + { + get // required for property visibility + { + if (!_sharedTimeSpan.HasValue) + throw new Exception("should not be called without value set"); + return _sharedTimeSpan.Value; + } + set { _sharedTimeSpan = value; } + } + /// /// Corresponds to CacheControl NoCache HTTP header /// @@ -98,7 +116,7 @@ protected virtual void EnsureCacheTimeQuery() protected void ResetCacheTimeQuery() { - CacheTimeQuery = new ShortTime( ServerTimeSpan, ClientTimeSpan ); + CacheTimeQuery = new ShortTime( ServerTimeSpan, ClientTimeSpan, _sharedTimeSpan); } protected virtual MediaTypeHeaderValue GetExpectedMediaType(HttpConfiguration config, HttpActionContext actionContext) @@ -247,6 +265,7 @@ protected virtual void ApplyCacheHeaders(HttpResponseMessage response, CacheTime var cachecontrol = new CacheControlHeaderValue { MaxAge = cacheTime.ClientTimeSpan, + SharedMaxAge = cacheTime.SharedTimeSpan, MustRevalidate = MustRevalidate, Private = Private }; diff --git a/test/WebApi.OutputCache.V2.Tests/ClientSideTests.cs b/test/WebApi.OutputCache.V2.Tests/ClientSideTests.cs index 845b9ac..e4b8cc0 100644 --- a/test/WebApi.OutputCache.V2.Tests/ClientSideTests.cs +++ b/test/WebApi.OutputCache.V2.Tests/ClientSideTests.cs @@ -178,6 +178,22 @@ public void private_true_headers_correct() Assert.IsTrue(result.Headers.CacheControl.Private); } + [Test] + public void shared_max_age_header_correct() + { + var client = new HttpClient(_server); + var result = client.GetAsync(_url + "Get_c100_s100_sm200").Result; + Assert.AreEqual(result.Headers.CacheControl.SharedMaxAge,TimeSpan.FromSeconds(200)); + } + + [Test] + public void shared_max_age_header_not_present() + { + var client = new HttpClient(_server); + var result = client.GetAsync(_url + "Get_c100_s100").Result; + Assert.AreEqual(result.Headers.CacheControl.SharedMaxAge, null); + } + [TestFixtureTearDown] public void fixture_dispose() { diff --git a/test/WebApi.OutputCache.V2.Tests/TestControllers/SampleController.cs b/test/WebApi.OutputCache.V2.Tests/TestControllers/SampleController.cs index d2f54ff..645ad07 100644 --- a/test/WebApi.OutputCache.V2.Tests/TestControllers/SampleController.cs +++ b/test/WebApi.OutputCache.V2.Tests/TestControllers/SampleController.cs @@ -152,5 +152,12 @@ public IHttpActionResult Get_ihttpactionresult() { return Ok("value"); } + + [CacheOutput(ClientTimeSpan = 100, ServerTimeSpan = 100, SharedTimeSpan = 200)] + public string Get_c100_s100_sm200() + { + return "test"; + } + } }