-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Consumer Api: Support Http ETags and If-None-Match Header (#1062)
* feat: Support ETag and If-None-Match header * chore: Add Header to Bruno request * fix: Enquote hashed string to comply with schema * feat: Add CachedApiResponse and modify GET /Tags endpoint in SDK * chore: Add integration test for cached GET /Tags * chore: Remove TODO (resolved) * chore: Address comments * chore: Typo * chore: Use Tag list parameter * feat: Add custom filter for easier caching * chore: use new custom filter * chore: Remove TODO * chore: Remove unused MD5 hasher * chore: Write comment referring to the StackOverflow answer and simplify the header removal * fix: Invert filter --------- Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
- Loading branch information
1 parent
8fd8a24
commit cd3612e
Showing
9 changed files
with
192 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,3 +9,7 @@ get { | |
body: none | ||
auth: none | ||
} | ||
|
||
headers { | ||
If-None-Match: lvcnNwFiRx633qFRBMM5ew== | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
78 changes: 78 additions & 0 deletions
78
BuildingBlocks/src/BuildingBlocks.API/Mvc/ControllerAttributes/HandleHttpCachingAttribute.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
using System.Security.Cryptography; | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.AspNetCore.Http.Headers; | ||
using Microsoft.AspNetCore.Mvc.Filters; | ||
using Microsoft.Net.Http.Headers; | ||
|
||
namespace Backbone.BuildingBlocks.API.Mvc.ControllerAttributes; | ||
|
||
/** | ||
* This filter handles Caching via the <c>If-None-Match</c> header in the request header | ||
* and the <c>ETag</c> in the response header. This class is based on this StackOverflow answer: | ||
* <see href="https://stackoverflow.com/a/76151167"></see> | ||
*/ | ||
public class HandleHttpCachingAttribute : ResultFilterAttribute | ||
{ | ||
private static readonly string[] HEADERS_TO_KEEP_FOR304 = | ||
[ | ||
HeaderNames.CacheControl, | ||
HeaderNames.ContentLocation, | ||
HeaderNames.ETag, | ||
HeaderNames.Expires, | ||
HeaderNames.Vary | ||
]; | ||
|
||
public override async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next) | ||
{ | ||
var request = context.HttpContext.Request; | ||
var response = context.HttpContext.Response; | ||
|
||
var originalStream = response.Body; | ||
using MemoryStream memoryStream = new(); | ||
|
||
response.Body = memoryStream; | ||
await next(); | ||
memoryStream.Position = 0; | ||
|
||
if (response.StatusCode == StatusCodes.Status200OK) | ||
{ | ||
var requestHeaders = request.GetTypedHeaders(); | ||
var responseHeaders = response.GetTypedHeaders(); | ||
|
||
responseHeaders.ETag ??= GenerateETag(memoryStream); | ||
|
||
if (IsClientCacheValid(requestHeaders, responseHeaders)) | ||
{ | ||
response.StatusCode = StatusCodes.Status304NotModified; | ||
|
||
foreach (var header in response.Headers.Where(h => !HEADERS_TO_KEEP_FOR304.Contains(h.Key))) | ||
response.Headers.Remove(header.Key); | ||
|
||
return; | ||
} | ||
} | ||
|
||
await memoryStream.CopyToAsync(originalStream); | ||
} | ||
|
||
private static EntityTagHeaderValue GenerateETag(Stream stream) | ||
{ | ||
var hashBytes = MD5.HashData(stream); | ||
stream.Position = 0; | ||
var hashString = Convert.ToBase64String(hashBytes); | ||
return new EntityTagHeaderValue($"\"{hashString}\""); | ||
} | ||
|
||
private static bool IsClientCacheValid(RequestHeaders reqHeaders, ResponseHeaders resHeaders) | ||
{ | ||
if (reqHeaders.IfNoneMatch.Any() && resHeaders.ETag is not null) | ||
return reqHeaders.IfNoneMatch.Any(etag => | ||
etag.Compare(resHeaders.ETag, useStrongComparison: false) | ||
); | ||
|
||
if (reqHeaders.IfModifiedSince is not null && resHeaders.LastModified is not null) | ||
return reqHeaders.IfModifiedSince >= resHeaders.LastModified; | ||
|
||
return false; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
6 changes: 6 additions & 0 deletions
6
BuildingBlocks/src/BuildingBlocks.SDK/Endpoints/Common/Types/CacheControl.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
namespace Backbone.BuildingBlocks.SDK.Endpoints.Common.Types; | ||
|
||
public class CacheControl | ||
{ | ||
public required string ETag { get; set; } | ||
} |
8 changes: 8 additions & 0 deletions
8
BuildingBlocks/src/BuildingBlocks.SDK/Endpoints/Common/Types/CachedApiResponse.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
namespace Backbone.BuildingBlocks.SDK.Endpoints.Common.Types; | ||
|
||
public class CachedApiResponse<TResult> : ApiResponse<TResult> | ||
{ | ||
public bool NotModified => Result == null; | ||
|
||
public string ETag { get; set; } = string.Empty; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters