http-cache-semantics
Advanced tools
Comparing version 1.0.0 to 2.0.0
93
index.js
'use strict'; | ||
// rfc7231 6.1 | ||
const statusCodeCacheableByDefault = [200, 203, 204, 206, 300, 301, 404, 405, 410, 414, 501]; | ||
// This implementation does not understand partial responses (206) | ||
const understoodStatuses = [200, 204, 301, 302, 303, 404, 410, 501]; | ||
function parseCacheControl(header) { | ||
@@ -24,4 +30,7 @@ const cc = {}; | ||
if (!res || !res.headers) { | ||
throw Error("headers missing"); | ||
throw Error("Response headers missing"); | ||
} | ||
if (!req || !req.headers) { | ||
throw Error("Request headers missing"); | ||
} | ||
@@ -31,3 +40,5 @@ this._responseTime = this.now(); | ||
this._res = res; | ||
this._cc = parseCacheControl(res.headers['cache-control']); | ||
this._rescc = parseCacheControl(res.headers['cache-control']); | ||
this._req = req; | ||
this._reqcc = parseCacheControl(req.headers['cache-control']); | ||
@@ -37,3 +48,3 @@ // When the Cache-Control header field is not present in a request, caches MUST consider the no-cache request pragma-directive | ||
if (!res.headers['cache-control'] && /no-cache/.test(res.headers.pragma)) { | ||
this._cc['no-cache'] = true; | ||
this._rescc['no-cache'] = true; | ||
} | ||
@@ -47,2 +58,59 @@ } | ||
storable() { | ||
const status = (this._res.status === undefined) ? 200 : this._res.status; | ||
// The "no-store" request directive indicates that a cache MUST NOT store any part of either this request or any response to it. | ||
return !this._reqcc['no-store'] && | ||
// A cache MUST NOT store a response to any request, unless: | ||
// The request method is understood by the cache and defined as being cacheable, and | ||
(!this._req.method || 'GET' === this._req.method || 'HEAD' === this._req.method || ('POST' === this._req.method && this._hasExplicitExpiration())) && | ||
// the response status code is understood by the cache, and | ||
understoodStatuses.includes(status) && | ||
// the "no-store" cache directive does not appear in request or response header fields, and | ||
!this._rescc['no-store'] && | ||
// the "private" response directive does not appear in the response, if the cache is shared, and | ||
(!this._isShared || !this._rescc.private) && | ||
// the Authorization header field does not appear in the request, if the cache is shared, | ||
(!this._isShared || !this._req.headers['authorization'] || this._allowsStoringAuthenticated()) && | ||
// the response either: | ||
( | ||
// contains an Expires header field, or | ||
this._res.headers.expires || | ||
// contains a max-age response directive, or | ||
// contains a s-maxage response directive and the cache is shared, or | ||
// contains a public response directive. | ||
this._rescc.public || this._rescc['max-age'] || this._rescc['s-maxage'] || | ||
// has a status code that is defined as cacheable by default | ||
statusCodeCacheableByDefault.includes(status) | ||
); | ||
}, | ||
_hasExplicitExpiration() { | ||
// 4.2.1 Calculating Freshness Lifetime | ||
return (this._isShared && this._rescc['s-maxage']) || | ||
this._rescc['max-age'] || | ||
this._res.headers.expires; | ||
}, | ||
_allowsStoringAuthenticated() { | ||
// following Cache-Control response directives (Section 5.2.2) have such an effect: must-revalidate, public, and s-maxage. | ||
return this._rescc['must-revalidate'] || this._rescc['public'] || this._rescc['s-maxage']; | ||
}, | ||
_varyKeyForRequest(req) { | ||
if (!this._res.headers.vary) return ''; | ||
let key = ''; | ||
const fields = this._res.headers.vary.toLowerCase().split(/\s*,\s*/); | ||
fields.sort(); | ||
for(const name of fields) { | ||
key += `${name}:${req.headers[name] || '÷'}\n`; | ||
} | ||
return key; | ||
}, | ||
cacheKey() { | ||
return `${this._req.method || 'GET'} ${this._req.url || ''} ${this._varyKeyForRequest(this._req)}`; | ||
}, | ||
/** | ||
@@ -79,3 +147,3 @@ * Value of the Date response header or current time if Date was demed invalid | ||
maxAge() { | ||
if (this._cc['no-cache'] || this._cc['no-store']) { | ||
if (!this.storable() || this._rescc['no-cache']) { | ||
return 0; | ||
@@ -86,8 +154,7 @@ } | ||
// so this implementation requires explicit opt-in via public header | ||
if (this._isShared && (this._cc['private'] || (this._res.headers['set-cookie'] && !this._cc['public']))) { | ||
if (this._isShared && (this._res.headers['set-cookie'] && !this._rescc['public'])) { | ||
return 0; | ||
} | ||
// TODO: vary is not supported yet | ||
if (this._res.headers['vary']) { | ||
if (this._res.headers.vary === '*') { | ||
return 0; | ||
@@ -98,4 +165,4 @@ } | ||
// if a response includes the s-maxage directive, a shared cache recipient MUST ignore the Expires field. | ||
if (this._cc['s-maxage']) { | ||
return parseInt(this._cc['s-maxage'], 10); | ||
if (this._rescc['s-maxage']) { | ||
return parseInt(this._rescc['s-maxage'], 10); | ||
} | ||
@@ -105,4 +172,4 @@ } | ||
// If a response includes a Cache-Control field with the max-age directive, a recipient MUST ignore the Expires field. | ||
if (this._cc['max-age']) { | ||
return parseInt(this._cc['max-age'], 10); | ||
if (this._rescc['max-age']) { | ||
return parseInt(this._rescc['max-age'], 10); | ||
} | ||
@@ -129,4 +196,4 @@ | ||
isFresh() { | ||
return this.maxAge() > this.age(); | ||
stale() { | ||
return this.maxAge() <= this.age(); | ||
}, | ||
@@ -133,0 +200,0 @@ }; |
{ | ||
"name": "http-cache-semantics", | ||
"version": "1.0.0", | ||
"version": "2.0.0", | ||
"description": "Parses Cache-Control headers and friends", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -15,3 +15,3 @@ # HTTP cache semantics | ||
// Current state | ||
const currentState = cache.isFresh(); | ||
const outOfDate = cache.stale(); | ||
``` | ||
@@ -43,2 +43,14 @@ | ||
### `stale()` | ||
Returns `true` if the response is stale (i.e. not fresh). | ||
It generally means the response can't be used any more without revalidation with the server. However, there are exceptions, e.g. client can explicitly allow stale responses. A fresh response still may not be used if other conditions—such as `Vary`—are not satisfied. | ||
### `cacheKey()` | ||
Returns a string that is a combination of method, URL, and headers selected with `Vary`. | ||
Note that `Vary: *` never matches any request, so matching of cache keys alone is not sufficient to satisfy a request. | ||
## Implemented | ||
@@ -50,7 +62,7 @@ | ||
* `Age` response header | ||
* Default cacheability of statuses and methods | ||
* Basic support for `Vary` | ||
## Unimplemented | ||
* Request properties are not evaluated | ||
* `Vary` support is as lame and incomplete as in web browsers | ||
* No support for revalidation and stale responses |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
21582
7
418
66
1