From 253b06dfaa091d986a8714c081fd1e01679f538a Mon Sep 17 00:00:00 2001 From: crupest Date: Wed, 10 Feb 2021 02:03:06 +0800 Subject: ... --- .../Timeline/Helpers/Cache/CacheableDataDigest.cs | 16 +++++ BackEnd/Timeline/Helpers/Cache/DataCacheHelper.cs | 82 ++++++++++++++++++++++ .../Helpers/Cache/DelegateCacheableDataProvider.cs | 28 ++++++++ .../Timeline/Helpers/Cache/ICacheableDataDigest.cs | 10 +++ .../Helpers/Cache/ICacheableDataProvider.cs | 11 +++ 5 files changed, 147 insertions(+) create mode 100644 BackEnd/Timeline/Helpers/Cache/CacheableDataDigest.cs create mode 100644 BackEnd/Timeline/Helpers/Cache/DataCacheHelper.cs create mode 100644 BackEnd/Timeline/Helpers/Cache/DelegateCacheableDataProvider.cs create mode 100644 BackEnd/Timeline/Helpers/Cache/ICacheableDataDigest.cs create mode 100644 BackEnd/Timeline/Helpers/Cache/ICacheableDataProvider.cs (limited to 'BackEnd/Timeline/Helpers/Cache') diff --git a/BackEnd/Timeline/Helpers/Cache/CacheableDataDigest.cs b/BackEnd/Timeline/Helpers/Cache/CacheableDataDigest.cs new file mode 100644 index 00000000..3b5bcf52 --- /dev/null +++ b/BackEnd/Timeline/Helpers/Cache/CacheableDataDigest.cs @@ -0,0 +1,16 @@ +using System; + +namespace Timeline.Helpers.Cache +{ + public class CacheableDataDigest + { + public CacheableDataDigest(string eTag, DateTime lastModified) + { + ETag = eTag; + LastModified = lastModified; + } + + public string ETag { get; set; } + public DateTime LastModified { get; set; } + } +} diff --git a/BackEnd/Timeline/Helpers/Cache/DataCacheHelper.cs b/BackEnd/Timeline/Helpers/Cache/DataCacheHelper.cs new file mode 100644 index 00000000..c26bdddc --- /dev/null +++ b/BackEnd/Timeline/Helpers/Cache/DataCacheHelper.cs @@ -0,0 +1,82 @@ +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Net.Http.Headers; +using System; +using System.Linq; +using System.Threading.Tasks; +using Timeline.Models; +using Timeline.Models.Http; + +namespace Timeline.Helpers.Cache +{ + public static class DataCacheHelper + { + public static async Task GenerateActionResult(Controller controller, ICacheableDataProvider provider, TimeSpan? maxAge = null) + { + const string CacheControlHeaderKey = "Cache-Control"; + const string IfNonMatchHeaderKey = "If-None-Match"; + const string IfModifiedSinceHeaderKey = "If-Modified-Since"; + const string ETagHeaderKey = "ETag"; + const string LastModifiedHeaderKey = "Last-Modified"; + + string GenerateCacheControlHeaderValue() + { + var cacheControlHeader = new CacheControlHeaderValue() + { + NoCache = true, + NoStore = false, + MaxAge = maxAge ?? TimeSpan.FromDays(14), + Private = true, + MustRevalidate = true + }; + return cacheControlHeader.ToString(); + } + + var digest = await provider.GetDigest(); + var eTagValue = '"' + digest.ETag + '"'; + var eTag = new EntityTagHeaderValue(eTagValue); + + ActionResult Generate304Result() + { + controller.Response.Headers.Add(ETagHeaderKey, eTagValue); + controller.Response.Headers.Add(LastModifiedHeaderKey, digest.LastModified.ToString("R")); + controller.Response.Headers.Add(CacheControlHeaderKey, GenerateCacheControlHeaderValue()); + return controller.StatusCode(StatusCodes.Status304NotModified, null); + } + + if (controller.Request.Headers.TryGetValue(IfNonMatchHeaderKey, out var ifNonMatchHeaderValue)) + { + if (!EntityTagHeaderValue.TryParseList(ifNonMatchHeaderValue, out var eTagList)) + { + return controller.BadRequest(ErrorResponse.Common.Header.IfNonMatch_BadFormat()); + } + + if (eTagList.FirstOrDefault(e => e.Equals(eTag)) != null) + { + return Generate304Result(); + } + } + else if (controller.Request.Headers.TryGetValue(IfModifiedSinceHeaderKey, out var ifModifiedSinceHeaderValue)) + { + if (!DateTime.TryParse(ifModifiedSinceHeaderValue, out var headerValue)) + { + return controller.BadRequest(new CommonResponse(ErrorCodes.Common.Header.IfModifiedSince_BadFormat, "Header If-Modified-Since is of bad format.")); + } + + if (headerValue > digest.LastModified) + { + return Generate304Result(); + } + } + + var data = await provider.GetData(); + controller.Response.Headers.Add(CacheControlHeaderKey, GenerateCacheControlHeaderValue()); + return controller.File(data.Data, data.ContentType, digest.LastModified, eTag); + } + + public static Task GenerateActionResult(Controller controller, Func> getDigestDelegate, Func> getDataDelegate, TimeSpan? maxAge = null) + { + return GenerateActionResult(controller, new DelegateCacheableDataProvider(getDigestDelegate, getDataDelegate), maxAge); + } + } +} diff --git a/BackEnd/Timeline/Helpers/Cache/DelegateCacheableDataProvider.cs b/BackEnd/Timeline/Helpers/Cache/DelegateCacheableDataProvider.cs new file mode 100644 index 00000000..80cb66c7 --- /dev/null +++ b/BackEnd/Timeline/Helpers/Cache/DelegateCacheableDataProvider.cs @@ -0,0 +1,28 @@ +using System; +using System.Threading.Tasks; +using Timeline.Models; + +namespace Timeline.Helpers.Cache +{ + public class DelegateCacheableDataProvider : ICacheableDataProvider + { + private readonly Func> _getDigestDelegate; + private readonly Func> _getDataDelegate; + + public DelegateCacheableDataProvider(Func> getDigestDelegate, Func> getDataDelegate) + { + _getDigestDelegate = getDigestDelegate; + _getDataDelegate = getDataDelegate; + } + + public Task GetDigest() + { + return _getDigestDelegate(); + } + + public Task GetData() + { + return _getDataDelegate(); + } + } +} diff --git a/BackEnd/Timeline/Helpers/Cache/ICacheableDataDigest.cs b/BackEnd/Timeline/Helpers/Cache/ICacheableDataDigest.cs new file mode 100644 index 00000000..32519d7e --- /dev/null +++ b/BackEnd/Timeline/Helpers/Cache/ICacheableDataDigest.cs @@ -0,0 +1,10 @@ +using System; + +namespace Timeline.Helpers.Cache +{ + public interface ICacheableDataDigest + { + string ETag { get; } + DateTime LastModified { get; } + } +} diff --git a/BackEnd/Timeline/Helpers/Cache/ICacheableDataProvider.cs b/BackEnd/Timeline/Helpers/Cache/ICacheableDataProvider.cs new file mode 100644 index 00000000..b270fb1d --- /dev/null +++ b/BackEnd/Timeline/Helpers/Cache/ICacheableDataProvider.cs @@ -0,0 +1,11 @@ +using System.Threading.Tasks; +using Timeline.Models; + +namespace Timeline.Helpers.Cache +{ + public interface ICacheableDataProvider + { + Task GetDigest(); + Task GetData(); + } +} -- cgit v1.2.3