http-cache-semantics
Advanced tools
Comparing version 4.0.4 to 4.1.0
88
index.js
'use strict'; | ||
// rfc7231 6.1 | ||
const statusCodeCacheableByDefault = [ | ||
const statusCodeCacheableByDefault = new Set([ | ||
200, | ||
@@ -15,6 +15,6 @@ 203, | ||
501, | ||
]; | ||
]); | ||
// This implementation does not understand partial responses (206) | ||
const understoodStatuses = [ | ||
const understoodStatuses = new Set([ | ||
200, | ||
@@ -34,4 +34,11 @@ 203, | ||
501, | ||
]; | ||
]); | ||
const errorStatusCodes = new Set([ | ||
500, | ||
502, | ||
503, | ||
504, | ||
]); | ||
const hopByHopHeaders = { | ||
@@ -48,2 +55,3 @@ date: true, // included, because we add Age update Date | ||
}; | ||
const excludedFromRevalidationUpdate = { | ||
@@ -57,2 +65,16 @@ // Since the old body is reused, it doesn't make sense to change properties of the body | ||
function toNumberOrZero(s) { | ||
const n = parseInt(s, 10); | ||
return isFinite(n) ? n : 0; | ||
} | ||
// RFC 5861 | ||
function isErrorResponse(response) { | ||
// consider undefined response as faulty | ||
if(!response) { | ||
return true | ||
} | ||
return errorStatusCodes.has(response.status); | ||
} | ||
function parseCacheControl(header) { | ||
@@ -94,3 +116,2 @@ const cc = {}; | ||
ignoreCargoCult, | ||
trustServerDate, | ||
_fromObject, | ||
@@ -111,4 +132,2 @@ } = {} | ||
this._isShared = shared !== false; | ||
this._trustServerDate = | ||
undefined !== trustServerDate ? trustServerDate : true; | ||
this._cacheHeuristic = | ||
@@ -174,3 +193,3 @@ undefined !== cacheHeuristic ? cacheHeuristic : 0.1; // 10% matches IE | ||
// the response status code is understood by the cache, and | ||
understoodStatuses.indexOf(this._status) !== -1 && | ||
understoodStatuses.has(this._status) && | ||
// the "no-store" cache directive does not appear in request or response header fields, and | ||
@@ -194,3 +213,3 @@ !this._rescc['no-store'] && | ||
// has a status code that is defined as cacheable by default | ||
statusCodeCacheableByDefault.indexOf(this._status) !== -1) | ||
statusCodeCacheableByDefault.has(this._status)) | ||
); | ||
@@ -342,20 +361,9 @@ } | ||
/** | ||
* Value of the Date response header or current time if Date was demed invalid | ||
* Value of the Date response header or current time if Date was invalid | ||
* @return timestamp | ||
*/ | ||
date() { | ||
if (this._trustServerDate) { | ||
return this._serverDate(); | ||
} | ||
return this._responseTime; | ||
} | ||
_serverDate() { | ||
const serverDate = Date.parse(this._resHeaders.date); | ||
if (isFinite(serverDate)) { | ||
const maxClockDrift = 8 * 3600 * 1000; | ||
const clockDrift = Math.abs(this._responseTime - serverDate); | ||
if (clockDrift < maxClockDrift) { | ||
return serverDate; | ||
} | ||
return serverDate; | ||
} | ||
@@ -372,7 +380,3 @@ return this._responseTime; | ||
age() { | ||
let age = Math.max(0, (this._responseTime - this.date()) / 1000); | ||
if (this._resHeaders.age) { | ||
let ageValue = this._ageValue(); | ||
if (ageValue > age) age = ageValue; | ||
} | ||
let age = this._ageValue(); | ||
@@ -384,4 +388,3 @@ const residentTime = (this.now() - this._responseTime) / 1000; | ||
_ageValue() { | ||
const ageValue = parseInt(this._resHeaders.age); | ||
return isFinite(ageValue) ? ageValue : 0; | ||
return toNumberOrZero(this._resHeaders.age); | ||
} | ||
@@ -422,3 +425,3 @@ | ||
if (this._rescc['s-maxage']) { | ||
return parseInt(this._rescc['s-maxage'], 10); | ||
return toNumberOrZero(this._rescc['s-maxage']); | ||
} | ||
@@ -429,3 +432,3 @@ } | ||
if (this._rescc['max-age']) { | ||
return parseInt(this._rescc['max-age'], 10); | ||
return toNumberOrZero(this._rescc['max-age']); | ||
} | ||
@@ -435,3 +438,3 @@ | ||
const serverDate = this._serverDate(); | ||
const serverDate = this.date(); | ||
if (this._resHeaders.expires) { | ||
@@ -460,3 +463,6 @@ const expires = Date.parse(this._resHeaders.expires); | ||
timeToLive() { | ||
return Math.max(0, this.maxAge() - this.age()) * 1000; | ||
const age = this.maxAge() - this.age(); | ||
const staleIfErrorAge = age + toNumberOrZero(this._rescc['stale-if-error']); | ||
const staleWhileRevalidateAge = age + toNumberOrZero(this._rescc['stale-while-revalidate']); | ||
return Math.max(0, age, staleIfErrorAge, staleWhileRevalidateAge) * 1000; | ||
} | ||
@@ -468,2 +474,10 @@ | ||
_useStaleIfError() { | ||
return this.maxAge() + toNumberOrZero(this._rescc['stale-if-error']) > this.age(); | ||
} | ||
useStaleWhileRevalidate() { | ||
return this.maxAge() + toNumberOrZero(this._rescc['stale-while-revalidate']) > this.age(); | ||
} | ||
static fromObject(obj) { | ||
@@ -586,2 +600,9 @@ return new this(undefined, undefined, { _fromObject: obj }); | ||
this._assertRequestHasHeaders(request); | ||
if(this._useStaleIfError() && isErrorResponse(response)) { // I consider the revalidation request unsuccessful | ||
return { | ||
modified: false, | ||
matches: false, | ||
policy: this, | ||
}; | ||
} | ||
if (!response || !response.headers) { | ||
@@ -664,3 +685,2 @@ throw Error('Response headers missing'); | ||
immutableMinTimeToLive: this._immutableMinTtl, | ||
trustServerDate: this._trustServerDate, | ||
}), | ||
@@ -667,0 +687,0 @@ modified: false, |
{ | ||
"name": "http-cache-semantics", | ||
"version": "4.0.4", | ||
"version": "4.1.0", | ||
"description": "Parses Cache-Control and other headers. Helps building correct HTTP caches and proxies", | ||
@@ -5,0 +5,0 @@ "repository": "https://github.com/kornelski/http-cache-semantics.git", |
# 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) | ||
`CachePolicy` tells when responses can be reused from a cache, taking into account [HTTP RFC 7234](http://httpwg.org/specs/rfc7234.html) rules for user agents and shared caches. It's aware of many tricky details such as the `Vary` header, proxy revalidation, and authenticated responses. | ||
`CachePolicy` tells when responses can be reused from a cache, taking into account [HTTP RFC 7234](http://httpwg.org/specs/rfc7234.html) rules for user agents and shared caches. | ||
It also implements [RFC 5861](https://tools.ietf.org/html/rfc5861), implementing `stale-if-error` and `stale-while-revalidate`. | ||
It's aware of many tricky details such as the `Vary` header, proxy revalidation, and authenticated responses. | ||
@@ -68,3 +70,2 @@ ## Usage | ||
ignoreCargoCult: false, | ||
trustServerDate: true, | ||
}; | ||
@@ -81,4 +82,2 @@ ``` | ||
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()` | ||
@@ -109,2 +108,3 @@ | ||
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()`. | ||
`stale-if-error` and `stale-while-revalidate` extend the time to live of the cache, that can still be used if stale. | ||
@@ -137,3 +137,3 @@ ### `toObject()`/`fromObject(json)` | ||
- `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 `false`, then a valid 304 Not Modified response has been received, and you can reuse the old cached response body. This is also affected by `stale-if-error`. | ||
- 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. | ||
@@ -193,6 +193,16 @@ | ||
- Basic revalidation request | ||
- `stale-if-error` | ||
## Unimplemented | ||
- Merging of range requests, If-Range (but correctly supports them as non-cacheable) | ||
- Merging of range requests, `If-Range` (but correctly supports them as non-cacheable) | ||
- Revalidation of multiple representations | ||
### Trusting server `Date` | ||
Per the RFC, the cache should take into account the time between server-supplied `Date` and the time it received the response. The RFC-mandated behavior creates two problems: | ||
* Servers with incorrectly set timezone may add several hours to cache age (or more, if the clock is completely wrong). | ||
* Even reasonably correct clocks may be off by a couple of seconds, breaking `max-age=1` trick (which is useful for reverse proxies on high-traffic servers). | ||
Previous versions of this library had an option to ignore the server date if it was "too inaccurate". To support the `max-age=1` trick the library also has to ignore dates that pretty accurate. There's no point of having an option to trust dates that are only a bit inaccurate, so this library won't trust any server dates. `max-age` will be interpreted from the time the response has been received, not from when it has been sent. This will affect only [RFC 1149 networks](https://tools.ietf.org/html/rfc1149). |
36180
605
204