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; return true;
} }
responseHeaders() { _copyWithoutHopByHopHeaders(inHeaders) {
const headers = {}; const headers = {};
for(const name in this._resHeaders) { for(const name in inHeaders) {
if (hopByHopHeaders[name]) continue; if (hopByHopHeaders[name]) continue;
headers[name] = this._resHeaders[name]; headers[name] = inHeaders[name];
} }
// 9.1. Connection // 9.1. Connection
if (this._resHeaders.connection) { if (inHeaders.connection) {
const tokens = this._resHeaders.connection.trim().split(/\s*,\s*/); const tokens = inHeaders.connection.trim().split(/\s*,\s*/);
for(const name of tokens) { for(const name of tokens) {
delete headers[name]; delete headers[name];
} }
@ -212,6 +212,11 @@ module.exports = class CachePolicy {
headers.warning = warnings.join(',').trim(); headers.warning = warnings.join(',').trim();
} }
} }
return headers;
}
responseHeaders() {
const headers = this._copyWithoutHopByHopHeaders(this._resHeaders);
headers.age = `${Math.round(this.age())}`; headers.age = `${Math.round(this.age())}`;
return headers; return headers;
} }
@ -348,25 +353,32 @@ module.exports = class CachePolicy {
}; };
} }
revalidationHeaders(incoming_req) { /**
this._assertRequestHasHeaders(incoming_req); * Headers for sending to the origin server to revalidate stale response.
if (!this._resHeaders.etag && !this._resHeaders['last-modified']) { * Allows server to return 304 to allow reuse of the previous response.
return incoming_req.headers; // no validators available *
} * Hop by hop headers are always stripped.
// revalidation allowed via HEAD * Revalidation headers may be added or removed, depending on request.
if (!this._requestMatches(incoming_req, true)) { */
return incoming_req.headers; // not for the same resource 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. */ /* 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) { 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. /* 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) */ 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']; headers['if-modified-since'] = this._resHeaders['last-modified'];
} }

6
package.json

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

81
test/revalidatetest.js

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

Loading…
Cancel
Save