Socket
Socket
Sign inDemoInstall

http-cache-semantics

Package Overview
Dependencies
0
Maintainers
1
Versions
26
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 3.5.1 to 3.6.0

test/updatetest.js

102

index.js

@@ -8,3 +8,9 @@ 'use strict';

const hopByHopHeaders = {'connection':true, 'keep-alive':true, 'proxy-authenticate':true, 'proxy-authorization':true, 'te':true, 'trailers':true, 'transfer-encoding':true, 'upgrade':true};
const hopByHopHeaders = {'connection':true, 'keep-alive':true, 'proxy-authenticate':true, 'proxy-authorization':true, 'te':true, 'trailer':true, 'transfer-encoding':true, 'upgrade':true};
const excludedFromRevalidationUpdate = {
'etag': true, 'last-modified': true, // Per spec
'content-range': true,
// 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,
};

@@ -48,5 +54,3 @@ function parseCacheControl(header) {

}
if (!req || !req.headers) {
throw Error("Request headers missing");
}
this._assertRequestHasHeaders(req);

@@ -369,2 +373,5 @@ this._responseTime = this.now();

// This implementation does not understand range requests
delete headers['if-range'];
if (!this._requestMatches(incomingReq, true) || !this.storable()) { // revalidation allowed via HEAD

@@ -382,5 +389,21 @@ // not for the same resource, or wasn't allowed to be cached anyway

// 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');
/* 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.
Note: This implementation does not understand partial responses (206) */
if (this._resHeaders['last-modified'] && !headers['if-modified-since']) {
if (forbidsWeakValidators) {
delete headers['if-modified-since'];
if (headers['if-none-match']) {
const etags = headers['if-none-match'].split(/,/).filter(etag => {
return !/^\s*W\//.test(etag);
});
if (!etags.length) {
delete headers['if-none-match'];
} else {
headers['if-none-match'] = etags.join(',').trim();
}
}
} else if (this._resHeaders['last-modified'] && !headers['if-modified-since']) {
headers['if-modified-since'] = this._resHeaders['last-modified'];

@@ -391,2 +414,71 @@ }

}
/**
* Creates new CachePolicy with information combined from the previews response,
* and the new revalidation response.
*
* Returns {policy, modified} where modified is a boolean indicating
* whether the response body has been modified, and old cached body can't be used.
*
* @return {Object} {policy: CachePolicy, modified: Boolean}
*/
revalidatedPolicy(request, response) {
this._assertRequestHasHeaders(request);
if (!response || !response.headers) {
throw Error("Response headers missing");
}
// These aren't going to be supported exactly, since one CachePolicy object
// doesn't know about all the other cached objects.
let matches = false;
if (response.status !== undefined && response.status != 304) {
matches = false;
} 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;
} else if (this._resHeaders.etag && response.headers.etag) {
// "If the new response contains a weak validator and that validator corresponds
// to one of the cache's stored responses,
// 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\//,'');
} else if (this._resHeaders['last-modified']) {
matches = this._resHeaders['last-modified'] === response.headers['last-modified'];
} else {
// If the new response does not include any form of validator (such as in the case where
// a client generates an If-Modified-Since request from a source other than the Last-Modified
// response header field), and there is only one stored response, and that stored response also
// 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']) {
matches = true;
}
}
if (!matches) {
return {
policy: new this.constructor(request, response),
modified: true,
}
}
// use other header fields provided in the 304 (Not Modified) response to replace all instances
// of the corresponding header fields in the stored response.
const headers = {};
for(const k in this._resHeaders) {
if (excludedFromRevalidationUpdate[k]) continue;
headers[k] = k in response.headers ? response.headers[k] : this._resHeaders[k];
}
const newResponse = Object.assign({}, response, {
status: this._status,
method: this._method,
headers,
});
return {
policy: new this.constructor(request, newResponse),
modified: false,
};
}
};

2

package.json
{
"name": "http-cache-semantics",
"version": "3.5.1",
"version": "3.6.0",
"description": "Parses Cache-Control and other headers. Helps building correct HTTP caches and proxies",

@@ -5,0 +5,0 @@ "main": "index.js",

@@ -66,3 +66,3 @@ # Can I cache this?

If `options.shared` is true (default), then response is evaluated from perspective of a shared cache (i.e. `private` is not cacheable and `s-maxage` is respected). If `options.shared` is false, then response is evaluated from perspective of a single-user cache (i.e. `private` is cacheable and `s-maxage` is ignored).
If `options.shared` is `true` (default), then the response is evaluated from a perspective of a shared cache (i.e. `private` is not cacheable and `s-maxage` is respected). If `options.shared` is `false`, then the response is evaluated from a perspective of a single-user cache (i.e. `private` is cacheable and `s-maxage` is ignored).

@@ -77,3 +77,3 @@ `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.

### `satisfiesWithoutRevalidation(new_request)`
### `satisfiesWithoutRevalidation(newRequest)`

@@ -84,12 +84,12 @@ This is the most important method. Use this method to check whether the cached response is still fresh in the context of the new request.

If it returns `false`, then the response may not be matching at all (e.g. it's for a different URL or method), or may require to be refreshed first.
If it returns `false`, then the response may not be matching at all (e.g. it's for a different URL or method), or may require to be refreshed first (see `revalidationHeaders()`).
### `responseHeaders()`
Returns updated, filtered set of response headers to return to clients receiving the cached response. This function is necessary, because proxies MUST always remove hop-by-hop headers (such as `TE` and `Connection`) and update response `Age` to avoid doubling cache time.
Returns updated, filtered set of response headers to return to clients receiving the cached response. This function is necessary, because proxies MUST always remove hop-by-hop headers (such as `TE` and `Connection`) and update response's `Age` to avoid doubling cache time.
### `revalidationHeaders()`
```js
cachedResponse.headers = cachePolicy.responseHeaders(cachedResponse);
```
Returns updated, filtered set of request headers to send to the origin server to check if the cached response can be reused. With this set of headers, the origin server may return status 304 indicating the response is still fresh.
### `timeToLive()`

@@ -105,2 +105,51 @@

### Refreshing stale cache (revalidation)
When a cached response has expired, it can be made fresh again by making a request to the origin server. The server may respond with status 304 (Not Modified) without sending the response body again, saving bandwidth.
The following methods help perform the update efficiently and correctly.
#### `revalidationHeaders(newRequest)`
Returns updated, filtered set of request headers to send to the origin server to check if the cached response can be reused. These headers allow the origin server to return status 304 indicating the response is still fresh. All headers unrelated to caching are passed through as-is.
Use this method when updating cache from the origin server.
```js
updateRequest.headers = cachePolicy.revalidationHeaders(updateRequest);
```
#### `revalidatedPolicy(revalidationRequest, revalidationResponse)`
Use this method to update the cache after receiving a new response from the origin server. It returns an object with two keys:
* `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);
if (!oldPolicy.satisfiesWithoutRevalidation(newRequest)) {
// Change the request to ask the origin server if the cached response can be used
newRequest.headers = oldPolicy.revalidationHeaders(newRequest);
// Send request to the origin server. The server may respond with status 304
const newResponse = await makeRequest(newResponse);
// Create updated policy and combined response from the old and new data
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());
// And proceed returning cached response as usual
response.headers = policy.responseHeaders();
return response;
}
```
# Yo, FRESH

@@ -124,3 +173,3 @@

* Range requests, If-Range
* Revalidation of multiple representations
* Updating of response after revalidation

@@ -45,3 +45,2 @@ 'use strict';

const incomingRequest = simpleRequestBut({method:'POST'});
// Returns the same object unmodified, which means no custom validation
const headers = cache.revalidationHeaders(incomingRequest);

@@ -114,2 +113,15 @@ assertHeadersPassed(headers);

it('should not send the Last-Modified value for POST', function(){
const postReq = {method:'POST', headers:{'if-modified-since':'yesterday'}};
const cache = new CachePolicy(postReq, lastModifiedResponse);
const actual = cache.revalidationHeaders(postReq)['if-modified-since'];
assert.equal(actual, undefined);
});
it('should not send the Last-Modified value for range requests', function(){
const rangeReq = {method:'GET', headers:{'accept-ranges':'1-3', 'if-modified-since':'yesterday'}};
const cache = new CachePolicy(rangeReq, lastModifiedResponse);
const actual = cache.revalidationHeaders(rangeReq)['if-modified-since'];
assert.equal(actual, undefined);
});
});
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc