Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for overriding settings per request #2935

Open
wants to merge 1 commit into
base: 5.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 20 additions & 5 deletions lib/application.js
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,10 @@ app.handle = function handle(req, res, callback) {
if (!res.locals) {
res.locals = Object.create(null);
}
if (!res.settings) {
res.settings = {};
}
res.settings.__proto__ = this.settings;

this.router.handle(req, res, done);
};
Expand Down Expand Up @@ -226,6 +230,7 @@ app.use = function use(fn) {
fn.handle(req, res, function (err) {
req.__proto__ = orig.request;
res.__proto__ = orig.response;
res.settings.__proto__ = orig.settings;
next(err);
});
});
Expand Down Expand Up @@ -520,13 +525,23 @@ app.render = function render(name, options, callback) {
var opts = options;
var renderOptions = {};
var view;
var self = this;

// support callback function as second arg
if (typeof options === 'function') {
done = options;
opts = {};
}

function settings(key) {
if (typeof options._settings !== 'undefined') {
return options._settings[key];
} else {
return self.get(key);
}
}
var cacheKey = settings('views') + '/' + name;

// merge app.locals
merge(renderOptions, this.locals);

Expand All @@ -545,16 +560,16 @@ app.render = function render(name, options, callback) {

// primed cache
if (renderOptions.cache) {
view = cache[name];
view = cache[cacheKey];
}

// view
if (!view) {
var View = this.get('view');
var View = settings('view');

view = new View(name, {
defaultEngine: this.get('view engine'),
root: this.get('views'),
defaultEngine: settings('view engine'),
root: settings('views'),
engines: engines
});

Expand All @@ -569,7 +584,7 @@ app.render = function render(name, options, callback) {

// prime the cache
if (renderOptions.cache) {
cache[name] = view;
cache[cacheKey] = view;
}
}

Expand Down
14 changes: 8 additions & 6 deletions lib/response.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ res.send = function send(body) {

// populate ETag
var etag;
var generateETag = len !== undefined && app.get('etag fn');
var generateETag = len !== undefined && this.settings['etag fn'];
if (typeof generateETag === 'function' && !this.get('ETag')) {
if ((etag = generateETag(chunk, encoding))) {
this.set('ETag', etag);
Expand Down Expand Up @@ -213,8 +213,8 @@ res.json = function json(obj) {

// settings
var app = this.app;
var replacer = app.get('json replacer');
var spaces = app.get('json spaces');
var replacer = this.settings['json replacer'];
var spaces = this.settings['json spaces'];
var body = JSON.stringify(val, replacer, spaces);

// content-type
Expand Down Expand Up @@ -249,10 +249,10 @@ res.jsonp = function jsonp(obj) {

// settings
var app = this.app;
var replacer = app.get('json replacer');
var spaces = app.get('json spaces');
var replacer = this.settings['json replacer'];
var spaces = this.settings['json spaces'];
var body = JSON.stringify(val, replacer, spaces);
var callback = this.req.query[app.get('jsonp callback name')];
var callback = this.req.query[this.settings['jsonp callback name']];

// content-type
if (!this.get('Content-Type')) {
Expand Down Expand Up @@ -855,6 +855,8 @@ res.render = function render(view, options, callback) {
self.send(str);
};

opts._settings = this.settings;

// render
app.render(view, opts, done);
};
Expand Down
169 changes: 169 additions & 0 deletions test/res.settings.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@

var express = require('..');
var request = require('supertest');

describe('res', function(){
describe('.settings', function(){
it('should override app setting', function(done){
var app = express();
app.set('json spaces', 2);
app.get('/', function(req, res){
res.settings['json spaces'] = 4;
res.json({a:'b'});
});
request(app)
.get('/')
.expect(200, '{\n "a": "b"\n}', done);
})

it('should default to app setting if not overridden', function(done){
var app = express();
app.set('json spaces', 2);
app.get('/', function(req, res){
res.send(res.settings['json spaces']);
});
request(app)
.get('/')
.expect(200, '2', done)
})

describe('when performing a series of requests', function(){
var app = express();
app.set('json spaces', 2);
app.get('/', function(req, res){
res.settings['json spaces'] = 4;
res.json({a:'b'});
});
app.get('/other', function(req, res){
res.json({a:'b'});
});

it('should affect settings on the request it is set', function(done){
request(app)
.get('/')
.expect(200, '{\n "a": "b"\n}', done);
})

it('should not affect settings for other requests', function(done){
request(app)
.get('/other')
.expect(200, '{\n "a": "b"\n}', done);
})
})

describe('when "views" is overridden', function(){
var app = express();
var count = 0;

function View(name, options){
this.name = name;
this.path = options.root;
count++;
}

View.prototype.render = function(options, fn){
fn(null, this.path);
};

app.set('view cache', true);
app.set('views', '/a');
app.set('view', View);

app.get('/:use_b', function(req, res) {
if (req.params.use_b === 'true') {
res.settings['views'] = '/b';
}
res.render('index');
});

it('should render distinct views with the same name and different "views"', function(done){
request(app)
.get('/false')
.expect(200, '/a', function(err, res) {
if (err) return done(err);
count.should.equal(1);
request(app)
.get('/true')
.expect(200, '/b', function(err, res) {
if (err) return done(err);
count.should.equal(2);
done();
});
});
});

it('should cache views with the same name and "views"', function(done){
request(app)
.get('/false')
.expect(200, '/a', function(err, res) {
if (err) return done(err);
count.should.equal(2);
request(app)
.get('/true')
.expect(200, '/b', function(err, res) {
if (err) return done(err);
count.should.equal(2);
done();
});
});
})
})

describe('with mounted app', function(){
it('should default to mounted application settings', function(done){
var app = express();
app.set('json spaces', 2);
var app2 = express();
app2.set('json spaces', 4);
app2.get('/', function(req, res) {
res.json({a:'b'});
});
app.use('/inner', app2);

request(app)
.get('/inner')
.expect(200, '{\n "a": "b"\n}', done);
})

it('should retain any overridden settings from parent', function(done){
var app = express();
app.use('/', function(req, res, next) {
res.settings['json spaces'] = 4;
next();
});
var app2 = express();
app2.get('/', function(req, res) {
res.json({a:'b'});
});
app.use('/inner', app2);

request(app)
.get('/inner')
.expect(200, '{\n "a": "b"\n}', done);
})

it('should default to parent app settings again', function(done){
var app = express();
app.set('json spaces', 2);
var app2 = express();
app2.set('json spaces', 4);
app2.use('/', function(req, res, next) {
res.set('x-ran-inner-middleware', 'true');
next();
});
app2.get('/', function(req, res) {
res.json({a:'b'});
});
app.use('/inner', app2);
app.get('/inner/not_really', function(req, res) {
res.json({c:'d'});
});

request(app)
.get('/inner/not_really')
.expect('x-ran-inner-middleware', 'true')
.expect(200, '{\n "c": "d"\n}', done);
})
})
})
})