http-cache-semantics
Advanced tools
Comparing version 4.0.2 to 4.0.3
282
index.js
'use strict'; | ||
// rfc7231 6.1 | ||
const statusCodeCacheableByDefault = [200, 203, 204, 206, 300, 301, 404, 405, 410, 414, 501]; | ||
const statusCodeCacheableByDefault = [ | ||
200, | ||
203, | ||
204, | ||
206, | ||
300, | ||
301, | ||
404, | ||
405, | ||
410, | ||
414, | ||
501, | ||
]; | ||
// This implementation does not understand partial responses (206) | ||
const understoodStatuses = [200, 203, 204, 300, 301, 302, 303, 307, 308, 404, 405, 410, 414, 501]; | ||
const understoodStatuses = [ | ||
200, | ||
203, | ||
204, | ||
300, | ||
301, | ||
302, | ||
303, | ||
307, | ||
308, | ||
404, | ||
405, | ||
410, | ||
414, | ||
501, | ||
]; | ||
const hopByHopHeaders = { | ||
'date': true, // included, because we add Age update Date | ||
'connection':true, 'keep-alive':true, 'proxy-authenticate':true, 'proxy-authorization':true, 'te':true, 'trailer':true, 'transfer-encoding':true, 'upgrade':true | ||
date: true, // included, because we add Age update Date | ||
connection: true, | ||
'keep-alive': true, | ||
'proxy-authenticate': true, | ||
'proxy-authorization': true, | ||
te: true, | ||
trailer: true, | ||
'transfer-encoding': true, | ||
upgrade: true, | ||
}; | ||
const excludedFromRevalidationUpdate = { | ||
// Since the old body is reused, it doesn't make sense to change properties of the body | ||
'content-length': true, 'content-encoding': true, 'transfer-encoding': true, | ||
'content-length': true, | ||
'content-encoding': true, | ||
'transfer-encoding': true, | ||
'content-range': true, | ||
@@ -25,5 +61,5 @@ }; | ||
const parts = header.trim().split(/\s*,\s*/); // TODO: lame parsing | ||
for(const part of parts) { | ||
const [k,v] = part.split(/\s*=\s*/, 2); | ||
cc[k] = (v === undefined) ? true : v.replace(/^"|"$/g, ''); // TODO: lame unquoting | ||
for (const part of parts) { | ||
const [k, v] = part.split(/\s*=\s*/, 2); | ||
cc[k] = v === undefined ? true : v.replace(/^"|"$/g, ''); // TODO: lame unquoting | ||
} | ||
@@ -36,3 +72,3 @@ | ||
let parts = []; | ||
for(const k in cc) { | ||
for (const k in cc) { | ||
const v = cc[k]; | ||
@@ -48,3 +84,14 @@ parts.push(v === true ? k : k + '=' + v); | ||
module.exports = class CachePolicy { | ||
constructor(req, res, {shared, cacheHeuristic, immutableMinTimeToLive, ignoreCargoCult, trustServerDate, _fromObject} = {}) { | ||
constructor( | ||
req, | ||
res, | ||
{ | ||
shared, | ||
cacheHeuristic, | ||
immutableMinTimeToLive, | ||
ignoreCargoCult, | ||
trustServerDate, | ||
_fromObject, | ||
} = {} | ||
) { | ||
if (_fromObject) { | ||
@@ -56,3 +103,3 @@ this._fromObject(_fromObject); | ||
if (!res || !res.headers) { | ||
throw Error("Response headers missing"); | ||
throw Error('Response headers missing'); | ||
} | ||
@@ -63,5 +110,10 @@ this._assertRequestHasHeaders(req); | ||
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; | ||
this._trustServerDate = | ||
undefined !== trustServerDate ? trustServerDate : true; | ||
this._cacheHeuristic = | ||
undefined !== cacheHeuristic ? cacheHeuristic : 0.1; // 10% matches IE | ||
this._immutableMinTtl = | ||
undefined !== immutableMinTimeToLive | ||
? immutableMinTimeToLive | ||
: 24 * 3600 * 1000; | ||
@@ -80,3 +132,7 @@ this._status = 'status' in res ? res.status : 200; | ||
// so there's no point stricly adhering to the blindly copy&pasted directives. | ||
if (ignoreCargoCult && "pre-check" in this._rescc && "post-check" in this._rescc) { | ||
if ( | ||
ignoreCargoCult && | ||
'pre-check' in this._rescc && | ||
'post-check' in this._rescc | ||
) { | ||
delete this._rescc['pre-check']; | ||
@@ -87,3 +143,5 @@ delete this._rescc['post-check']; | ||
delete this._rescc['must-revalidate']; | ||
this._resHeaders = Object.assign({}, this._resHeaders, {'cache-control': formatCacheControl(this._rescc)}); | ||
this._resHeaders = Object.assign({}, this._resHeaders, { | ||
'cache-control': formatCacheControl(this._rescc), | ||
}); | ||
delete this._resHeaders.expires; | ||
@@ -95,3 +153,6 @@ delete this._resHeaders.pragma; | ||
// as having the same effect as if "Cache-Control: no-cache" were present (see Section 5.2.1). | ||
if (!res.headers['cache-control'] && /no-cache/.test(res.headers.pragma)) { | ||
if ( | ||
res.headers['cache-control'] == null && | ||
/no-cache/.test(res.headers.pragma) | ||
) { | ||
this._rescc['no-cache'] = true; | ||
@@ -107,6 +168,9 @@ } | ||
// 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'] && | ||
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 | ||
('GET' === this._method || 'HEAD' === this._method || ('POST' === this._method && this._hasExplicitExpiration())) && | ||
('GET' === this._method || | ||
'HEAD' === this._method || | ||
('POST' === this._method && this._hasExplicitExpiration())) && | ||
// the response status code is understood by the cache, and | ||
@@ -119,14 +183,17 @@ understoodStatuses.indexOf(this._status) !== -1 && | ||
// the Authorization header field does not appear in the request, if the cache is shared, | ||
(!this._isShared || this._noAuthorization || this._allowsStoringAuthenticated()) && | ||
(!this._isShared || | ||
this._noAuthorization || | ||
this._allowsStoringAuthenticated()) && | ||
// the response either: | ||
( | ||
// contains an Expires header field, or | ||
this._resHeaders.expires || | ||
// contains an Expires header field, or | ||
(this._resHeaders.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'] || | ||
this._rescc.public || | ||
this._rescc['max-age'] || | ||
this._rescc['s-maxage'] || | ||
// has a status code that is defined as cacheable by default | ||
statusCodeCacheableByDefault.indexOf(this._status) !== -1 | ||
)); | ||
statusCodeCacheableByDefault.indexOf(this._status) !== -1) | ||
); | ||
} | ||
@@ -136,5 +203,7 @@ | ||
// 4.2.1 Calculating Freshness Lifetime | ||
return (this._isShared && this._rescc['s-maxage']) || | ||
return ( | ||
(this._isShared && this._rescc['s-maxage']) || | ||
this._rescc['max-age'] || | ||
this._resHeaders.expires; | ||
this._resHeaders.expires | ||
); | ||
} | ||
@@ -144,3 +213,3 @@ | ||
if (!req || !req.headers) { | ||
throw Error("Request headers missing"); | ||
throw Error('Request headers missing'); | ||
} | ||
@@ -164,3 +233,6 @@ } | ||
if (requestCC['min-fresh'] && this.timeToLive() < 1000*requestCC['min-fresh']) { | ||
if ( | ||
requestCC['min-fresh'] && | ||
this.timeToLive() < 1000 * requestCC['min-fresh'] | ||
) { | ||
return false; | ||
@@ -172,3 +244,7 @@ } | ||
if (this.stale()) { | ||
const allowsStale = requestCC['max-stale'] && !this._rescc['must-revalidate'] && (true === requestCC['max-stale'] || requestCC['max-stale'] > this.age() - this.maxAge()); | ||
const allowsStale = | ||
requestCC['max-stale'] && | ||
!this._rescc['must-revalidate'] && | ||
(true === requestCC['max-stale'] || | ||
requestCC['max-stale'] > this.age() - this.maxAge()); | ||
if (!allowsStale) { | ||
@@ -184,8 +260,12 @@ return false; | ||
// The presented effective request URI and that of the stored response match, and | ||
return (!this._url || this._url === req.url) && | ||
(this._host === req.headers.host) && | ||
return ( | ||
(!this._url || this._url === req.url) && | ||
this._host === req.headers.host && | ||
// the request method associated with the stored response allows it to be used for the presented request, and | ||
(!req.method || this._method === req.method || (allowHeadMethod && 'HEAD' === req.method)) && | ||
(!req.method || | ||
this._method === req.method || | ||
(allowHeadMethod && 'HEAD' === req.method)) && | ||
// selecting header fields nominated by the stored response (if any) match those presented, and | ||
this._varyMatches(req); | ||
this._varyMatches(req) | ||
); | ||
} | ||
@@ -195,3 +275,7 @@ | ||
// 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']; | ||
return ( | ||
this._rescc['must-revalidate'] || | ||
this._rescc.public || | ||
this._rescc['s-maxage'] | ||
); | ||
} | ||
@@ -209,4 +293,7 @@ | ||
const fields = this._resHeaders.vary.trim().toLowerCase().split(/\s*,\s*/); | ||
for(const name of fields) { | ||
const fields = this._resHeaders.vary | ||
.trim() | ||
.toLowerCase() | ||
.split(/\s*,\s*/); | ||
for (const name of fields) { | ||
if (req.headers[name] !== this._reqHeaders[name]) return false; | ||
@@ -219,3 +306,3 @@ } | ||
const headers = {}; | ||
for(const name in inHeaders) { | ||
for (const name in inHeaders) { | ||
if (hopByHopHeaders[name]) continue; | ||
@@ -227,3 +314,3 @@ headers[name] = inHeaders[name]; | ||
const tokens = inHeaders.connection.trim().split(/\s*,\s*/); | ||
for(const name of tokens) { | ||
for (const name of tokens) { | ||
delete headers[name]; | ||
@@ -251,4 +338,10 @@ } | ||
// lifetime greater than 24 hours and the response's age is greater than 24 hours. | ||
if (age > 3600*24 && !this._hasExplicitExpiration() && this.maxAge() > 3600*24) { | ||
headers.warning = (headers.warning ? `${headers.warning}, ` : '') + '113 - "rfc7234 5.5.4"'; | ||
if ( | ||
age > 3600 * 24 && | ||
!this._hasExplicitExpiration() && | ||
this.maxAge() > 3600 * 24 | ||
) { | ||
headers.warning = | ||
(headers.warning ? `${headers.warning}, ` : '') + | ||
'113 - "rfc7234 5.5.4"'; | ||
} | ||
@@ -272,5 +365,5 @@ headers.age = `${Math.round(age)}`; | ||
_serverDate() { | ||
const dateValue = Date.parse(this._resHeaders.date) | ||
const dateValue = Date.parse(this._resHeaders.date); | ||
if (isFinite(dateValue)) { | ||
const maxClockDrift = 8*3600*1000; | ||
const maxClockDrift = 8 * 3600 * 1000; | ||
const clockDrift = Math.abs(this._responseTime - dateValue); | ||
@@ -291,3 +384,3 @@ if (clockDrift < maxClockDrift) { | ||
age() { | ||
let age = Math.max(0, (this._responseTime - this.date())/1000); | ||
let age = Math.max(0, (this._responseTime - this.date()) / 1000); | ||
if (this._resHeaders.age) { | ||
@@ -298,3 +391,3 @@ let ageValue = this._ageValue(); | ||
const residentTime = (this.now() - this._responseTime)/1000; | ||
const residentTime = (this.now() - this._responseTime) / 1000; | ||
return age + residentTime; | ||
@@ -322,3 +415,8 @@ } | ||
// so this implementation requires explicit opt-in via public header | ||
if (this._isShared && (this._resHeaders['set-cookie'] && !this._rescc.public && !this._rescc.immutable)) { | ||
if ( | ||
this._isShared && | ||
(this._resHeaders['set-cookie'] && | ||
!this._rescc.public && | ||
!this._rescc.immutable) | ||
) { | ||
return 0; | ||
@@ -355,3 +453,3 @@ } | ||
} | ||
return Math.max(defaultMinTtl, (expires - dateValue)/1000); | ||
return Math.max(defaultMinTtl, (expires - dateValue) / 1000); | ||
} | ||
@@ -362,3 +460,6 @@ | ||
if (isFinite(lastModified) && dateValue > lastModified) { | ||
return Math.max(defaultMinTtl, (dateValue - lastModified)/1000 * this._cacheHeuristic); | ||
return Math.max( | ||
defaultMinTtl, | ||
((dateValue - lastModified) / 1000) * this._cacheHeuristic | ||
); | ||
} | ||
@@ -371,3 +472,3 @@ } | ||
timeToLive() { | ||
return Math.max(0, this.maxAge() - this.age())*1000; | ||
return Math.max(0, this.maxAge() - this.age()) * 1000; | ||
} | ||
@@ -380,8 +481,8 @@ | ||
static fromObject(obj) { | ||
return new this(undefined, undefined, {_fromObject:obj}); | ||
return new this(undefined, undefined, { _fromObject: obj }); | ||
} | ||
_fromObject(obj) { | ||
if (this._responseTime) throw Error("Reinitialized"); | ||
if (!obj || obj.v !== 1) throw Error("Invalid serialization"); | ||
if (this._responseTime) throw Error('Reinitialized'); | ||
if (!obj || obj.v !== 1) throw Error('Invalid serialization'); | ||
@@ -391,3 +492,4 @@ this._responseTime = obj.t; | ||
this._cacheHeuristic = obj.ch; | ||
this._immutableMinTtl = obj.imm !== undefined ? obj.imm : 24*3600*1000; | ||
this._immutableMinTtl = | ||
obj.imm !== undefined ? obj.imm : 24 * 3600 * 1000; | ||
this._status = obj.st; | ||
@@ -406,3 +508,3 @@ this._resHeaders = obj.resh; | ||
return { | ||
v:1, | ||
v: 1, | ||
t: this._responseTime, | ||
@@ -438,3 +540,4 @@ sh: this._isShared, | ||
if (!this._requestMatches(incomingReq, true) || !this.storable()) { // revalidation allowed via HEAD | ||
if (!this._requestMatches(incomingReq, true) || !this.storable()) { | ||
// revalidation allowed via HEAD | ||
// not for the same resource, or wasn't allowed to be cached anyway | ||
@@ -448,7 +551,13 @@ delete headers['if-none-match']; | ||
if (this._resHeaders.etag) { | ||
headers['if-none-match'] = headers['if-none-match'] ? `${headers['if-none-match']}, ${this._resHeaders.etag}` : this._resHeaders.etag; | ||
headers['if-none-match'] = headers['if-none-match'] | ||
? `${headers['if-none-match']}, ${this._resHeaders.etag}` | ||
: this._resHeaders.etag; | ||
} | ||
// Clients MAY issue simple (non-subrange) GET requests with either weak validators or strong validators. Clients MUST NOT use weak validators in other forms of request. | ||
const forbidsWeakValidators = headers['accept-ranges'] || headers['if-match'] || headers['if-unmodified-since'] || (this._method && this._method != 'GET'); | ||
const forbidsWeakValidators = | ||
headers['accept-ranges'] || | ||
headers['if-match'] || | ||
headers['if-unmodified-since'] || | ||
(this._method && this._method != 'GET'); | ||
@@ -461,5 +570,7 @@ /* SHOULD send the Last-Modified value in non-subrange cache validation requests (using If-Modified-Since) if only a Last-Modified value has been provided by the origin server. | ||
if (headers['if-none-match']) { | ||
const etags = headers['if-none-match'].split(/,/).filter(etag => { | ||
return !/^\s*W\//.test(etag); | ||
}); | ||
const etags = headers['if-none-match'] | ||
.split(/,/) | ||
.filter(etag => { | ||
return !/^\s*W\//.test(etag); | ||
}); | ||
if (!etags.length) { | ||
@@ -471,3 +582,6 @@ delete headers['if-none-match']; | ||
} | ||
} else if (this._resHeaders['last-modified'] && !headers['if-modified-since']) { | ||
} else if ( | ||
this._resHeaders['last-modified'] && | ||
!headers['if-modified-since'] | ||
) { | ||
headers['if-modified-since'] = this._resHeaders['last-modified']; | ||
@@ -491,3 +605,3 @@ } | ||
if (!response || !response.headers) { | ||
throw Error("Response headers missing"); | ||
throw Error('Response headers missing'); | ||
} | ||
@@ -500,7 +614,13 @@ | ||
matches = false; | ||
} else if (response.headers.etag && !/^\s*W\//.test(response.headers.etag)) { | ||
} else if ( | ||
response.headers.etag && | ||
!/^\s*W\//.test(response.headers.etag) | ||
) { | ||
// "All of the stored responses with the same strong validator are selected. | ||
// If none of the stored responses contain the same strong validator, | ||
// then the cache MUST NOT use the new response to update any stored responses." | ||
matches = this._resHeaders.etag && this._resHeaders.etag.replace(/^\s*W\//,'') === response.headers.etag; | ||
matches = | ||
this._resHeaders.etag && | ||
this._resHeaders.etag.replace(/^\s*W\//, '') === | ||
response.headers.etag; | ||
} else if (this._resHeaders.etag && response.headers.etag) { | ||
@@ -510,5 +630,9 @@ // "If the new response contains a weak validator and that validator corresponds | ||
// then the most recent of those matching stored responses is selected for update." | ||
matches = this._resHeaders.etag.replace(/^\s*W\//,'') === response.headers.etag.replace(/^\s*W\//,''); | ||
matches = | ||
this._resHeaders.etag.replace(/^\s*W\//, '') === | ||
response.headers.etag.replace(/^\s*W\//, ''); | ||
} else if (this._resHeaders['last-modified']) { | ||
matches = this._resHeaders['last-modified'] === response.headers['last-modified']; | ||
matches = | ||
this._resHeaders['last-modified'] === | ||
response.headers['last-modified']; | ||
} else { | ||
@@ -519,4 +643,8 @@ // If the new response does not include any form of validator (such as in the case where | ||
// lacks a validator, then that stored response is selected for update. | ||
if (!this._resHeaders.etag && !this._resHeaders['last-modified'] && | ||
!response.headers.etag && !response.headers['last-modified']) { | ||
if ( | ||
!this._resHeaders.etag && | ||
!this._resHeaders['last-modified'] && | ||
!response.headers.etag && | ||
!response.headers['last-modified'] | ||
) { | ||
matches = true; | ||
@@ -534,3 +662,3 @@ } | ||
matches: false, | ||
} | ||
}; | ||
} | ||
@@ -541,4 +669,7 @@ | ||
const headers = {}; | ||
for(const k in this._resHeaders) { | ||
headers[k] = k in response.headers && !excludedFromRevalidationUpdate[k] ? response.headers[k] : this._resHeaders[k]; | ||
for (const k in this._resHeaders) { | ||
headers[k] = | ||
k in response.headers && !excludedFromRevalidationUpdate[k] | ||
? response.headers[k] | ||
: this._resHeaders[k]; | ||
} | ||
@@ -552,3 +683,8 @@ | ||
return { | ||
policy: new this.constructor(request, newResponse, {shared: this._isShared, cacheHeuristic: this._cacheHeuristic, immutableMinTimeToLive: this._immutableMinTtl, trustServerDate: this._trustServerDate}), | ||
policy: new this.constructor(request, newResponse, { | ||
shared: this._isShared, | ||
cacheHeuristic: this._cacheHeuristic, | ||
immutableMinTimeToLive: this._immutableMinTtl, | ||
trustServerDate: this._trustServerDate, | ||
}), | ||
modified: false, | ||
@@ -555,0 +691,0 @@ matches: true, |
{ | ||
"name": "http-cache-semantics", | ||
"version": "4.0.2", | ||
"description": "Parses Cache-Control and other headers. Helps building correct HTTP caches and proxies", | ||
"repository": "https://github.com/kornelski/http-cache-semantics.git", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "mocha" | ||
}, | ||
"files": [ | ||
"index.js" | ||
], | ||
"author": "Kornel Lesiński <kornel@geekhood.net> (https://kornel.ski/)", | ||
"license": "BSD-2-Clause", | ||
"devDependencies": { | ||
"mocha": "^5.1.0" | ||
} | ||
"name": "http-cache-semantics", | ||
"version": "4.0.3", | ||
"description": "Parses Cache-Control and other headers. Helps building correct HTTP caches and proxies", | ||
"repository": "https://github.com/kornelski/http-cache-semantics.git", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "mocha" | ||
}, | ||
"files": [ | ||
"index.js" | ||
], | ||
"author": "Kornel Lesiński <kornel@geekhood.net> (https://kornel.ski/)", | ||
"license": "BSD-2-Clause", | ||
"devDependencies": { | ||
"eslint": "^5.13.0", | ||
"eslint-plugin-prettier": "^3.0.1", | ||
"husky": "^0.14.3", | ||
"lint-staged": "^8.1.3", | ||
"mocha": "^5.1.0", | ||
"prettier": "^1.14.3", | ||
"prettier-eslint-cli": "^4.7.1" | ||
} | ||
} |
@@ -19,3 +19,7 @@ # Can I cache this? [![Build Status](https://travis-ci.org/kornelski/http-cache-semantics.svg?branch=master)](https://travis-ci.org/kornelski/http-cache-semantics) | ||
// (this is pseudocode, roll your own cache (lru-cache package works)) | ||
letsPretendThisIsSomeCache.set(request.url, {policy, response}, policy.timeToLive()); | ||
letsPretendThisIsSomeCache.set( | ||
request.url, | ||
{ policy, response }, | ||
policy.timeToLive() | ||
); | ||
``` | ||
@@ -25,3 +29,3 @@ | ||
// And later, when you receive a new request: | ||
const {policy, response} = letsPretendThisIsSomeCache.get(newRequest.url); | ||
const { policy, response } = letsPretendThisIsSomeCache.get(newRequest.url); | ||
@@ -64,3 +68,3 @@ // It's not enough that it exists in the cache, it has to match the new request, too: | ||
cacheHeuristic: 0.1, | ||
immutableMinTimeToLive: 24*3600*1000, // 24h | ||
immutableMinTimeToLive: 24 * 3600 * 1000, // 24h | ||
ignoreCargoCult: false, | ||
@@ -73,3 +77,3 @@ trustServerDate: true, | ||
`options.cacheHeuristic` is a fraction of response's age that is used as a fallback cache duration. The default is 0.1 (10%), e.g. if a file hasn't been modified for 100 days, it'll be cached for 100*0.1 = 10 days. | ||
`options.cacheHeuristic` is a fraction of response's age that is used as a fallback cache duration. The default is 0.1 (10%), e.g. if a file hasn't been modified for 100 days, it'll be cached for 100\*0.1 = 10 days. | ||
@@ -104,3 +108,3 @@ `options.immutableMinTimeToLive` is a number of milliseconds to assume as the default time to cache responses with `Cache-Control: immutable`. Note that [per RFC](http://httpwg.org/http-extensions/immutable.html) these can become stale, so `max-age` still overrides the default. | ||
Returns approximate time in *milliseconds* until the response becomes stale (i.e. not fresh). | ||
Returns approximate time in _milliseconds_ until the response becomes stale (i.e. not fresh). | ||
@@ -133,10 +137,12 @@ After that time (when `timeToLive() <= 0`) the response might not be usable without revalidation. However, there are exceptions, e.g. a client can explicitly allow stale responses, so always check with `satisfiesWithoutRevalidation()`. | ||
* `policy` — A new `CachePolicy` with HTTP headers updated from `revalidationResponse`. You can always replace the old cached `CachePolicy` with the new one. | ||
* `modified` — Boolean indicating whether the response body has changed. | ||
* If `false`, then a valid 304 Not Modified response has been received, and you can reuse the old cached response body. | ||
* If `true`, you should use new response's body (if present), or make another request to the origin server without any conditional headers (i.e. don't use `revalidationHeaders()` this time) to get the new resource. | ||
- `policy` — A new `CachePolicy` with HTTP headers updated from `revalidationResponse`. You can always replace the old cached `CachePolicy` with the new one. | ||
- `modified` — Boolean indicating whether the response body has changed. | ||
- If `false`, then a valid 304 Not Modified response has been received, and you can reuse the old cached response body. | ||
- If `true`, you should use new response's body (if present), or make another request to the origin server without any conditional headers (i.e. don't use `revalidationHeaders()` this time) to get the new resource. | ||
```js | ||
// When serving requests from cache: | ||
const {oldPolicy, oldResponse} = letsPretendThisIsSomeCache.get(newRequest.url); | ||
const { oldPolicy, oldResponse } = letsPretendThisIsSomeCache.get( | ||
newRequest.url | ||
); | ||
@@ -151,7 +157,14 @@ if (!oldPolicy.satisfiesWithoutRevalidation(newRequest)) { | ||
// Create updated policy and combined response from the old and new data | ||
const {policy, modified} = oldPolicy.revalidatedPolicy(newRequest, newResponse); | ||
const { policy, modified } = oldPolicy.revalidatedPolicy( | ||
newRequest, | ||
newResponse | ||
); | ||
const response = modified ? newResponse : oldResponse; | ||
// Update the cache with the newer/fresher response | ||
letsPretendThisIsSomeCache.set(newRequest.url, {policy, response}, policy.timeToLive()); | ||
letsPretendThisIsSomeCache.set( | ||
newRequest.url, | ||
{ policy, response }, | ||
policy.timeToLive() | ||
); | ||
@@ -170,19 +183,19 @@ // And proceed returning cached response as usual | ||
* [ImageOptim API](https://imageoptim.com/api), [make-fetch-happen](https://github.com/zkat/make-fetch-happen), [cacheable-request](https://www.npmjs.com/package/cacheable-request) ([got](https://www.npmjs.com/package/got)), [npm/registry-fetch](https://github.com/npm/registry-fetch), [etc.](https://github.com/kornelski/http-cache-semantics/network/dependents) | ||
- [ImageOptim API](https://imageoptim.com/api), [make-fetch-happen](https://github.com/zkat/make-fetch-happen), [cacheable-request](https://www.npmjs.com/package/cacheable-request) ([got](https://www.npmjs.com/package/got)), [npm/registry-fetch](https://github.com/npm/registry-fetch), [etc.](https://github.com/kornelski/http-cache-semantics/network/dependents) | ||
## Implemented | ||
* `Cache-Control` response header with all the quirks. | ||
* `Expires` with check for bad clocks. | ||
* `Pragma` response header. | ||
* `Age` response header. | ||
* `Vary` response header. | ||
* Default cacheability of statuses and methods. | ||
* Requests for stale data. | ||
* Filtering of hop-by-hop headers. | ||
* Basic revalidation request | ||
- `Cache-Control` response header with all the quirks. | ||
- `Expires` with check for bad clocks. | ||
- `Pragma` response header. | ||
- `Age` response header. | ||
- `Vary` response header. | ||
- Default cacheability of statuses and methods. | ||
- Requests for stale data. | ||
- Filtering of hop-by-hop headers. | ||
- Basic revalidation request | ||
## Unimplemented | ||
* Merging of range requests, If-Range (but correctly supports them as non-cacheable) | ||
* Revalidation of multiple representations | ||
- Merging of range requests, If-Range (but correctly supports them as non-cacheable) | ||
- Revalidation of multiple representations |
34782
590
194
7