make-fetch-happen
Advanced tools
Comparing version 2.0.4 to 2.1.0
@@ -5,2 +5,12 @@ # Change Log | ||
<a name="2.1.0"></a> | ||
# [2.1.0](https://github.com/zkat/make-fetch-happen/compare/v2.0.4...v2.1.0) (2017-04-09) | ||
### Features | ||
* **cache:** cache now obeys Age and a variety of other things (#13) ([7b9652d](https://github.com/zkat/make-fetch-happen/commit/7b9652d)) | ||
<a name="2.0.4"></a> | ||
@@ -7,0 +17,0 @@ ## [2.0.4](https://github.com/zkat/make-fetch-happen/compare/v2.0.3...v2.0.4) (2017-04-09) |
149
index.js
'use strict' | ||
let Cache | ||
const CachePolicy = require('http-cache-semantics') | ||
const fetch = require('node-fetch') | ||
@@ -97,6 +98,6 @@ const LRU = require('lru-cache') | ||
} | ||
if (res && opts.cache === 'default' && !isStale(res)) { | ||
if (res && opts.cache === 'default' && !isStale(req, res)) { | ||
return res | ||
} else if (res && (opts.cache === 'default' || opts.cache === 'no-cache')) { | ||
return condFetch(uri, res, opts) | ||
return condFetch(req, res, opts) | ||
} else if (res && ( | ||
@@ -116,3 +117,3 @@ opts.cache === 'force-cache' || opts.cache === 'only-if-cached' | ||
// Missing cache entry, or mode is default (if stale), reload, no-store | ||
return remoteFetch(uri, opts) | ||
return remoteFetch(req.url, opts) | ||
} | ||
@@ -125,90 +126,82 @@ }) | ||
// https://tools.ietf.org/html/rfc7234#section-4.2 | ||
function isStale (res) { | ||
if (!res) { return null } | ||
const ctrl = res.headers.get('Cache-Control') || '' | ||
if (ctrl.match(/must-revalidate/i)) { | ||
return true | ||
function adaptHeaders (headers) { | ||
const newHs = {} | ||
for (let k of headers.keys()) { | ||
newHs[k] = headers.get(k) | ||
} | ||
if (ctrl.match(/immutable/i)) { | ||
return false | ||
} | ||
const maxAge = freshnessLifetime(res) | ||
const currentAge = (new Date() - new Date(res.headers.get('Date') || new Date())) / 1000 | ||
return maxAge <= currentAge | ||
return newHs | ||
} | ||
// https://tools.ietf.org/html/rfc7234#section-4.2.1 | ||
function freshnessLifetime (res) { | ||
const cacheControl = res.headers.get('Cache-Control') || '' | ||
const pragma = res.headers.get('Pragma') || '' | ||
const maxAgeMatch = cacheControl.match(/(?:s-maxage|max-age)\s*=\s*(\d+)/i) | ||
const noCacheMatch = ( | ||
cacheControl.match(/no-cache/i) || | ||
pragma.match(/no-cache/i) | ||
) | ||
if (noCacheMatch) { | ||
// no-cache requires revalidation on every request | ||
return 0 | ||
} else if (maxAgeMatch) { | ||
return +maxAgeMatch[1] | ||
} else if (res.headers.get('Expires')) { | ||
const expireDate = new Date(res.headers.get('Expires')) | ||
const resDate = new Date(res.headers.get('Date') || new Date()) | ||
return (expireDate - resDate) / 1000 | ||
} else { | ||
return heuristicFreshness(res) | ||
function makePolicy (req, res) { | ||
const _req = { | ||
url: req.url, | ||
method: req.method, | ||
headers: adaptHeaders(req.headers) | ||
} | ||
const _res = { | ||
status: res.status, | ||
headers: adaptHeaders(res.headers) | ||
} | ||
return new CachePolicy(_req, _res) | ||
} | ||
// https://tools.ietf.org/html/rfc7234#section-4.2.2 | ||
function heuristicFreshness (res) { | ||
const lastMod = res.headers.get('Last-Modified') | ||
const date = new Date(res.headers.get('Date') || new Date()) | ||
!res.headers.get('Warning') && setWarning( | ||
res, 113, 'Used heuristics to calculate cache freshness' | ||
) | ||
if (lastMod) { | ||
const age = (date - new Date(lastMod)) / 1000 | ||
return Math.min(age * 0.1, 300) | ||
} else { | ||
return 300 | ||
// https://tools.ietf.org/html/rfc7234#section-4.2 | ||
function isStale (req, res) { | ||
if (!res) { return null } | ||
const _req = { | ||
url: req.url, | ||
method: req.method, | ||
headers: adaptHeaders(req.headers) | ||
} | ||
const bool = !makePolicy(req, res).satisfiesWithoutRevalidation(_req) | ||
return bool | ||
} | ||
function condFetch (uri, cachedRes, opts) { | ||
const ctrl = cachedRes.headers.get('cache-control') || '' | ||
const newHeaders = {} | ||
function mustRevalidate (res) { | ||
return (res.headers.get('cache-control') || '').match(/must-revalidate/i) | ||
} | ||
function condFetch (req, cachedRes, opts) { | ||
let newHeaders = {} | ||
Object.keys(opts.headers || {}).forEach(k => { | ||
newHeaders[k] = opts.headers[k] | ||
}) | ||
if (cachedRes.headers.get('etag')) { | ||
const condHeader = opts.method !== 'GET' | ||
? 'if-match' | ||
: 'if-none-match' | ||
newHeaders[condHeader] = cachedRes.headers.get('etag') | ||
const policy = makePolicy(req, cachedRes) | ||
const _req = { | ||
url: req.url, | ||
method: req.method, | ||
headers: newHeaders | ||
} | ||
if (cachedRes.headers.get('last-modified')) { | ||
const condHeader = opts.method !== 'GET' && opts.method !== 'HEAD' | ||
? 'if-unmodified-since' | ||
: 'if-modified-since' | ||
newHeaders[condHeader] = cachedRes.headers.get('last-modified') | ||
} | ||
opts.headers = newHeaders | ||
if (isStale(cachedRes)) { | ||
setWarning(cachedRes, 110, 'Local cached response stale') | ||
} | ||
return remoteFetch(uri, opts).then(condRes => { | ||
opts.headers = policy.revalidationHeaders(_req) | ||
return remoteFetch(req.url, opts).then(condRes => { | ||
const revaled = policy.revalidatedPolicy(_req, { | ||
status: condRes.status, | ||
headers: adaptHeaders(condRes.headers) | ||
}) | ||
if (condRes.status === 304) { | ||
condRes.body = cachedRes.body | ||
condRes.headers.set('Warning', cachedRes.headers.get('Warning')) | ||
} else if (condRes.status >= 500 && !ctrl.match(/must-revalidate/i)) { | ||
return opts.cacheManager.put(req, condRes, opts).then(newRes => { | ||
newRes.headers = new fetch.Headers(revaled.policy.responseHeaders()) | ||
if (revaled.modified) { | ||
setWarning(newRes, 110, 'Revalidation failed even with 304 response. Using stale body with new headers.') | ||
} else { | ||
setWarning(newRes, 110, 'Local cached response stale') | ||
} | ||
return newRes | ||
}) | ||
} else if ( | ||
condRes.status >= 500 && | ||
!mustRevalidate(cachedRes) | ||
) { | ||
setWarning( | ||
cachedRes, 111, `Revalidation failed. Returning stale response` | ||
cachedRes, 111, `Revalidation failed with status ${condRes.status}. Returning stale response` | ||
) | ||
return cachedRes | ||
} else { | ||
return condRes | ||
} | ||
return condRes | ||
}).then(res => { | ||
return res | ||
}).catch(err => { | ||
if (ctrl.match(/must-revalidate/i)) { | ||
if (mustRevalidate(cachedRes)) { | ||
throw err | ||
@@ -222,5 +215,5 @@ } else { | ||
function setWarning (reqOrRes, code, message, host) { | ||
function setWarning (reqOrRes, code, message, host, append) { | ||
host = host || 'localhost' | ||
reqOrRes.headers.set( | ||
reqOrRes.headers[append ? 'append' : 'set']( | ||
'Warning', | ||
@@ -237,3 +230,3 @@ `${code} ${host} ${ | ||
const agent = getAgent(uri, opts) | ||
const headers = { | ||
let headers = { | ||
'connection': agent ? 'keep-alive' : 'close', | ||
@@ -247,2 +240,3 @@ 'user-agent': `${pkg.name}/${pkg.version} (+https://npm.im/${pkg.name})` | ||
} | ||
headers = new fetch.Headers(headers) | ||
const reqOpts = { | ||
@@ -277,10 +271,9 @@ agent, | ||
} | ||
const cacheCtrl = res.headers.get('cache-control') || '' | ||
if ( | ||
(req.method === 'GET' || req.method === 'HEAD') && | ||
opts.cacheManager && | ||
!cacheCtrl.match(/no-store/i) && | ||
opts.cache !== 'no-store' && | ||
(req.method === 'GET' || req.method === 'HEAD') && | ||
makePolicy(req, res).storable() && | ||
// No other statuses should be stored! | ||
(res.status === 200 || res.status === 304) | ||
res.status === 200 | ||
) { | ||
@@ -287,0 +280,0 @@ return opts.cacheManager.put(req, res, opts) |
{ | ||
"name": "make-fetch-happen", | ||
"version": "2.0.4", | ||
"version": "2.1.0", | ||
"description": "Opinionated, caching, retrying fetch client", | ||
@@ -45,2 +45,3 @@ "main": "index.js", | ||
"https-proxy-agent": "^1.0.0", | ||
"http-cache-semantics": "^3.7.3", | ||
"lru-cache": "^4.0.2", | ||
@@ -47,0 +48,0 @@ "mississippi": "^1.2.0", |
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
Network access
Supply chain riskThis module accesses the network.
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
501688
14
7121
35
+ Addedhttp-cache-semantics@^3.7.3
+ Addedhttp-cache-semantics@3.8.1(transitive)