Add configurable query parser

closes #2215
This commit is contained in:
Douglas Christopher Wilson 2014-07-13 23:15:00 -04:00
parent b43205ca98
commit e29fa25bb4
5 changed files with 149 additions and 41 deletions

View File

@ -1,6 +1,11 @@
unreleased unreleased
========== ==========
* configurable query parser with `app.set('query parser', parser)`
- `app.set('query parser', 'extended')` parse with "qs" module
- `app.set('query parser', 'simple')` parse with "querystring" core module
- `app.set('query parser', false)` disable query string parsing
- `app.set('query parser', true)` enable simple parsing
* perf: prevent multiple `Buffer` creation in `res.send` * perf: prevent multiple `Buffer` creation in `res.send`
4.6.1 / 2014-07-12 4.6.1 / 2014-07-12

View File

@ -12,6 +12,7 @@ var debug = require('debug')('express:application');
var View = require('./view'); var View = require('./view');
var http = require('http'); var http = require('http');
var compileETag = require('./utils').compileETag; var compileETag = require('./utils').compileETag;
var compileQueryParser = require('./utils').compileQueryParser;
var compileTrust = require('./utils').compileTrust; var compileTrust = require('./utils').compileTrust;
var deprecate = require('depd')('express'); var deprecate = require('depd')('express');
var resolve = require('path').resolve; var resolve = require('path').resolve;
@ -51,6 +52,7 @@ app.defaultConfiguration = function(){
this.set('etag', 'weak'); this.set('etag', 'weak');
var env = process.env.NODE_ENV || 'development'; var env = process.env.NODE_ENV || 'development';
this.set('env', env); this.set('env', env);
this.set('query parser', 'extended');
this.set('subdomain offset', 2); this.set('subdomain offset', 2);
this.set('trust proxy', false); this.set('trust proxy', false);
@ -104,7 +106,7 @@ app.lazyrouter = function() {
strict: this.enabled('strict routing') strict: this.enabled('strict routing')
}); });
this._router.use(query()); this._router.use(query(this.get('query parser fn')));
this._router.use(middleware.init(this)); this._router.use(middleware.init(this));
} }
}; };
@ -306,6 +308,10 @@ app.set = function(setting, val){
debug('compile etag %s', val); debug('compile etag %s', val);
this.set('etag fn', compileETag(val)); this.set('etag fn', compileETag(val));
break; break;
case 'query parser':
debug('compile query parser %s', val);
this.set('query parser fn', compileQueryParser(val));
break;
case 'trust proxy': case 'trust proxy':
debug('compile trust proxy %s', val); debug('compile trust proxy %s', val);
this.set('trust proxy fn', compileTrust(val)); this.set('trust proxy fn', compileTrust(val));

View File

@ -2,36 +2,27 @@
* Module dependencies. * Module dependencies.
*/ */
var qs = require('qs');
var parseUrl = require('parseurl'); var parseUrl = require('parseurl');
var qs = require('qs');
/** /**
* Query:
*
* Automatically parse the query-string when available,
* populating the `req.query` object using
* [qs](https://github.com/visionmedia/node-querystring).
*
* Examples:
*
* .use(connect.query())
* .use(function(req, res){
* res.end(JSON.stringify(req.query));
* });
*
* The `options` passed are provided to qs.parse function.
*
* @param {Object} options * @param {Object} options
* @return {Function} * @return {Function}
* @api public * @api public
*/ */
module.exports = function query(options){ module.exports = function query(options) {
var queryparse = qs.parse;
if (typeof options === 'function') {
queryparse = options;
options = undefined;
}
return function query(req, res, next){ return function query(req, res, next){
if (!req.query) { if (!req.query) {
req.query = ~req.url.indexOf('?') var val = parseUrl(req).query;
? qs.parse(parseUrl(req).query, options) req.query = queryparse(val, options);
: {};
} }
next(); next();

View File

@ -7,6 +7,8 @@ var crc32 = require('buffer-crc32');
var crypto = require('crypto'); var crypto = require('crypto');
var basename = require('path').basename; var basename = require('path').basename;
var proxyaddr = require('proxy-addr'); var proxyaddr = require('proxy-addr');
var qs = require('qs');
var querystring = require('querystring');
var typer = require('media-typer'); var typer = require('media-typer');
/** /**
@ -202,6 +204,41 @@ exports.compileETag = function(val) {
return fn; return fn;
} }
/**
* Compile "query parser" value to function.
*
* @param {String|Function} val
* @return {Function}
* @api private
*/
exports.compileQueryParser = function compileQueryParser(val) {
var fn;
if (typeof val === 'function') {
return val;
}
switch (val) {
case true:
fn = querystring.parse;
break;
case false:
fn = newObject;
break;
case 'extended':
fn = qs.parse;
break;
case 'simple':
fn = querystring.parse;
break;
default:
throw new TypeError('unknown value for query parser function: ' + val);
}
return fn;
}
/** /**
* Compile "proxy trust" value to function. * Compile "proxy trust" value to function.
* *
@ -252,3 +289,14 @@ exports.setCharset = function(type, charset){
// format type // format type
return typer.format(parsed); return typer.format(parsed);
}; };
/**
* Return new empty objet.
*
* @return {Object}
* @api private
*/
function newObject() {
return {};
}

View File

@ -5,33 +5,91 @@ var express = require('../')
describe('req', function(){ describe('req', function(){
describe('.query', function(){ describe('.query', function(){
it('should default to {}', function(done){ it('should default to {}', function(done){
var app = express(); var app = createApp();
app.use(function(req, res){
req.query.should.eql({});
res.end();
});
request(app) request(app)
.get('/') .get('/')
.end(function(res){ .expect(200, '{}', done);
done(); });
});
})
it('should contain the parsed query-string', function(done){ it('should default to parse complex keys', function (done) {
var app = express(); var app = createApp();
app.use(function(req, res){
req.query.should.eql({ user: { name: 'tj' }});
res.end();
});
request(app) request(app)
.get('/?user[name]=tj') .get('/?user[name]=tj')
.end(function(res){ .expect(200, '{"user":{"name":"tj"}}', done);
done(); });
describe('when "query parser" is extended', function () {
it('should parse complex keys', function (done) {
var app = createApp('extended');
request(app)
.get('/?user[name]=tj')
.expect(200, '{"user":{"name":"tj"}}', done);
}); });
}) });
describe('when "query parser" is simple', function () {
it('should not parse complex keys', function (done) {
var app = createApp('simple');
request(app)
.get('/?user[name]=tj')
.expect(200, '{"user[name]":"tj"}', done);
});
});
describe('when "query parser" is a function', function () {
it('should parse using function', function (done) {
var app = createApp(function (str) {
return {'length': (str || '').length};
});
request(app)
.get('/?user[name]=tj')
.expect(200, '{"length":17}', done);
});
});
describe('when "query parser" disabled', function () {
it('should not parse query', function (done) {
var app = createApp(false);
request(app)
.get('/?user[name]=tj')
.expect(200, '{}', done);
});
});
describe('when "query parser" disabled', function () {
it('should not parse complex keys', function (done) {
var app = createApp(true);
request(app)
.get('/?user[name]=tj')
.expect(200, '{"user[name]":"tj"}', done);
});
});
describe('when "query parser" an unknown value', function () {
it('should throw', function () {
createApp.bind(null, 'bogus').should.throw(/unknown value.*query parser/);
});
});
}) })
}) })
function createApp(setting) {
var app = express();
if (setting !== undefined) {
app.set('query parser', setting);
}
app.use(function (req, res) {
res.send(req.query);
});
return app;
}