Generate ETags for all request responses

closes #2546
This commit is contained in:
Douglas Christopher Wilson 2015-02-18 00:12:28 -05:00
parent e1057bd7fd
commit eaf3318dd3
4 changed files with 158 additions and 133 deletions

View File

@ -1,6 +1,8 @@
3.x
===
* Generate `ETag`s for all request responses
- No longer restricted to only responses for `GET` and `HEAD` requests
* Use `content-type` to parse `Content-Type` headers
* deps: connect@2.29.0
- Use `content-type` to parse `Content-Type` headers

View File

@ -1,6 +1,8 @@
(The MIT License)
Copyright (c) 2009-2013 TJ Holowaychuk <tj@vision-media.ca>
Copyright (c) 2013 Roman Shtylman <shtylman+expressjs@gmail.com>
Copyright (c) 2014-2015 Douglas Christopher Wilson <doug@somethingdoug.com>
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the

View File

@ -1,5 +1,13 @@
/*!
* express
* Copyright(c) 2009-2013 TJ Holowaychuk
* Copyright(c) 2014-2015 Douglas Christopher Wilson
* MIT Licensed
*/
/**
* Module dependencies.
* @api private
*/
var contentDisposition = require('content-disposition');
@ -86,7 +94,6 @@ res.links = function(links){
res.send = function(body){
var req = this.req;
var head = 'HEAD' == req.method;
var type;
var encoding;
var len;
@ -153,12 +160,12 @@ res.send = function(body){
this.set('Content-Length', len);
}
// ETag support
var etag = len !== undefined && app.get('etag fn');
if (etag && ('GET' === req.method || 'HEAD' === req.method)) {
if (!this.get('ETag')) {
etag = etag(body, encoding);
etag && this.set('ETag', etag);
// populate ETag
var etag;
var generateETag = len !== undefined && app.get('etag fn');
if (typeof generateETag === 'function' && !this.get('ETag')) {
if ((etag = generateETag(body, encoding))) {
this.set('ETag', etag);
}
}
@ -173,8 +180,13 @@ res.send = function(body){
body = '';
}
// respond
this.end((head ? null : body), encoding);
if (req.method === 'HEAD') {
// skip body for HEAD
this.end();
} else {
// respond
this.end(body, encoding);
}
return this;
};

View File

@ -1,7 +1,8 @@
var express = require('../')
, request = require('supertest')
, assert = require('assert');
var assert = require('assert');
var express = require('..');
var methods = require('methods');
var request = require('supertest');
describe('res', function(){
describe('.send(null)', function(){
@ -110,10 +111,10 @@ describe('res', function(){
})
})
it('should set ETag', function(done){
it('should set ETag', function (done) {
var app = express();
app.use(function(req, res){
app.use(function (req, res) {
var str = Array(1024 * 2).join('-');
res.send(str);
});
@ -121,24 +122,7 @@ describe('res', function(){
request(app)
.get('/')
.expect('ETag', 'W/"fz/jGo0ONwzb+aKy/rWipg=="')
.end(done);
})
it('should not set ETag for non-GET/HEAD', function(done){
var app = express();
app.use(function(req, res){
var str = Array(1024 * 2).join('-');
res.send(str);
});
request(app)
.post('/')
.end(function(err, res){
if (err) return done(err);
assert(!res.header.etag, 'has an ETag');
done();
});
.expect(200, done);
})
it('should not override Content-Type', function(done){
@ -199,10 +183,10 @@ describe('res', function(){
})
})
it('should set ETag', function(done){
it('should set ETag', function (done) {
var app = express();
app.use(function(req, res){
app.use(function (req, res) {
var str = Array(1024 * 2).join('-');
res.send(new Buffer(str));
});
@ -210,7 +194,7 @@ describe('res', function(){
request(app)
.get('/')
.expect('ETag', 'W/"fz/jGo0ONwzb+aKy/rWipg=="')
.end(done);
.expect(200, done);
})
it('should not override Content-Type', function(done){
@ -358,12 +342,12 @@ describe('res', function(){
.expect('{"foo":"bar"}', done);
})
describe('"etag" setting', function(){
describe('when enabled', function(){
it('should send ETag', function(done){
describe('"etag" setting', function () {
describe('when enabled', function () {
it('should send ETag', function (done) {
var app = express();
app.use(function(req, res){
app.use(function (req, res) {
res.send('kajdslfkasdf');
});
@ -371,76 +355,95 @@ describe('res', function(){
request(app)
.get('/')
.expect('etag', 'W/"c-5aee35d8"', done)
})
it('should send ETag for empty string response', function(done){
var app = express()
app.use(function(req, res){
res.send('')
});
app.enable('etag')
request(app)
.get('/')
.expect('etag', 'W/"0-0"', done)
})
it('should send ETag for long response', function(done){
var app = express();
app.use(function(req, res){
var str = Array(1024 * 2).join('-');
res.send(str);
});
app.enable('etag');
request(app)
.get('/')
.expect('etag', 'W/"fz/jGo0ONwzb+aKy/rWipg=="', done)
.expect('ETag', 'W/"c-5aee35d8"')
.expect(200, done);
});
it('should not override ETag when manually set', function(done){
var app = express();
methods.forEach(function (method) {
if (method === 'connect') return;
app.use(function(req, res){
res.set('etag', '"asdf"');
res.send(200);
});
it('should send ETag in response to ' + method.toUpperCase() + ' request', function (done) {
var app = express();
app.enable('etag');
app[method]('/', function (req, res) {
res.send('kajdslfkasdf');
});
request(app)
.get('/')
.expect('etag', '"asdf"', done)
});
it('should not send ETag for res.send()', function(done){
var app = express()
app.use(function(req, res){
res.send()
});
app.enable('etag')
request(app)
.get('/')
.end(function(err, res){
res.headers.should.not.have.property('etag');
done();
request(app)
[method]('/')
.expect('ETag', 'W/"c-5aee35d8"')
.expect(200, done);
})
});
it('should send ETag for empty string response', function (done) {
var app = express();
app.use(function (req, res) {
res.send('');
});
app.enable('etag');
request(app)
.get('/')
.expect('ETag', 'W/"0-0"')
.expect(200, done);
})
it('should send ETag for long response', function (done) {
var app = express();
app.use(function (req, res) {
var str = Array(1024 * 2).join('-');
res.send(str);
});
app.enable('etag');
request(app)
.get('/')
.expect('ETag', 'W/"fz/jGo0ONwzb+aKy/rWipg=="')
.expect(200, done);
});
it('should not override ETag when manually set', function (done) {
var app = express();
app.use(function (req, res) {
res.set('etag', '"asdf"');
res.send(200);
});
app.enable('etag');
request(app)
.get('/')
.expect('ETag', '"asdf"')
.expect(200, done);
});
it('should not send ETag for res.send()', function (done) {
var app = express();
app.use(function (req, res) {
res.send();
});
app.enable('etag');
request(app)
.get('/')
.expect(shouldNotHaveHeader('ETag'))
.expect(200, done);
})
});
describe('when disabled', function(){
it('should send no ETag', function(done){
describe('when disabled', function () {
it('should send no ETag', function (done) {
var app = express();
app.use(function(req, res){
app.use(function (req, res) {
var str = Array(1024 * 2).join('-');
res.send(str);
});
@ -449,97 +452,103 @@ describe('res', function(){
request(app)
.get('/')
.end(function(err, res){
res.headers.should.not.have.property('etag');
done();
});
.expect(shouldNotHaveHeader('ETag'))
.expect(200, done);
});
it('should send ETag when manually set', function(done){
it('should send ETag when manually set', function (done) {
var app = express();
app.disable('etag');
app.use(function(req, res){
app.use(function (req, res) {
res.set('etag', '"asdf"');
res.send(200);
});
request(app)
.get('/')
.expect('etag', '"asdf"', done)
.expect('ETag', '"asdf"')
.expect(200, done);
});
});
describe('when "strong"', function(){
it('should send strong ETag', function(done){
var app = express()
describe('when "strong"', function () {
it('should send strong ETag', function (done) {
var app = express();
app.set('etag', 'strong');
app.use(function(req, res){
app.use(function (req, res) {
res.send('hello, world!');
});
request(app)
.get('/')
.expect('etag', '"Otu60XkfuuPskIiUxJY4cA=="', done)
.expect('ETag', '"Otu60XkfuuPskIiUxJY4cA=="')
.expect(200, done);
})
})
describe('when "weak"', function(){
it('should send weak ETag', function(done){
var app = express()
describe('when "weak"', function () {
it('should send weak ETag', function (done) {
var app = express();
app.set('etag', 'weak');
app.use(function(req, res){
app.use(function (req, res) {
res.send('hello, world!');
});
request(app)
.get('/')
.expect('etag', 'W/"d-58988d13"', done)
.expect('ETag', 'W/"d-58988d13"')
.expect(200, done)
})
})
describe('when a function', function(){
it('should send custom ETag', function(done){
var app = express()
describe('when a function', function () {
it('should send custom ETag', function (done) {
var app = express();
app.set('etag', function(body, encoding){
body.should.equal('hello, world!')
encoding.should.equal('utf8')
return '"custom"'
app.set('etag', function (body, encoding) {
body.should.equal('hello, world!');
encoding.should.equal('utf8');
return '"custom"';
});
app.use(function(req, res){
app.use(function (req, res) {
res.send('hello, world!');
});
request(app)
.get('/')
.expect('etag', '"custom"', done)
.expect('ETag', '"custom"')
.expect(200, done);
})
it('should not send falsy ETag', function(done){
var app = express()
it('should not send falsy ETag', function (done) {
var app = express();
app.set('etag', function(body, encoding){
return undefined
app.set('etag', function (body, encoding) {
return undefined;
});
app.use(function(req, res){
app.use(function (req, res) {
res.send('hello, world!');
});
request(app)
.get('/')
.end(function(err, res){
res.headers.should.not.have.property('etag')
done();
})
.expect(shouldNotHaveHeader('ETag'))
.expect(200, done);
})
})
})
})
function shouldNotHaveHeader(header) {
return function (res) {
assert.ok(!(header.toLowerCase() in res.headers), 'should not have header ' + header)
}
}