Skip to content

Commit

Permalink
Option to usefully cheat on max-age calculation against the spec
Browse files Browse the repository at this point in the history
  • Loading branch information
kornelski committed Apr 21, 2018
1 parent 9ce9e12 commit ca485f2
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 7 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ const options = {
cacheHeuristic: 0.1,
immutableMinTimeToLive: 24*3600*1000, // 24h
ignoreCargoCult: false,
trustServerDate: true,
};
```

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

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.

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.

### `storable()`

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.
Expand Down
12 changes: 10 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ function formatCacheControl(cc) {
}

module.exports = class CachePolicy {
constructor(req, res, {shared, cacheHeuristic, immutableMinTimeToLive, ignoreCargoCult, _fromObject} = {}) {
constructor(req, res, {shared, cacheHeuristic, immutableMinTimeToLive, ignoreCargoCult, trustServerDate, _fromObject} = {}) {
if (_fromObject) {
this._fromObject(_fromObject);
return;
Expand All @@ -56,6 +56,7 @@ module.exports = class CachePolicy {

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

Expand Down Expand Up @@ -241,6 +242,13 @@ module.exports = class CachePolicy {
* @return timestamp
*/
date() {
if (this._trustServerDate) {
return this._serverDate();
}
return this._responseTime;
}

_serverDate() {
const dateValue = Date.parse(this._resHeaders.date)
if (isFinite(dateValue)) {
const maxClockDrift = 8*3600*1000;
Expand Down Expand Up @@ -313,7 +321,7 @@ module.exports = class CachePolicy {

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

const dateValue = this.date();
const dateValue = this._serverDate();
if (this._resHeaders.expires) {
const expires = Date.parse(this._resHeaders.expires);
// A cache recipient MUST interpret invalid date formats, especially the value "0", as representing a time in the past (i.e., "already expired").
Expand Down
7 changes: 4 additions & 3 deletions test/misctest.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@ describe('Other', function() {
});
});

it('GitHub response', function() {
it('GitHub response with small clock skew', function() {
const res = {
headers: {
server: 'GitHub.com',
date: new Date().toUTCString(),
date: new Date(Date.now()-77*1000).toUTCString(),
'content-type': 'application/json; charset=utf-8',
'transfer-encoding': 'chunked',
connection: 'close',
Expand Down Expand Up @@ -69,7 +69,8 @@ describe('Other', function() {
};

const c = new CachePolicy(req, res, {
shared: false
shared: false,
trustServerDate: false,
});
assert(c.satisfiesWithoutRevalidation(req));
})
Expand Down
23 changes: 21 additions & 2 deletions test/responsetest.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,33 @@ describe('Response headers', function() {
});

it('cache with expires', function() {
const now = Date.now();
const cache = new CachePolicy(req, {headers:{
'date': new Date().toGMTString(),
'expires': new Date(Date.now() + 2000).toGMTString(),
'date': new Date(now).toGMTString(),
'expires': new Date(now + 2000).toGMTString(),
}});
assert(!cache.stale());
assert.equal(2, cache.maxAge());
});

it('cache with expires relative to date', function() {
const now = Date.now();
const cache = new CachePolicy(req, {headers:{
'date': new Date(now-3000).toGMTString(),
'expires': new Date(now).toGMTString(),
}});
assert.equal(3, cache.maxAge());
});

it('cache with expires always relative to date', function() {
const now = Date.now();
const cache = new CachePolicy(req, {headers:{
'date': new Date(now-3000).toGMTString(),
'expires': new Date(now).toGMTString(),
}},{trustServerDate:false});
assert.equal(3, cache.maxAge());
});

it('cache expires no date', function() {
const cache = new CachePolicy(req, {headers:{
'cache-control': 'public',
Expand Down

0 comments on commit ca485f2

Please sign in to comment.