Skip to content

Commit ca485f2

Browse files
committed
Option to usefully cheat on max-age calculation against the spec
1 parent 9ce9e12 commit ca485f2

File tree

4 files changed

+38
-7
lines changed

4 files changed

+38
-7
lines changed

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ const options = {
6161
cacheHeuristic: 0.1,
6262
immutableMinTimeToLive: 24*3600*1000, // 24h
6363
ignoreCargoCult: false,
64+
trustServerDate: true,
6465
};
6566
```
6667

@@ -72,6 +73,8 @@ If `options.shared` is `true` (default), then the response is evaluated from a p
7273

7374
If `options.ignoreCargoCult` is true, common anti-cache directives will be completely ignored if the non-standard `pre-check` and `post-check` directives are present. These two useless directives are most commonly found in bad StackOverflow answers and PHP's "session limiter" defaults.
7475

76+
If `options.trustServerDate` is false, then server's `Date` header won't be used as the base for `max-age`. This is against the RFC, but it's useful if you want to cache responses with very short `max-age`, but your local clock is not exactly in sync with the server's.
77+
7578
### `storable()`
7679

7780
Returns `true` if the response can be stored in a cache. If it's `false` then you MUST NOT store either the request or the response.

index.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ function formatCacheControl(cc) {
4343
}
4444

4545
module.exports = class CachePolicy {
46-
constructor(req, res, {shared, cacheHeuristic, immutableMinTimeToLive, ignoreCargoCult, _fromObject} = {}) {
46+
constructor(req, res, {shared, cacheHeuristic, immutableMinTimeToLive, ignoreCargoCult, trustServerDate, _fromObject} = {}) {
4747
if (_fromObject) {
4848
this._fromObject(_fromObject);
4949
return;
@@ -56,6 +56,7 @@ module.exports = class CachePolicy {
5656

5757
this._responseTime = this.now();
5858
this._isShared = shared !== false;
59+
this._trustServerDate = undefined !== trustServerDate ? trustServerDate : true;
5960
this._cacheHeuristic = undefined !== cacheHeuristic ? cacheHeuristic : 0.1; // 10% matches IE
6061
this._immutableMinTtl = undefined !== immutableMinTimeToLive ? immutableMinTimeToLive : 24*3600*1000;
6162

@@ -241,6 +242,13 @@ module.exports = class CachePolicy {
241242
* @return timestamp
242243
*/
243244
date() {
245+
if (this._trustServerDate) {
246+
return this._serverDate();
247+
}
248+
return this._responseTime;
249+
}
250+
251+
_serverDate() {
244252
const dateValue = Date.parse(this._resHeaders.date)
245253
if (isFinite(dateValue)) {
246254
const maxClockDrift = 8*3600*1000;
@@ -313,7 +321,7 @@ module.exports = class CachePolicy {
313321

314322
const defaultMinTtl = this._rescc.immutable ? this._immutableMinTtl : 0;
315323

316-
const dateValue = this.date();
324+
const dateValue = this._serverDate();
317325
if (this._resHeaders.expires) {
318326
const expires = Date.parse(this._resHeaders.expires);
319327
// A cache recipient MUST interpret invalid date formats, especially the value "0", as representing a time in the past (i.e., "already expired").

test/misctest.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@ describe('Other', function() {
3131
});
3232
});
3333

34-
it('GitHub response', function() {
34+
it('GitHub response with small clock skew', function() {
3535
const res = {
3636
headers: {
3737
server: 'GitHub.com',
38-
date: new Date().toUTCString(),
38+
date: new Date(Date.now()-77*1000).toUTCString(),
3939
'content-type': 'application/json; charset=utf-8',
4040
'transfer-encoding': 'chunked',
4141
connection: 'close',
@@ -69,7 +69,8 @@ describe('Other', function() {
6969
};
7070

7171
const c = new CachePolicy(req, res, {
72-
shared: false
72+
shared: false,
73+
trustServerDate: false,
7374
});
7475
assert(c.satisfiesWithoutRevalidation(req));
7576
})

test/responsetest.js

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,14 +87,33 @@ describe('Response headers', function() {
8787
});
8888

8989
it('cache with expires', function() {
90+
const now = Date.now();
9091
const cache = new CachePolicy(req, {headers:{
91-
'date': new Date().toGMTString(),
92-
'expires': new Date(Date.now() + 2000).toGMTString(),
92+
'date': new Date(now).toGMTString(),
93+
'expires': new Date(now + 2000).toGMTString(),
9394
}});
9495
assert(!cache.stale());
9596
assert.equal(2, cache.maxAge());
9697
});
9798

99+
it('cache with expires relative to date', function() {
100+
const now = Date.now();
101+
const cache = new CachePolicy(req, {headers:{
102+
'date': new Date(now-3000).toGMTString(),
103+
'expires': new Date(now).toGMTString(),
104+
}});
105+
assert.equal(3, cache.maxAge());
106+
});
107+
108+
it('cache with expires always relative to date', function() {
109+
const now = Date.now();
110+
const cache = new CachePolicy(req, {headers:{
111+
'date': new Date(now-3000).toGMTString(),
112+
'expires': new Date(now).toGMTString(),
113+
}},{trustServerDate:false});
114+
assert.equal(3, cache.maxAge());
115+
});
116+
98117
it('cache expires no date', function() {
99118
const cache = new CachePolicy(req, {headers:{
100119
'cache-control': 'public',

0 commit comments

Comments
 (0)