Browse Source

Strip hop by hop headers

master v3.5.1
Kornel Lesiński 8 years ago
parent
commit
dc41aa2267
  1. 46
      index.js
  2. 6
      package.json
  3. 81
      test/revalidatetest.js

46
index.js

@ -189,15 +189,15 @@ module.exports = class CachePolicy {
return true;
}
responseHeaders() {
_copyWithoutHopByHopHeaders(inHeaders) {
const headers = {};
for(const name in this._resHeaders) {
for(const name in inHeaders) {
if (hopByHopHeaders[name]) continue;
headers[name] = this._resHeaders[name];
headers[name] = inHeaders[name];
}
// 9.1. Connection
if (this._resHeaders.connection) {
const tokens = this._resHeaders.connection.trim().split(/\s*,\s*/);
if (inHeaders.connection) {
const tokens = inHeaders.connection.trim().split(/\s*,\s*/);
for(const name of tokens) {
delete headers[name];
}
@ -212,6 +212,11 @@ module.exports = class CachePolicy {
headers.warning = warnings.join(',').trim();
}
}
return headers;
}
responseHeaders() {
const headers = this._copyWithoutHopByHopHeaders(this._resHeaders);
headers.age = `${Math.round(this.age())}`;
return headers;
}
@ -348,25 +353,32 @@ module.exports = class CachePolicy {
};
}
revalidationHeaders(incoming_req) {
this._assertRequestHasHeaders(incoming_req);
if (!this._resHeaders.etag && !this._resHeaders['last-modified']) {
return incoming_req.headers; // no validators available
}
// revalidation allowed via HEAD
if (!this._requestMatches(incoming_req, true)) {
return incoming_req.headers; // not for the same resource
/**
* Headers for sending to the origin server to revalidate stale response.
* Allows server to return 304 to allow reuse of the previous response.
*
* Hop by hop headers are always stripped.
* Revalidation headers may be added or removed, depending on request.
*/
revalidationHeaders(incomingReq) {
this._assertRequestHasHeaders(incomingReq);
const headers = this._copyWithoutHopByHopHeaders(incomingReq.headers);
if (!this._requestMatches(incomingReq, true) || !this.storable()) { // revalidation allowed via HEAD
// not for the same resource, or wasn't allowed to be cached anyway
delete headers['if-none-match'];
delete headers['if-modified-since'];
return headers;
}
const headers = Object.assign({}, incoming_req.headers);
/* MUST send that entity-tag in any cache validation request (using If-Match or If-None-Match) if an entity-tag has been provided by the origin server. */
if (this._resHeaders.etag) {
headers['if-none-match'] = this._resHeaders.etag;
headers['if-none-match'] = headers['if-none-match'] ? `${headers['if-none-match']}, ${this._resHeaders.etag}` : this._resHeaders.etag;
}
/* 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'] && this.storable()) {
if (this._resHeaders['last-modified'] && !headers['if-modified-since']) {
headers['if-modified-since'] = this._resHeaders['last-modified'];
}

6
package.json

@ -1,12 +1,16 @@
{
"name": "http-cache-semantics",
"version": "3.5.0",
"version": "3.5.1",
"description": "Parses Cache-Control and other headers. Helps building correct HTTP caches and proxies",
"main": "index.js",
"repository": "https://github.com/pornel/http-cache-semantics.git",
"scripts": {
"test": "mocha"
},
"files": [
"index.js",
"test"
],
"author": "Kornel Lesiński <kornel@geekhood.net> (https://kornel.ski/)",
"license": "BSD-2-Clause",
"devDependencies": {

81
test/revalidatetest.js

@ -5,7 +5,11 @@ const CachePolicy = require('..');
const simpleRequest = {
method:'GET',
headers:{host:'www.w3c.org'},
headers:{
host:'www.w3c.org',
connection: 'close',
'x-custom': 'yes',
},
url:'/Protocols/rfc2616/rfc2616-sec14.html',
};
function simpleRequestBut(overrides) {
@ -18,57 +22,88 @@ const lastModifiedResponse = {headers:Object.assign({'last-modified':'Tue, 15 No
const multiValidatorResponse = {headers:Object.assign({},etaggedResponse.headers,lastModifiedResponse.headers)};
const alwaysVariableResponse = {headers:Object.assign({'vary':'*'},cacheableResponse.headers)};
function assertHeadersPassed(headers) {
assert.strictEqual(headers.connection, undefined);
assert.strictEqual(headers['x-custom'], 'yes');
}
function assertNoValidators(headers) {
assert.strictEqual(headers['if-none-match'], undefined);
assert.strictEqual(headers['if-modified-since'], undefined);
}
describe('Can be revalidated?', function() {
it('ok if method changes to HEAD', function(){
const cache = new CachePolicy(simpleRequest,etaggedResponse);
const headers = cache.revalidationHeaders(simpleRequestBut({method:'HEAD'}));
assert.equal(headers['if-none-match'], '"123456789"');
const cache = new CachePolicy(simpleRequest,etaggedResponse);
const headers = cache.revalidationHeaders(simpleRequestBut({method:'HEAD'}));
assertHeadersPassed(headers);
assert.equal(headers['if-none-match'], '"123456789"');
});
it('not if method mismatch (other than HEAD)',function(){
const cache = new CachePolicy(simpleRequest,etaggedResponse);
const incomingRequest = simpleRequestBut({method:'POST'});
// Returns the same object unmodified, which means no custom validation
assert.strictEqual(incomingRequest.headers, cache.revalidationHeaders(incomingRequest));
it('not if method mismatch (other than HEAD)', function(){
const cache = new CachePolicy(simpleRequest,etaggedResponse);
const incomingRequest = simpleRequestBut({method:'POST'});
// Returns the same object unmodified, which means no custom validation
const headers = cache.revalidationHeaders(incomingRequest);
assertHeadersPassed(headers);
assertNoValidators(headers);
});
it('not if url mismatch',function(){
const cache = new CachePolicy(simpleRequest,etaggedResponse);
const incomingRequest = simpleRequestBut({url:'/yomomma'});
assert.strictEqual(incomingRequest.headers, cache.revalidationHeaders(incomingRequest));
it('not if url mismatch', function(){
const cache = new CachePolicy(simpleRequest,etaggedResponse);
const incomingRequest = simpleRequestBut({url:'/yomomma'});
const headers = cache.revalidationHeaders(incomingRequest);
assertHeadersPassed(headers);
assertNoValidators(headers);
});
it('not if host mismatch',function(){
it('not if host mismatch', function(){
const cache = new CachePolicy(simpleRequest,etaggedResponse);
const incomingRequest = simpleRequestBut({headers:{host:'www.w4c.org'}});
assert.strictEqual(incomingRequest.headers, cache.revalidationHeaders(incomingRequest));
const headers = cache.revalidationHeaders(incomingRequest);
assertNoValidators(headers);
assert.strictEqual(headers['x-custom'], undefined);
});
it('not if vary fields prevent',function(){
const cache = new CachePolicy(simpleRequest,alwaysVariableResponse);
assert.strictEqual(simpleRequest.headers, cache.revalidationHeaders(simpleRequest));
it('not if vary fields prevent', function(){
const cache = new CachePolicy(simpleRequest,alwaysVariableResponse);
const headers = cache.revalidationHeaders(simpleRequest);
assertHeadersPassed(headers);
assertNoValidators(headers);
});
it('when entity tag validator is present', function() {
const cache = new CachePolicy(simpleRequest, etaggedResponse);
const headers = cache.revalidationHeaders(simpleRequest);
assertHeadersPassed(headers);
assert.equal(headers['if-none-match'], '"123456789"');
});
it('when last-modified validator is present', function() {
const cache = new CachePolicy(simpleRequest, lastModifiedResponse);
const headers = cache.revalidationHeaders(simpleRequest);
assert.equal(headers['if-modified-since'], 'Tue, 15 Nov 1994 12:45:26 GMT');
const cache = new CachePolicy(simpleRequest, lastModifiedResponse);
const headers = cache.revalidationHeaders(simpleRequest);
assertHeadersPassed(headers);
assert.equal(headers['if-modified-since'], 'Tue, 15 Nov 1994 12:45:26 GMT');
});
it('not without validators', function() {
const cache = new CachePolicy(simpleRequest, cacheableResponse);
assert.strictEqual(simpleRequest.headers, cache.revalidationHeaders(simpleRequest));
const headers = cache.revalidationHeaders(simpleRequest);
assertHeadersPassed(headers);
assertNoValidators(headers);
})
});
describe('Validation request', function(){
it('must contain any etag', function(){
const cache = new CachePolicy(simpleRequest,multiValidatorResponse);
const expected = multiValidatorResponse.headers.etag;
const actual = cache.revalidationHeaders(simpleRequest)['if-none-match'];
assert.equal(actual,expected);
});
it('should send the Last-Modified value',function(){
it('should send the Last-Modified value', function(){
const cache = new CachePolicy(simpleRequest,multiValidatorResponse);
const expected = multiValidatorResponse.headers['last-modified'];
const actual = cache.revalidationHeaders(simpleRequest)['if-modified-since'];

Loading…
Cancel
Save