Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
9 changes: 6 additions & 3 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,16 +54,19 @@ Frequency of updates to data is relatively low compared to reads
```mermaid
graph TD
Request
NoCache{Has no-cache<br/>in Cache-Control?}
HasMaxAge{Has max-age<br/>or max-stale<br/>in Cache-Control?}
CacheValid{Cached timestamp<br/>within allowed<br/>staleness?}
CacheValid{Cached timestamp<br/>within allowed<br/>staleness?<br/>Considers min-fresh}
FreshTimestamp[Get fresh timestamp<br/>from SQL]
UseCached[Use cached timestamp]
CalculateEtag[Calculate current ETag<br/>based on timestamp<br/>from web assembly and SQL]
IfNoneMatch{Has<br/>If-None-Match<br/>header?}
EtagMatch{Current<br/>Etag matches<br/>If-None-Match?}
AddETag[Add current ETag<br/>to Response headers]
304[Respond with<br/>304 Not-Modified]
Request --> HasMaxAge
Request --> NoCache
NoCache -->|Yes| FreshTimestamp
NoCache -->|No| HasMaxAge
HasMaxAge -->|Yes| CacheValid
HasMaxAge -->|No| FreshTimestamp
CacheValid -->|Yes| UseCached
Expand Down Expand Up @@ -145,7 +148,7 @@ internal static string BuildEtag(string timeStamp, string? suffix)
return $"\"{AssemblyWriteTime}-{timeStamp}-{suffix}\"";
}
```
<sup><a href='/src/Delta/DeltaExtensions_Shared.cs#L285-L297' title='Snippet source file'>snippet source</a> | <a href='#snippet-BuildEtag' title='Start of snippet'>anchor</a></sup>
<sup><a href='/src/Delta/DeltaExtensions_Shared.cs#L326-L338' title='Snippet source file'>snippet source</a> | <a href='#snippet-BuildEtag' title='Start of snippet'>anchor</a></sup>
<!-- endSnippet -->


Expand Down
7 changes: 5 additions & 2 deletions readme.source.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,19 @@ Frequency of updates to data is relatively low compared to reads
```mermaid
graph TD
Request
NoCache{Has no-cache<br/>in Cache-Control?}
HasMaxAge{Has max-age<br/>or max-stale<br/>in Cache-Control?}
CacheValid{Cached timestamp<br/>within allowed<br/>staleness?}
CacheValid{Cached timestamp<br/>within allowed<br/>staleness?<br/>Considers min-fresh}
FreshTimestamp[Get fresh timestamp<br/>from SQL]
UseCached[Use cached timestamp]
CalculateEtag[Calculate current ETag<br/>based on timestamp<br/>from web assembly and SQL]
IfNoneMatch{Has<br/>If-None-Match<br/>header?}
EtagMatch{Current<br/>Etag matches<br/>If-None-Match?}
AddETag[Add current ETag<br/>to Response headers]
304[Respond with<br/>304 Not-Modified]
Request --> HasMaxAge
Request --> NoCache
NoCache -->|Yes| FreshTimestamp
NoCache -->|No| HasMaxAge
HasMaxAge -->|Yes| CacheValid
HasMaxAge -->|No| FreshTimestamp
CacheValid -->|Yes| UseCached
Expand Down
55 changes: 48 additions & 7 deletions src/Delta/DeltaExtensions_Shared.cs
Original file line number Diff line number Diff line change
Expand Up @@ -169,24 +169,36 @@ sealed record TimeStampCache(string Value, long Ticks);
static bool TryGetCachedTimeStamp(HttpRequest request, [NotNullWhen(true)] out string? timeStamp)
{
timeStamp = null;
var cacheControl = request.Headers.CacheControl;

if (HasDirective(cacheControl, "no-cache"))
{
return false;
}

var cache = timeStampCache;
if (cache is null)
{
return false;
}

if (!TryParseStaleness(request.Headers.CacheControl, out var maxSeconds))
if (!TryParseStaleness(cacheControl, out var maxSeconds))
{
return false;
}

if (maxSeconds != int.MaxValue)
var elapsed = Stopwatch.GetElapsedTime(cache.Ticks);

if (maxSeconds != int.MaxValue &&
elapsed.TotalSeconds > maxSeconds)
{
var elapsed = Stopwatch.GetElapsedTime(cache.Ticks);
if (elapsed.TotalSeconds > maxSeconds)
{
return false;
}
return false;
}

if (TryParseDirectiveValue(cacheControl, "min-fresh=", out var minFresh) &&
elapsed.TotalSeconds + minFresh > maxSeconds)
{
return false;
}

timeStamp = cache.Value;
Expand Down Expand Up @@ -261,6 +273,35 @@ static bool TryParseDirectiveValue(string header, string directive, out int seco
return int.TryParse(span[..end], out seconds);
}

static bool HasDirective(StringValues cacheControl, string directive)
{
foreach (var value in cacheControl)
{
if (value is not null &&
value.Contains(directive, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}

return false;
}

static bool TryParseDirectiveValue(StringValues cacheControl, string directive, out int seconds)
{
foreach (var value in cacheControl)
{
if (value is not null &&
TryParseDirectiveValue(value, directive, out seconds))
{
return true;
}
}

seconds = 0;
return false;
}

[LoggerMessage(Message = "Delta {path}: Using cached timestamp")]
static partial void LogTimeStampCacheHit(ILogger logger, LogLevel level, string path);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
target: {
notModified: false,
context: {
Request: {
Headers: {
Cache-Control: min-fresh=5
}
},
Response: {
StatusCode: OK,
Headers: {
Delta-No304: Request method is not GET
}
}
}
},
log: {
Information: Delta /path: No 304. Request method is not GET,
EventId: {
Name: LogNo304
},
State: [
{
path: /path
},
{
reason: Request method is not GET
},
{
{OriginalFormat}: Delta {path}: No 304. {reason}
}
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
target: {
notModified: false,
context: {
Request: {
Headers: {
Cache-Control: no-cache, min-fresh=5
}
},
Response: {
StatusCode: OK,
Headers: {
Delta-No304: Request method is not GET
}
}
}
},
log: {
Information: Delta /path: No 304. Request method is not GET,
EventId: {
Name: LogNo304
},
State: [
{
path: /path
},
{
reason: Request method is not GET
},
{
{OriginalFormat}: Delta {path}: No 304. {reason}
}
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
target: {
notModified: false,
context: {
Request: {
Headers: {
Cache-Control: min-fresh=5
}
},
Response: {
StatusCode: OK,
Headers: {
Cache-Control: public, max-age=31536000, immutable,
Delta-No304: Request method is not GET
}
}
}
},
log: {
Information: Delta /path: No 304. Request method is not GET,
EventId: {
Name: LogNo304
},
State: [
{
path: /path
},
{
reason: Request method is not GET
},
{
{OriginalFormat}: Delta {path}: No 304. {reason}
}
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
target: {
notModified: false,
context: {
Request: {
Headers: {
Cache-Control: no-cache, min-fresh=5
}
},
Response: {
StatusCode: OK,
Headers: {
Cache-Control: public, max-age=31536000, immutable,
Delta-No304: Request method is not GET
}
}
}
},
log: {
Information: Delta /path: No 304. Request method is not GET,
EventId: {
Name: LogNo304
},
State: [
{
path: /path
},
{
reason: Request method is not GET
},
{
{OriginalFormat}: Delta {path}: No 304. {reason}
}
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
target: {
notModified: false,
context: {
Request: {
Headers: {
Cache-Control: min-fresh=5
}
},
Response: {
StatusCode: OK,
Headers: {
Delta-No304: Request method is not GET
}
}
}
},
log: {
Information: Delta /path: No 304. Request method is not GET,
EventId: {
Name: LogNo304
},
State: [
{
path: /path
},
{
reason: Request method is not GET
},
{
{OriginalFormat}: Delta {path}: No 304. {reason}
}
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
target: {
notModified: false,
context: {
Request: {
Headers: {
Cache-Control: no-cache, min-fresh=5
}
},
Response: {
StatusCode: OK,
Headers: {
Delta-No304: Request method is not GET
}
}
}
},
log: {
Information: Delta /path: No 304. Request method is not GET,
EventId: {
Name: LogNo304
},
State: [
{
path: /path
},
{
reason: Request method is not GET
},
{
{OriginalFormat}: Delta {path}: No 304. {reason}
}
]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
target: {
notModified: false,
context: {
Request: {
Headers: {
Cache-Control: min-fresh=5
}
},
Response: {
StatusCode: OK,
Headers: {
Cache-Control: public, max-age=31536000, immutable,
Delta-No304: Request method is not GET
}
}
}
},
log: {
Information: Delta /path: No 304. Request method is not GET,
EventId: {
Name: LogNo304
},
State: [
{
path: /path
},
{
reason: Request method is not GET
},
{
{OriginalFormat}: Delta {path}: No 304. {reason}
}
]
}
}
Loading
Loading