diff --git a/bin/report-latency b/bin/report-latency index 896834f4d..21dbc9255 100755 --- a/bin/report-latency +++ b/bin/report-latency @@ -10,214 +10,210 @@ var sprintf = require('util').format; var nopt = require('nopt'); - - ///--- Globals var BUCKETS = {}; var REQUEST_IDS = {}; var OPTS = { - 'average': Boolean, - 'help': Boolean, - 'end': Date, - 'max-latency': Number, - 'max-requests': Number, - 'output': String, - 'percentile': [Number, Array], - 'period': Number, - 'requests': Boolean, - 'start': Date + 'average': Boolean, + 'help': Boolean, + 'end': Date, + 'max-latency': Number, + 'max-requests': Number, + 'output': String, + 'percentile': [Number, Array], + 'period': Number, + 'requests': Boolean, + 'start': Date }; var SHORT_OPTS = { - 'a': ['--average'], - 'h': ['--help'], - 'i': ['--period'], - 'e': ['--end'], - 'l': ['--max-latency'], - 'n': ['--max-requests'], - 'o': ['--output'], - 'p': ['--percentile'], - 'r': ['--requests'], - 's': ['--start'] + 'a': ['--average'], + 'h': ['--help'], + 'i': ['--period'], + 'e': ['--end'], + 'l': ['--max-latency'], + 'n': ['--max-requests'], + 'o': ['--output'], + 'p': ['--percentile'], + 'r': ['--requests'], + 's': ['--start'] }; - - ///--- Functions function percentile(p, vals) { - p = parseInt(p, 10); - return vals[(Math.round(((p/100) * vals.length) + 1/2) - 1)].latency; + p = parseInt(p, 10); + return vals[(Math.round(((p / 100) * vals.length) + 1 / 2) - 1)].latency; } function report(buckets, output) { - Object.keys(buckets).sort(function (a, b) { - return parseInt(a, 10) - parseInt(b, 10); - }).forEach(function (k) { - var avg = 0; - var perc = []; - var req = buckets[k].length; - var sum = 0; - var t = Math.round(buckets[k]._time); - - buckets[k] = buckets[k].sort(function (a, b) { - return a.latency - b.latency; + Object.keys(buckets).sort(function (a, b) { + return parseInt(a, 10) - parseInt(b, 10); + }).forEach(function (k) { + var avg = 0; + var perc = []; + var req = buckets[k].length; + var sum = 0; + var t = Math.round(buckets[k]._time); + + buckets[k] = buckets[k].sort(function (a, b) { + return a.latency - b.latency; + }); + + buckets[k].forEach(function (v) { + sum += v.latency; + }); + + if (sum > 0 && req > 0) { + if (output.average) + output.average.push([t, Math.round(sum / req)]); + if (output.requests) + output.requests.push([t, buckets[k].length]); + Object.keys(output.percentile).forEach(function (p) { + var _p = percentile(p, buckets[k]); + output.percentile[p].push([t, _p]); }); - - buckets[k].forEach(function (v) { - sum += v.latency; - }); - - if (sum > 0 && req > 0) { - if (output.average) - output.average.push([t, Math.round(sum/req)]); - if (output.requests) - output.requests.push([t, buckets[k].length]); - Object.keys(output.percentile).forEach(function (p) { - var _p = percentile(p, buckets[k]); - output.percentile[p].push([t, _p]); - }); - } + } }); - return output; + return output; } function usage(code, message) { - var str = ''; - Object.keys(SHORT_OPTS).forEach(function(k) { - if (!Array.isArray(SHORT_OPTS[k])) - return; - - var opt = SHORT_OPTS[k][0].replace('--', ''); - var type = OPTS[opt].name || 'string'; - if (type && type === 'boolean') - type = ''; - type = type.toLowerCase(); - - str += ' [--' + opt + ' ' + type + ']'; - }); - str += ' [file ...]'; - - if (message) - console.error(message); - - console.error('usage: ' + path.basename(process.argv[1]) + str); - process.exit(code); + var str = ''; + Object.keys(SHORT_OPTS).forEach(function (k) { + if (!Array.isArray(SHORT_OPTS[k])) + return; + + var opt = SHORT_OPTS[k][0].replace('--', ''); + var type = OPTS[opt].name || 'string'; + if (type && type === 'boolean') + type = ''; + type = type.toLowerCase(); + + str += ' [--' + opt + ' ' + type + ']'; + }); + str += ' [file ...]'; + + if (message) + console.error(message); + + console.error('usage: ' + path.basename(process.argv[1]) + str); + process.exit(code); } - ///--- Mainline var parsed; try { - parsed = nopt(OPTS, SHORT_OPTS, process.argv, 2); + parsed = nopt(OPTS, SHORT_OPTS, process.argv, 2); } catch (e) { - usage(1, e.toString()); + usage(1, e.toString()); } if (parsed.help) - usage(0); + usage(0); if (!parsed.average && !parsed.percentile) - usage(1, '--average or --percentile required'); + usage(1, '--average or --percentile required'); if (parsed.argv.remain.length < 1) - usage(1, 'log file required'); + usage(1, 'log file required'); var config = { - average: parsed.average || false, - maxLatency: parsed['max-latency'] || 1000, - maxRequests: parsed['max-requests'] || 10000, - percentile: parsed.percentile || [], - period: parsed.period || 60, - requests: parsed.requests || false, - start: parsed.start ? (parsed.start.getTime() / 1000) : 0, - end: parsed.end ? (parsed.end.getTime() / 1000) : Number.MAX_VALUE + average: parsed.average || false, + maxLatency: parsed['max-latency'] || 1000, + maxRequests: parsed['max-requests'] || 10000, + percentile: parsed.percentile || [], + period: parsed.period || 60, + requests: parsed.requests || false, + start: parsed.start ? (parsed.start.getTime() / 1000) : 0, + end: parsed.end ? (parsed.end.getTime() / 1000) : Number.MAX_VALUE }; var buckets = {}; var done = 0; parsed.argv.remain.forEach(function (f) { - var stream = readline.createInterface({ - input: fs.createReadStream(f), - output: null - }) - stream.on('line', function (l) { - var record; - var t = -1; - - try { - record = JSON.parse(l); - } catch (e) {} - - if (!record) - return; - - var t = -1; - if (record.time) - t = (new Date(record.time).getTime() / 1000); - - if (record._audit !== true || - REQUEST_IDS[record.req_id] || - t < config.start || - t > config.end) { - - console.error('Skipping %s', l); - } - - REQUEST_IDS[record.req_id] = true; - record.time = t; - - var b = Math.round(record.time / config.period) + ''; - if (!buckets[b]) - buckets[b] = []; - - buckets[b].push(record); - buckets[b]._time = record.time // good enough - }); + var stream = readline.createInterface({ + input: fs.createReadStream(f), + output: null + }) + stream.on('line', function (l) { + var record; + var t = -1; + + try { + record = JSON.parse(l); + } catch (e) { + } + + if (!record) + return; + + var t = -1; + if (record.time) + t = (new Date(record.time).getTime() / 1000); + + if (record._audit !== true || + REQUEST_IDS[record.req_id] || + t < config.start || + t > config.end) { + + console.error('Skipping %s', l); + } + + REQUEST_IDS[record.req_id] = true; + record.time = t; + + var b = Math.round(record.time / config.period) + ''; + if (!buckets[b]) + buckets[b] = []; + + buckets[b].push(record); + buckets[b]._time = record.time // good enough + }); + + stream.on('end', function () { + if (++done === parsed.argv.remain.length) { + console.error('Generating report...'); + + var output = { + average: config.average ? [] : false, + requests: config.requests ? [] : false, + percentile: {} + }; + config.percentile.forEach(function (p) { + output.percentile[p] = []; + }); + + output = report(buckets, output); + var finalOutput = []; + if (output.average) { + finalOutput.push({ + name: 'avg', + values: output.average + }); + } + if (output.requests) { + finalOutput.push({ + name: 'n', + values: output.requests + }); + } + Object.keys(output.percentile).forEach(function (k) { + finalOutput.push({ + name: 'p' + k, + values: output.percentile[k] + }); + }); - stream.on('end', function () { - if (++done === parsed.argv.remain.length) { - console.error('Generating report...'); - - var output = { - average: config.average ? [] : false, - requests: config.requests ? [] : false, - percentile: {} - }; - config.percentile.forEach(function (p) { - output.percentile[p] = []; - }); - - output = report(buckets, output); - var finalOutput = []; - if (output.average) { - finalOutput.push({ - name: 'avg', - values: output.average - }); - } - if (output.requests) { - finalOutput.push({ - name: 'n', - values: output.requests - }); - } - Object.keys(output.percentile).forEach(function (k) { - finalOutput.push({ - name: 'p' + k, - values: output.percentile[k] - }); - }); - - console.log(JSON.stringify(finalOutput)); - } - }); + console.log(JSON.stringify(finalOutput)); + } + }); }); diff --git a/examples/CORS/cors.js b/examples/CORS/cors.js index c88b2ac7e..bc2264513 100644 --- a/examples/CORS/cors.js +++ b/examples/CORS/cors.js @@ -4,8 +4,8 @@ var srv = restify.createServer(); srv.use(restify.CORS()); function foo(req, res, next) { - res.send(204); - next(); + res.send(204); + next(); } srv.put('/foo', foo); diff --git a/examples/bench/bench.js b/examples/bench/bench.js index b0af25f0c..dc0e28968 100644 --- a/examples/bench/bench.js +++ b/examples/bench/bench.js @@ -2,10 +2,10 @@ var server = require('../../lib').createServer(); //var server = require('express')(); server.get('/echo/:name', function (req, res, next) { - res.setHeader('content-type', 'text/plain'); - res.send(200, req.params.name); + res.setHeader('content-type', 'text/plain'); + res.send(200, req.params.name); }); server.listen(8080, function () { - console.log('ready'); + console.log('ready'); }); \ No newline at end of file diff --git a/examples/dtrace/demo.js b/examples/dtrace/demo.js index 09dea0b44..efb58e5a8 100644 --- a/examples/dtrace/demo.js +++ b/examples/dtrace/demo.js @@ -71,53 +71,51 @@ var restify = require('../../lib'); var Logger = require('bunyan'); - ///--- Globals var NAME = 'exampleapp'; - ///--- Mainline var log = new Logger({ - name: NAME, - level: 'trace', - service: NAME, - serializers: restify.bunyan.serializers + name: NAME, + level: 'trace', + service: NAME, + serializers: restify.bunyan.serializers }); var server = restify.createServer({ - name: NAME, - Logger: log, - formatters: { - 'application/foo': function(req, res, body) { - if (body instanceof Error) { - body = body.stack; - } else if (Buffer.isBuffer(body)) { - body = body.toString('base64'); - } else { - switch (typeof(body)) { - case 'boolean': - case 'number': - case 'string': - body = body.toString(); - break; - - case 'undefined': - body = ''; - break; - - default: - body = body === null ? '' : - JSON.stringify(body); - break; - } - - } - return body; + name: NAME, + Logger: log, + formatters: { + 'application/foo': function (req, res, body) { + if (body instanceof Error) { + body = body.stack; + } else if (Buffer.isBuffer(body)) { + body = body.toString('base64'); + } else { + switch (typeof(body)) { + case 'boolean': + case 'number': + case 'string': + body = body.toString(); + break; + + case 'undefined': + body = ''; + break; + + default: + body = body === null ? '' : + JSON.stringify(body); + break; } + + } + return body; } + } }); server.use(restify.acceptParser(server.acceptable)); @@ -127,50 +125,52 @@ server.use(restify.queryParser()); server.use(restify.urlEncodedBodyParser()); server.use(function slowHandler(req, res, next) { - setTimeout(function() { next(); }, 250); + setTimeout(function () { + next(); + }, 250); }); server.get({url: '/foo/:id', name: 'GetFoo'}, function (req, res, next) { - next(); + next(); }, function sendResult(req, res, next) { - res.send({ - hello: req.params.id - }); - next(); + res.send({ + hello: req.params.id + }); + next(); }); server.head('/foo/:id', function (req, res, next) { - res.send({ - hello: req.params.id - }); - next(); + res.send({ + hello: req.params.id + }); + next(); }); server.put('/foo/:id', function (req, res, next) { - res.send({ - hello: req.params.id - }); - next(); + res.send({ + hello: req.params.id + }); + next(); }); server.post('/foo/:id', function (req, res, next) { - res.json(201, req.params); - next(); + res.json(201, req.params); + next(); }); server.del('/foo/:id', function (req, res, next) { - res.send(204); - next(); + res.send(204); + next(); }); -server.on('after', function(req, res, name) { - req.log.info('%s just finished: %d.', name, res.code); +server.on('after', function (req, res, name) { + req.log.info('%s just finished: %d.', name, res.code); }); -server.on('NotFound', function(req, res) { - res.send(404, req.url + ' was not found'); +server.on('NotFound', function (req, res) { + res.send(404, req.url + ' was not found'); }); -server.listen(9080, function() { - log.info('listening: %s', server.url); +server.listen(9080, function () { + log.info('listening: %s', server.url); }); diff --git a/examples/dtrace/hello.js b/examples/dtrace/hello.js index 7773d59bb..c6ead0ef9 100644 --- a/examples/dtrace/hello.js +++ b/examples/dtrace/hello.js @@ -1,7 +1,7 @@ var restify = require('../lib'); var server = restify.createServer({ - name: 'helloworld' + name: 'helloworld' }); server.use(restify.acceptParser(server.acceptable)); @@ -11,21 +11,21 @@ server.use(restify.queryParser()); server.use(restify.urlEncodedBodyParser()); server.use(function slowHandler(req, res, next) { - setTimeout(function() { - next(); - }, 250); + setTimeout(function () { + next(); + }, 250); }); server.get({ - path: '/hello/:name', - name: 'GetFoo' + path: '/hello/:name', + name: 'GetFoo' }, function respond(req, res, next) { - res.send({ - hello: req.params.name - }); - next(); + res.send({ + hello: req.params.name + }); + next(); }); -server.listen(8080, function() { - console.log('listening: %s', server.url); +server.listen(8080, function () { + console.log('listening: %s', server.url); }); \ No newline at end of file diff --git a/examples/jsonp/jsonp.js b/examples/jsonp/jsonp.js index 2ffb748e9..547b68344 100644 --- a/examples/jsonp/jsonp.js +++ b/examples/jsonp/jsonp.js @@ -4,10 +4,10 @@ var srv = restify.createServer(); srv.use(restify.queryParser()); srv.use(restify.jsonp()); srv.get('/', function (req, res, next) { - res.send({hello: 'world'}); - next(); + res.send({hello: 'world'}); + next(); }); srv.listen(8080, function () { - console.log('ready on %s', srv.url); + console.log('ready on %s', srv.url); }); \ No newline at end of file diff --git a/examples/sockio/sockio.js b/examples/sockio/sockio.js index d637c22ef..21f67c46e 100644 --- a/examples/sockio/sockio.js +++ b/examples/sockio/sockio.js @@ -6,28 +6,26 @@ var socketio = require('socket.io'); var restify = require('../lib'); - ///--- Globals var IP = (function () { - var nics = os.networkInterfaces(); - var keys = Object.keys(nics).filter(function (k) { - return (nics[k].some(function (n) { - return (!n.internal); - })); - }); - return (nics[keys.pop()].pop().address); + var nics = os.networkInterfaces(); + var keys = Object.keys(nics).filter(function (k) { + return (nics[k].some(function (n) { + return (!n.internal); + })); + }); + return (nics[keys.pop()].pop().address); })(); var HTML = '\n' + - ''; - + ''; ///--- Mainline @@ -36,22 +34,22 @@ var server = restify.createServer(); var io = socketio.listen(server); server.get('/', function indexHTML(req, res, next) { - res.setHeader('Content-Type', 'text/html'); - res.setHeader('Content-Length', Buffer.byteLength(HTML)); - res.writeHead(200); - res.write(HTML); - res.end(); - next(); + res.setHeader('Content-Type', 'text/html'); + res.setHeader('Content-Length', Buffer.byteLength(HTML)); + res.writeHead(200); + res.write(HTML); + res.end(); + next(); }); io.sockets.on('connection', function (socket) { - socket.emit('news', { hello: 'world' }); - socket.on('my other event', function (data) { - console.log(data); - }); + socket.emit('news', { hello: 'world' }); + socket.on('my other event', function (data) { + console.log(data); + }); }); server.listen(8080, IP, function () { - console.log('socket.io server listening at %s', server.url); + console.log('socket.io server listening at %s', server.url); }); diff --git a/examples/spdy/spdy.js b/examples/spdy/spdy.js index c56b3361e..47c8cad5c 100644 --- a/examples/spdy/spdy.js +++ b/examples/spdy/spdy.js @@ -3,26 +3,26 @@ var bunyan = require('bunyan'); var restify = require('../../lib'); var srv = restify.createServer({ - spdy: { - cert: fs.readFileSync('./keys/spdy-cert.pem'), - key: fs.readFileSync('./keys/spdy-key.pem'), - ca: fs.readFileSync('keys/spdy-csr.pem') - } + spdy: { + cert: fs.readFileSync('./keys/spdy-cert.pem'), + key: fs.readFileSync('./keys/spdy-key.pem'), + ca: fs.readFileSync('keys/spdy-csr.pem') + } }); srv.get('/', function (req, res, next) { - res.send({hello: 'world'}); - next(); + res.send({hello: 'world'}); + next(); }); srv.on('after', restify.auditLogger({ - body: true, - log: bunyan.createLogger({ - name: 'audit', - stream: process.stdout - }) + body: true, + log: bunyan.createLogger({ + name: 'audit', + stream: process.stdout + }) })); srv.listen(8080, function () { - console.log('ready on %s', srv.url); + console.log('ready on %s', srv.url); }); diff --git a/examples/todoapp/lib/client.js b/examples/todoapp/lib/client.js index 0fef9b304..7d7956360 100644 --- a/examples/todoapp/lib/client.js +++ b/examples/todoapp/lib/client.js @@ -8,130 +8,127 @@ var getopt = require('posix-getopt'); var restify = require('restify'); - ///--- Globals var sprintf = util.format; - ///--- API function TodoClient(options) { - assert.object(options, 'options'); - assert.object(options.log, 'options.log'); - assert.optionalString(options.socketPath, 'options.socketPath'); - assert.optionalString(options.url, 'options.url'); - assert.optionalString(options.version, 'options.version'); - - var self = this; - var ver = options.version || '~1.0'; - - this.client = restify.createClient({ - log: options.log, - name: 'TodoClient', - socketPath: options.socketPath, - type: 'json', - url: options.url, - version: ver - }); - this.log = options.log.child({component: 'TodoClient'}, true); - this.url = options.url; - this.version = ver; - - if (options.username && options.password) { - this.username = options.username; - this.client.basicAuth(options.username, options.password); - } + assert.object(options, 'options'); + assert.object(options.log, 'options.log'); + assert.optionalString(options.socketPath, 'options.socketPath'); + assert.optionalString(options.url, 'options.url'); + assert.optionalString(options.version, 'options.version'); + + var self = this; + var ver = options.version || '~1.0'; + + this.client = restify.createClient({ + log: options.log, + name: 'TodoClient', + socketPath: options.socketPath, + type: 'json', + url: options.url, + version: ver + }); + this.log = options.log.child({component: 'TodoClient'}, true); + this.url = options.url; + this.version = ver; + + if (options.username && options.password) { + this.username = options.username; + this.client.basicAuth(options.username, options.password); + } } TodoClient.prototype.create = function create(task, cb) { - assert.string(task, 'task'); - assert.func(cb, 'callback'); - - this.client.post('/todo', {task: task}, function (err, req, res, obj) { - if (err) { - cb(err); - } else { - cb(null, obj); - } - }); + assert.string(task, 'task'); + assert.func(cb, 'callback'); + + this.client.post('/todo', {task: task}, function (err, req, res, obj) { + if (err) { + cb(err); + } else { + cb(null, obj); + } + }); }; TodoClient.prototype.list = function list(cb) { - assert.func(cb, 'callback'); - - this.client.get('/todo', function (err, req, res, obj) { - if (err) { - cb(err); - } else { - cb(null, obj); - } - }); + assert.func(cb, 'callback'); + + this.client.get('/todo', function (err, req, res, obj) { + if (err) { + cb(err); + } else { + cb(null, obj); + } + }); }; TodoClient.prototype.get = function get(name, cb) { - assert.string(name, 'name'); - assert.func(cb, 'callback'); - - this.client.get('/todo/' + name, function (err, req, res, obj) { - if (err) { - cb(err); - } else { - cb(null, obj); - } - }); + assert.string(name, 'name'); + assert.func(cb, 'callback'); + + this.client.get('/todo/' + name, function (err, req, res, obj) { + if (err) { + cb(err); + } else { + cb(null, obj); + } + }); }; TodoClient.prototype.update = function update(todo, cb) { - assert.object(todo, 'todo'); - assert.func(cb, 'callback'); - - this.client.put('/todo/' + todo.name, todo, function (err) { - if (err) { - cb(err); - } else { - cb(null); - } - }); + assert.object(todo, 'todo'); + assert.func(cb, 'callback'); + + this.client.put('/todo/' + todo.name, todo, function (err) { + if (err) { + cb(err); + } else { + cb(null); + } + }); }; TodoClient.prototype.del = function del(name, cb) { - if (typeof (name) === 'function') { - cb = name; - name = ''; + if (typeof (name) === 'function') { + cb = name; + name = ''; + } + assert.string(name, 'name'); + assert.func(cb, 'callback'); + + var p = '/todo' + (name.length > 0 ? '/' + name : ''); + this.client.del(p, function (err) { + if (err) { + cb(err); + } else { + cb(null); } - assert.string(name, 'name'); - assert.func(cb, 'callback'); - - var p = '/todo' + (name.length > 0 ? '/' + name : ''); - this.client.del(p, function (err) { - if (err) { - cb(err); - } else { - cb(null); - } - }); + }); }; TodoClient.prototype.toString = function toString() { - var str = sprintf('[object TodoClient this little hackery - // just ensures that we're never < TRACE - LOG.level(Math.max(bunyan.TRACE, (LOG.level() - 10))); - if (LOG.level() <= bunyan.DEBUG) - LOG = LOG.child({src: true}); - break; - - case 'z': - opts.password = option.optarg; - break; - - default: - usage('invalid option: ' + option.option); - break; - } + var option; + var opts = {} + var parser = new getopt.BasicParser('hvd:p:u:z:', process.argv); + + while ((option = parser.getopt()) !== undefined) { + switch (option.option) { + case 'd': + opts.directory = path.normalize(option.optarg); + break; + + case 'h': + usage(); + break; + + case 'p': + opts.port = parseInt(option.optarg, 10); + break; + + case 'u': + opts.user = option.optarg; + break; + + case 'v': + // Allows us to set -vvv -> this little hackery + // just ensures that we're never < TRACE + LOG.level(Math.max(bunyan.TRACE, (LOG.level() - 10))); + if (LOG.level() <= bunyan.DEBUG) + LOG = LOG.child({src: true}); + break; + + case 'z': + opts.password = option.optarg; + break; + + default: + usage('invalid option: ' + option.option); + break; } + } - return (opts); + return (opts); } function usage(msg) { - if (msg) - console.error(msg); - - var str = 'usage: ' + - NAME + - ' [-v] [-d dir] [-p port] [-u user] [-z password]'; - console.error(str); - process.exit(msg ? 1 : 0); + if (msg) + console.error(msg); + + var str = 'usage: ' + + NAME + + ' [-v] [-d dir] [-p port] [-u user] [-z password]'; + console.error(str); + process.exit(msg ? 1 : 0); } - ///--- Mainline (function main() { - var options = parseOptions(); - - LOG.debug(options, 'command line arguments parsed'); - - // First setup our 'database' - var dir = path.normalize((options.directory || '/tmp') + '/todos'); - try { - fs.mkdirSync(dir); - } catch (e) { - if (e.code !== 'EEXIST') { - LOG.fatal(e, 'unable to create "database" %s', dir); - process.exit(1); - } + var options = parseOptions(); + + LOG.debug(options, 'command line arguments parsed'); + + // First setup our 'database' + var dir = path.normalize((options.directory || '/tmp') + '/todos'); + try { + fs.mkdirSync(dir); + } catch (e) { + if (e.code !== 'EEXIST') { + LOG.fatal(e, 'unable to create "database" %s', dir); + process.exit(1); } + } - var server = todo.createServer({ - directory: dir, - log: LOG - }); + var server = todo.createServer({ + directory: dir, + log: LOG + }); - // At last, let's rock and roll - server.listen((options.port || 8080), function onListening() { - LOG.info('listening at %s', server.url); - }); + // At last, let's rock and roll + server.listen((options.port || 8080), function onListening() { + LOG.info('listening at %s', server.url); + }); })(); diff --git a/examples/todoapp/test/todo.test.js b/examples/todoapp/test/todo.test.js index b67738cbc..13ddd5e23 100644 --- a/examples/todoapp/test/todo.test.js +++ b/examples/todoapp/test/todo.test.js @@ -9,7 +9,6 @@ var restify = require('restify'); var todo = require('../lib'); - ///--- Globals var CLIENT; @@ -18,121 +17,120 @@ var SERVER; var SOCK = '/tmp/.todo_sock'; - ///--- Tests exports.setup = function (t) { - var log = bunyan.createLogger({ - name: 'todo_unit_test', - level: process.env.LOG_LEVEL || 'info', - serializers: restify.bunyan.serializers, - stream: process.stdout + var log = bunyan.createLogger({ + name: 'todo_unit_test', + level: process.env.LOG_LEVEL || 'info', + serializers: restify.bunyan.serializers, + stream: process.stdout + }); + + + fs.mkdir(DIR, function (err) { + if (err && err.code !== 'EEXIST') { + console.error('unable to mkdir: ' + err.stack); + process.exit(1); + } + + SERVER = todo.createServer({ + directory: DIR, + log: log.child({component: 'server'}, true), + noAudit: true }); - - fs.mkdir(DIR, function (err) { - if (err && err.code !== 'EEXIST') { - console.error('unable to mkdir: ' + err.stack); - process.exit(1); - } - - SERVER = todo.createServer({ - directory: DIR, - log: log.child({component: 'server'}, true), - noAudit: true - }); - - t.ok(SERVER); - SERVER.listen(SOCK, function () { - CLIENT = todo.createClient({ - log: log.child({component: 'client'}, true), - socketPath: SOCK - }); - t.ok(CLIENT); - t.done(); - }); + t.ok(SERVER); + SERVER.listen(SOCK, function () { + CLIENT = todo.createClient({ + log: log.child({component: 'client'}, true), + socketPath: SOCK + }); + t.ok(CLIENT); + t.done(); }); + }); }; exports.listEmpty = function (t) { - CLIENT.list(function (err, todos) { - t.ifError(err); - t.ok(todos); - t.ok(Array.isArray(todos)); - if (todos) - t.equal(todos.length, 0); - t.done(); - }); + CLIENT.list(function (err, todos) { + t.ifError(err); + t.ok(todos); + t.ok(Array.isArray(todos)); + if (todos) + t.equal(todos.length, 0); + t.done(); + }); }; exports.create = function (t) { - var task = 'check that unit test works'; - CLIENT.create(task, function (err, todo) { - t.ifError(err); - t.ok(todo); - if (todo) { - t.ok(todo.name); - t.equal(todo.task, task); - } - t.done(); - }); + var task = 'check that unit test works'; + CLIENT.create(task, function (err, todo) { + t.ifError(err); + t.ok(todo); + if (todo) { + t.ok(todo.name); + t.equal(todo.task, task); + } + t.done(); + }); }; exports.listAndGet = function (t) { - CLIENT.list(function (err, todos) { - t.ifError(err); - t.ok(todos); - t.ok(Array.isArray(todos)); - if (todos) { - t.equal(todos.length, 1); - CLIENT.get(todos[0], function (err2, todo) { - t.ifError(err2); - t.ok(todo); - t.done(); - }); - } else { - t.done(); - } - }); + CLIENT.list(function (err, todos) { + t.ifError(err); + t.ok(todos); + t.ok(Array.isArray(todos)); + if (todos) { + t.equal(todos.length, 1); + CLIENT.get(todos[0], function (err2, todo) { + t.ifError(err2); + t.ok(todo); + t.done(); + }); + } else { + t.done(); + } + }); }; exports.update = function (t) { - CLIENT.list(function (err, todos) { - t.ifError(err); - t.ok(todos); - t.ok(Array.isArray(todos)); - if (todos) { - t.equal(todos.length, 1); - - var todo = { - name: todos[0], - task: 'something else' - }; - CLIENT.update(todo, function (err2) { - t.ifError(err2); - t.done(); - }); - } else { - t.done(); - } - }); + CLIENT.list(function (err, todos) { + t.ifError(err); + t.ok(todos); + t.ok(Array.isArray(todos)); + if (todos) { + t.equal(todos.length, 1); + + var todo = { + name: todos[0], + task: 'something else' + }; + CLIENT.update(todo, function (err2) { + t.ifError(err2); + t.done(); + }); + } else { + t.done(); + } + }); }; exports.teardown = function teardown(t) { - CLIENT.del(function (err) { - t.ifError(err); + CLIENT.del(function (err) { + t.ifError(err); - SERVER.once('close', function () { - fs.rmdir(DIR, function (err) { - t.ifError(err); - t.done(); - }); - }); - SERVER.close(); + SERVER.once('close', function () { + fs.rmdir(DIR, function (err) { + t.ifError(err); + t.done(); + }); }); + SERVER.close(); + }); }; diff --git a/lib/bunyan_helper.js b/lib/bunyan_helper.js index c421d83c6..7c6616e06 100644 --- a/lib/bunyan_helper.js +++ b/lib/bunyan_helper.js @@ -9,7 +9,6 @@ var LRU = require('lru-cache'); var uuid = require('node-uuid'); - ///--- Globals var sprintf = util.format; @@ -17,23 +16,22 @@ var DEFAULT_REQ_ID = uuid.v4(); var STR_FMT = '[object %s]'; - ///--- Helpers function appendStream(streams, s) { - assert.arrayOfObject(streams, 'streams'); - assert.object(s, 'stream'); + assert.arrayOfObject(streams, 'streams'); + assert.object(s, 'stream'); - if (s instanceof Stream) { - streams.push({ - raw: false, - stream: s - }); - } else { - assert.optionalBool(s.raw, 'stream.raw'); - assert.object(s.stream, 'stream.stream'); - streams.push(s); - } + if (s instanceof Stream) { + streams.push({ + raw: false, + stream: s + }); + } else { + assert.optionalBool(s.raw, 'stream.raw'); + assert.object(s.stream, 'stream.stream'); + streams.push(s); + } } @@ -58,176 +56,178 @@ function appendStream(streams, s) { * a "req_id" field. Default false. */ function RequestCaptureStream(opts) { - assert.object(opts, 'options'); - assert.optionalObject(opts.stream, 'options.stream'); - assert.optionalArrayOfObject(opts.streams, 'options.streams'); - assert.optionalNumber(opts.level, 'options.level'); - assert.optionalNumber(opts.maxRecords, 'options.maxRecords'); - assert.optionalNumber(opts.maxRequestIds, 'options.maxRequestIds'); - assert.optionalBool(opts.dumpDefault, 'options.dumpDefault'); - - var self = this; - Stream.call(this); - - this.level = opts.level ? bunyan.resolveLevel(opts.level) : bunyan.WARN; - this.limit = opts.maxRecords || 100; - this.maxRequestIds = opts.maxRequestIds || 1000; - this.requestMap = LRU({ - max: self.maxRequestIds - }); - this.dumpDefault = opts.dumpDefault; - - this._offset = -1; - this._rings = []; - - this.streams = []; - - if (opts.stream) - appendStream(this.streams, opts.stream); - - if (opts.streams) - opts.streams.forEach(appendStream.bind(null, this.streams)); - - this.haveNonRawStreams = false; - for (var i = 0; i < this.streams.length; i++) { - if (!this.streams[i].raw) { - this.haveNonRawStreams = true; - break; - } + assert.object(opts, 'options'); + assert.optionalObject(opts.stream, 'options.stream'); + assert.optionalArrayOfObject(opts.streams, 'options.streams'); + assert.optionalNumber(opts.level, 'options.level'); + assert.optionalNumber(opts.maxRecords, 'options.maxRecords'); + assert.optionalNumber(opts.maxRequestIds, 'options.maxRequestIds'); + assert.optionalBool(opts.dumpDefault, 'options.dumpDefault'); + + var self = this; + Stream.call(this); + + this.level = opts.level ? bunyan.resolveLevel(opts.level) : bunyan.WARN; + this.limit = opts.maxRecords || 100; + this.maxRequestIds = opts.maxRequestIds || 1000; + this.requestMap = LRU({ + max: self.maxRequestIds + }); + this.dumpDefault = opts.dumpDefault; + + this._offset = -1; + this._rings = []; + + this.streams = []; + + if (opts.stream) + appendStream(this.streams, opts.stream); + + if (opts.streams) + opts.streams.forEach(appendStream.bind(null, this.streams)); + + this.haveNonRawStreams = false; + for (var i = 0; i < this.streams.length; i++) { + if (!this.streams[i].raw) { + this.haveNonRawStreams = true; + break; } + } } util.inherits(RequestCaptureStream, Stream); RequestCaptureStream.prototype.write = function write(record) { - var req_id = record.req_id || DEFAULT_REQ_ID; - var ring; - var self = this; - - if (!(ring = this.requestMap.get(req_id))) { - if (++this._offset > this.maxRequestIds) - this._offset = 0; - - if (this._rings.length <= this._offset) { - this._rings.push(new bunyan.RingBuffer({ - limit: self.limit - })); - } - - ring = this._rings[this._offset]; - ring.records.length = 0; - this.requestMap.set(req_id, ring); + var req_id = record.req_id || DEFAULT_REQ_ID; + var ring; + var self = this; + + if (!(ring = this.requestMap.get(req_id))) { + if (++this._offset > this.maxRequestIds) + this._offset = 0; + + if (this._rings.length <= this._offset) { + this._rings.push(new bunyan.RingBuffer({ + limit: self.limit + })); } - assert.ok(ring, 'no ring found'); - - if (record.level >= this.level) { - var i, r, ser; - for (i = 0; i < ring.records.length; i++) { - r = ring.records[i]; - if (this.haveNonRawStreams) { - ser = JSON.stringify(r, - bunyan.safeCycles()) + '\n'; - } - self.streams.forEach(function (s) { - s.stream.write(s.raw ? r : ser); - }); - } - ring.records.length = 0; - if (this.dumpDefault) { - var defaultRing = self.requestMap.get(DEFAULT_REQ_ID); - for (i = 0; i < defaultRing.records.length; i++) { - r = defaultRing.records[i]; - if (this.haveNonRawStreams) { - ser = JSON.stringify(r, - bunyan.safeCycles()) + '\n'; - } - self.streams.forEach(function (s) { - s.stream.write(s.raw ? r : ser); - }); - } - defaultRing.records.length = 0; + ring = this._rings[this._offset]; + ring.records.length = 0; + this.requestMap.set(req_id, ring); + } + + assert.ok(ring, 'no ring found'); + + if (record.level >= this.level) { + var i, r, ser; + for (i = 0; i < ring.records.length; i++) { + r = ring.records[i]; + if (this.haveNonRawStreams) { + ser = JSON.stringify(r, + bunyan.safeCycles()) + '\n'; + } + self.streams.forEach(function (s) { + s.stream.write(s.raw ? r : ser); + }); + } + ring.records.length = 0; + if (this.dumpDefault) { + var defaultRing = self.requestMap.get(DEFAULT_REQ_ID); + for (i = 0; i < defaultRing.records.length; i++) { + r = defaultRing.records[i]; + if (this.haveNonRawStreams) { + ser = JSON.stringify(r, + bunyan.safeCycles()) + '\n'; } - } else { - ring.write(record); + self.streams.forEach(function (s) { + s.stream.write(s.raw ? r : ser); + }); + } + defaultRing.records.length = 0; } + } else { + ring.write(record); + } }; RequestCaptureStream.prototype.toString = function toString() { - return (sprintf(STR_FMT, - this.constructor.name, - this.level, - this.limit, - this.maxRequestIds)); + return (sprintf(STR_FMT, + this.constructor.name, + this.level, + this.limit, + this.maxRequestIds)); }; - ///--- Serializers function clientReq(req) { - if (!req) - return (req); - - var host; - - try { - host = req.host.split(':')[0]; - } catch (e) { - host = false; - } - - return ({ - method: req ? req.method : false, - url: req ? req.path : false, - address: host, - port: req ? req.port : false, - headers: req ? req.headers : false - }); + if (!req) + return (req); + + var host; + + try { + host = req.host.split(':')[0]; + } catch (e) { + host = false; + } + + return ({ + method: req ? req.method : false, + url: req ? req.path : false, + address: host, + port: req ? req.port : false, + headers: req ? req.headers : false + }); } function clientRes(res) { - if (!res || !res.statusCode) - return (res); + if (!res || !res.statusCode) + return (res); - return ({ - statusCode: res.statusCode, - headers: res.headers - }); + return ({ + statusCode: res.statusCode, + headers: res.headers + }); } var SERIALIZERS = { - err: bunyan.stdSerializers.err, - req: bunyan.stdSerializers.req, - res: bunyan.stdSerializers.res, - client_req: clientReq, - client_res: clientRes + err: bunyan.stdSerializers.err, + req: bunyan.stdSerializers.req, + res: bunyan.stdSerializers.res, + client_req: clientReq, + client_res: clientRes }; ///--- Exports module.exports = { - RequestCaptureStream: RequestCaptureStream, - serializers: SERIALIZERS, - - createLogger: function createLogger(name) { - return (bunyan.createLogger({ - name: name, - serializers: SERIALIZERS, - streams: [ { - level: 'warn', - stream: process.stderr - }, { - level: 'debug', - type: 'raw', - stream: new RequestCaptureStream({ - stream: process.stderr - }) - } ] - })); - } + RequestCaptureStream: RequestCaptureStream, + serializers: SERIALIZERS, + + createLogger: function createLogger(name) { + return (bunyan.createLogger({ + name: name, + serializers: SERIALIZERS, + streams: [ + { + level: 'warn', + stream: process.stderr + }, + { + level: 'debug', + type: 'raw', + stream: new RequestCaptureStream({ + stream: process.stderr + }) + } + ] + })); + } }; diff --git a/lib/clients/http_client.js b/lib/clients/http_client.js index 5c1460117..8973a9b49 100644 --- a/lib/clients/http_client.js +++ b/lib/clients/http_client.js @@ -31,16 +31,16 @@ var KeepAliveAgentSecure; var httpMaxSockets = http.globalAgent.maxSockets; var httpsMaxSockets = https.globalAgent.maxSockets; if (!nativeKeepAlive) { - KeepAliveAgent = require('keep-alive-agent'); - KeepAliveAgentSecure = KeepAliveAgent.Secure; + KeepAliveAgent = require('keep-alive-agent'); + KeepAliveAgentSecure = KeepAliveAgent.Secure; } else { - KeepAliveAgent = http.Agent; - KeepAliveAgentSecure = https.Agent; - // maxSockets defaults to Infinity, but that doesn't - // lend itself well to KeepAlive, since sockets will - // never be reused. - httpMaxSockets = Math.min(httpMaxSockets, 1024); - httpsMaxSockets = Math.min(httpsMaxSockets, 1024); + KeepAliveAgent = http.Agent; + KeepAliveAgentSecure = https.Agent; + // maxSockets defaults to Infinity, but that doesn't + // lend itself well to KeepAlive, since sockets will + // never be reused. + httpMaxSockets = Math.min(httpMaxSockets, 1024); + httpsMaxSockets = Math.min(httpsMaxSockets, 1024); } ///--- Globals @@ -49,492 +49,489 @@ if (!nativeKeepAlive) { var VERSION = JSON.parse(fs.readFileSync(require('path').normalize(__dirname + '/../../package.json'), 'utf8')).version; - ///--- Helpers function cloneRetryOptions(options, defaults) { - if (options === false) { - return (false); - } - - assert.optionalObject(options, 'options.retry'); - var r = options || {}; - assert.optionalNumber(r.minTimeout, 'options.retry.minTimeout'); - assert.optionalNumber(r.maxTimeout, 'options.retry.maxTimeout'); - assert.optionalNumber(r.retries, 'options.retry.retries'); - assert.optionalObject(defaults, 'defaults'); - defaults = defaults || {}; - - return ({ - minTimeout: r.minTimeout || defaults.minTimeout || 1000, - maxTimeout: r.maxTimeout || defaults.maxTimeout || Infinity, - retries: r.retries || defaults.retries || 4 - }); + if (options === false) { + return (false); + } + + assert.optionalObject(options, 'options.retry'); + var r = options || {}; + assert.optionalNumber(r.minTimeout, 'options.retry.minTimeout'); + assert.optionalNumber(r.maxTimeout, 'options.retry.maxTimeout'); + assert.optionalNumber(r.retries, 'options.retry.retries'); + assert.optionalObject(defaults, 'defaults'); + defaults = defaults || {}; + + return ({ + minTimeout: r.minTimeout || defaults.minTimeout || 1000, + maxTimeout: r.maxTimeout || defaults.maxTimeout || Infinity, + retries: r.retries || defaults.retries || 4 + }); } function defaultUserAgent() { - var UA = 'restify/' + VERSION + - ' (' + os.arch() + '-' + os.platform() + '; ' + - 'v8/' + process.versions.v8 + '; ' + - 'OpenSSL/' + process.versions.openssl + ') ' + - 'node/' + process.versions.node; + var UA = 'restify/' + VERSION + + ' (' + os.arch() + '-' + os.platform() + '; ' + + 'v8/' + process.versions.v8 + '; ' + + 'OpenSSL/' + process.versions.openssl + ') ' + + 'node/' + process.versions.node; - return (UA); + return (UA); } function ConnectTimeoutError(ms) { - if (Error.captureStackTrace) - Error.captureStackTrace(this, ConnectTimeoutError); + if (Error.captureStackTrace) + Error.captureStackTrace(this, ConnectTimeoutError); - this.message = 'connect timeout after ' + ms + 'ms'; - this.name = 'ConnectTimeoutError'; + this.message = 'connect timeout after ' + ms + 'ms'; + this.name = 'ConnectTimeoutError'; } util.inherits(ConnectTimeoutError, Error); function rawRequest(opts, cb) { - assert.object(opts, 'options'); - assert.object(opts.log, 'options.log'); - assert.func(cb, 'callback'); - - cb = once(cb); - - var id = dtrace.nextId(); - var log = opts.log; - var proto = opts.protocol === 'https:' ? https : http; - var timer; - - if (opts.cert && opts.key) - opts.agent = false; - - if (opts.connectTimeout) { - timer = setTimeout(function connectTimeout() { - timer = null; - if (req) { - req.abort(); - } - - var err = new ConnectTimeoutError(opts.connectTimeout); - dtrace._rstfy_probes['client-error'].fire(function () { - return ([id, err.toString()]); - }); - cb(err, req); - }, opts.connectTimeout); - } - - dtrace._rstfy_probes['client-request'].fire(function () { - return ([ - opts.method, - opts.path, - opts.headers, - id - ]); + assert.object(opts, 'options'); + assert.object(opts.log, 'options.log'); + assert.func(cb, 'callback'); + + cb = once(cb); + + var id = dtrace.nextId(); + var log = opts.log; + var proto = opts.protocol === 'https:' ? https : http; + var timer; + + if (opts.cert && opts.key) + opts.agent = false; + + if (opts.connectTimeout) { + timer = setTimeout(function connectTimeout() { + timer = null; + if (req) { + req.abort(); + } + + var err = new ConnectTimeoutError(opts.connectTimeout); + dtrace._rstfy_probes['client-error'].fire(function () { + return ([id, err.toString()]); + }); + cb(err, req); + }, opts.connectTimeout); + } + + dtrace._rstfy_probes['client-request'].fire(function () { + return ([ + opts.method, + opts.path, + opts.headers, + id + ]); + }); + + var req = proto.request(opts, function onResponse(res) { + clearTimeout(timer); + dtrace._rstfy_probes['client-response'].fire(function () { + return ([ id, res.statusCode, res.headers ]); }); + log.trace({client_res: res}, 'Response received'); - var req = proto.request(opts, function onResponse(res) { - clearTimeout(timer); - dtrace._rstfy_probes['client-response'].fire(function () { - return ([ id, res.statusCode, res.headers ]); - }); - log.trace({client_res: res}, 'Response received'); + res.log = log; - res.log = log; + var err; + if (res.statusCode >= 400) + err = errors.codeToHttpError(res.statusCode); - var err; - if (res.statusCode >= 400) - err = errors.codeToHttpError(res.statusCode); + req.removeAllListeners('error'); + req.removeAllListeners('socket'); + req.emit('result', (err || null), res); + }); + req.log = log; - req.removeAllListeners('error'); - req.removeAllListeners('socket'); - req.emit('result', (err || null), res); - }); - req.log = log; - - req.on('error', function onError(err) { - dtrace._rstfy_probes['client-error'].fire(function () { - return ([id, (err || {}).toString()]); - }); - log.trace({err: err}, 'Request failed'); - clearTimeout(timer); - - cb(err, req); - if (req) { - process.nextTick(function () { - req.emit('result', err, null); - }); - } + req.on('error', function onError(err) { + dtrace._rstfy_probes['client-error'].fire(function () { + return ([id, (err || {}).toString()]); }); + log.trace({err: err}, 'Request failed'); + clearTimeout(timer); + + cb(err, req); + if (req) { + process.nextTick(function () { + req.emit('result', err, null); + }); + } + }); - req.once('upgrade', function onUpgrade(res, socket, _head) { - clearTimeout(timer); - dtrace._rstfy_probes['client-response'].fire(function () { - return ([ id, res.statusCode, res.headers ]); - }); - log.trace({client_res: res}, 'upgrade response received'); + req.once('upgrade', function onUpgrade(res, socket, _head) { + clearTimeout(timer); + dtrace._rstfy_probes['client-response'].fire(function () { + return ([ id, res.statusCode, res.headers ]); + }); + log.trace({client_res: res}, 'upgrade response received'); - res.log = log; + res.log = log; - var err; - if (res.statusCode >= 400) - err = errors.codeToHttpError(res.statusCode); + var err; + if (res.statusCode >= 400) + err = errors.codeToHttpError(res.statusCode); - req.removeAllListeners('error'); - req.removeAllListeners('socket'); - req.emit('upgradeResult', (err || null), res, socket, _head); - }); + req.removeAllListeners('error'); + req.removeAllListeners('socket'); + req.emit('upgradeResult', (err || null), res, socket, _head); + }); - req.once('socket', function onSocket(socket) { - var _socket = socket; - if (opts.protocol === 'https:' && socket.socket) { - _socket = socket.socket; - } + req.once('socket', function onSocket(socket) { + var _socket = socket; + if (opts.protocol === 'https:' && socket.socket) { + _socket = socket.socket; + } - if (_socket.writable && !_socket._connecting) { - clearTimeout(timer); - cb(null, req); - return; - } + if (_socket.writable && !_socket._connecting) { + clearTimeout(timer); + cb(null, req); + return; + } - _socket.once('connect', function onConnect() { - clearTimeout(timer); - if (opts._keep_alive) { - _socket.setKeepAlive(true); - socket.setKeepAlive(true); - } + _socket.once('connect', function onConnect() { + clearTimeout(timer); + if (opts._keep_alive) { + _socket.setKeepAlive(true); + socket.setKeepAlive(true); + } - cb(null, req); - }); + cb(null, req); }); + }); - if (opts.signRequest) - opts.signRequest(req); + if (opts.signRequest) + opts.signRequest(req); - if (log.trace()) - log.trace({client_req: opts}, 'request sent'); + if (log.trace()) + log.trace({client_req: opts}, 'request sent'); } // end `rawRequest` - ///--- API function HttpClient(options) { - assert.object(options, 'options'); - assert.optionalObject(options.headers, 'options.headers'); - assert.object(options.log, 'options.log'); - assert.optionalFunc(options.signRequest, 'options.signRequest'); - assert.optionalString(options.socketPath, 'options.socketPath'); - assert.optionalString(options.url, 'options.url'); - - EventEmitter.call(this); - - var self = this; - - this.agent = options.agent; - this.ca = options.ca; - this.cert = options.cert; - this.ciphers = options.ciphers; - this.connectTimeout = options.connectTimeout || false; - this.headers = options.headers || {}; - this.log = options.log; - if (!this.log.serializers) - { - // Ensure logger has a reasonable serializer for `client_res` - // and `client_req` logged in this module. - this.log = this.log.child({serializers: bunyan.serializers}); - } - this.key = options.key; - this.name = options.name || 'HttpClient'; - this.passphrase = options.passphrase; - this.pfx = options.pfx; - if (options.rejectUnauthorized !== undefined) { - this.rejectUnauthorized = options.rejectUnauthorized; - } else { - this.rejectUnauthorized = true; - } - - if (process.env.https_proxy) { - this.proxy = url.parse(process.env.https_proxy); - } else if (process.env.http_proxy) { - this.proxy = url.parse(process.env.http_proxy); - } else if (options.proxy) { - this.proxy = options.proxy; - } else { - this.proxy = false; - } - - this.retry = cloneRetryOptions(options.retry); - this.signRequest = options.signRequest || false; - this.socketPath = options.socketPath || false; - this.url = options.url ? url.parse(options.url) : {}; - - if (options.accept) { - if (options.accept.indexOf('/') === -1) - options.accept = mime.lookup(options.accept); - - this.headers.accept = options.accept; - } - - if (options.contentType) { - if (options.contentType.indexOf('/') === -1) - options.type = mime.lookup(options.contentType); - - this.headers['content-type'] = options.contentType; - } - - if (options.userAgent !== false) { - this.headers['user-agent'] = options.userAgent || - defaultUserAgent(); - } - - if (options.version) - this.headers['accept-version'] = options.version; - - if (this.agent === undefined) { - var Agent; - var maxSockets; - - if (this.proxy) { - if (this.url.protocol == 'https:') { - if (this.proxy.protocol === 'https:') { - Agent = tunnelAgent.httpsOverHttps; - } else { - Agent = tunnelAgent.httpsOverHttp; - } - } else { - if (this.proxy.protocol === 'https:') { - Agent = tunnelAgent.httpOverHttps; - } else { - Agent = tunnelAgent.httpOverHttp; - } - } - } else if (this.url.protocol === 'https:') { - Agent = KeepAliveAgentSecure; - maxSockets = httpsMaxSockets; + assert.object(options, 'options'); + assert.optionalObject(options.headers, 'options.headers'); + assert.object(options.log, 'options.log'); + assert.optionalFunc(options.signRequest, 'options.signRequest'); + assert.optionalString(options.socketPath, 'options.socketPath'); + assert.optionalString(options.url, 'options.url'); + + EventEmitter.call(this); + + var self = this; + + this.agent = options.agent; + this.ca = options.ca; + this.cert = options.cert; + this.ciphers = options.ciphers; + this.connectTimeout = options.connectTimeout || false; + this.headers = options.headers || {}; + this.log = options.log; + if (!this.log.serializers) { + // Ensure logger has a reasonable serializer for `client_res` + // and `client_req` logged in this module. + this.log = this.log.child({serializers: bunyan.serializers}); + } + this.key = options.key; + this.name = options.name || 'HttpClient'; + this.passphrase = options.passphrase; + this.pfx = options.pfx; + if (options.rejectUnauthorized !== undefined) { + this.rejectUnauthorized = options.rejectUnauthorized; + } else { + this.rejectUnauthorized = true; + } + + if (process.env.https_proxy) { + this.proxy = url.parse(process.env.https_proxy); + } else if (process.env.http_proxy) { + this.proxy = url.parse(process.env.http_proxy); + } else if (options.proxy) { + this.proxy = options.proxy; + } else { + this.proxy = false; + } + + this.retry = cloneRetryOptions(options.retry); + this.signRequest = options.signRequest || false; + this.socketPath = options.socketPath || false; + this.url = options.url ? url.parse(options.url) : {}; + + if (options.accept) { + if (options.accept.indexOf('/') === -1) + options.accept = mime.lookup(options.accept); + + this.headers.accept = options.accept; + } + + if (options.contentType) { + if (options.contentType.indexOf('/') === -1) + options.type = mime.lookup(options.contentType); + + this.headers['content-type'] = options.contentType; + } + + if (options.userAgent !== false) { + this.headers['user-agent'] = options.userAgent || + defaultUserAgent(); + } + + if (options.version) + this.headers['accept-version'] = options.version; + + if (this.agent === undefined) { + var Agent; + var maxSockets; + + if (this.proxy) { + if (this.url.protocol == 'https:') { + if (this.proxy.protocol === 'https:') { + Agent = tunnelAgent.httpsOverHttps; } else { - Agent = KeepAliveAgent; - maxSockets = httpMaxSockets; + Agent = tunnelAgent.httpsOverHttp; } - - if (this.proxy) { - this.agent = new Agent({ - proxy: self.proxy, - rejectUnauthorized: self.rejectUnauthorized, - ca: self.ca - }); + } else { + if (this.proxy.protocol === 'https:') { + Agent = tunnelAgent.httpOverHttps; } else { - this.agent = new Agent({ - cert: self.cert, - ca: self.ca, - ciphers: self.ciphers, - key: self.key, - maxSockets: maxSockets, - - // require('keep-alive-agent') - maxKeepAliveRequests: 0, - maxKeepAliveTime: 0, - - // native keepalive - keepAliveMsecs: 1000, - keepAlive: true, - - passphrase: self.passphrase, - pfx: self.pfx, - rejectUnauthorized: self.rejectUnauthorized - }); - this._keep_alive = true; + Agent = tunnelAgent.httpOverHttp; } + } + } else if (this.url.protocol === 'https:') { + Agent = KeepAliveAgentSecure; + maxSockets = httpsMaxSockets; + } else { + Agent = KeepAliveAgent; + maxSockets = httpMaxSockets; + } + + if (this.proxy) { + this.agent = new Agent({ + proxy: self.proxy, + rejectUnauthorized: self.rejectUnauthorized, + ca: self.ca + }); + } else { + this.agent = new Agent({ + cert: self.cert, + ca: self.ca, + ciphers: self.ciphers, + key: self.key, + maxSockets: maxSockets, + + // require('keep-alive-agent') + maxKeepAliveRequests: 0, + maxKeepAliveTime: 0, + + // native keepalive + keepAliveMsecs: 1000, + keepAlive: true, + + passphrase: self.passphrase, + pfx: self.pfx, + rejectUnauthorized: self.rejectUnauthorized + }); + this._keep_alive = true; } + } } util.inherits(HttpClient, EventEmitter); module.exports = HttpClient; HttpClient.prototype.close = function close() { - var sockets = this.agent.sockets; - Object.keys((sockets || {})).forEach(function (k) { - if (Array.isArray(sockets[k])) { - sockets[k].forEach(function (s) { - s.end(); - }); - } - }); + var sockets = this.agent.sockets; + Object.keys((sockets || {})).forEach(function (k) { + if (Array.isArray(sockets[k])) { + sockets[k].forEach(function (s) { + s.end(); + }); + } + }); - sockets = this.agent.idleSockets || this.agent.freeSockets; - Object.keys((sockets || {})).forEach(function (k) { - sockets[k].forEach(function (s) { - s.end(); - }); + sockets = this.agent.idleSockets || this.agent.freeSockets; + Object.keys((sockets || {})).forEach(function (k) { + sockets[k].forEach(function (s) { + s.end(); }); + }); }; HttpClient.prototype.del = function del(options, callback) { - var opts = this._options('DELETE', options); + var opts = this._options('DELETE', options); - return (this.read(opts, callback)); + return (this.read(opts, callback)); }; HttpClient.prototype.get = function get(options, callback) { - var opts = this._options('GET', options); + var opts = this._options('GET', options); - return (this.read(opts, callback)); + return (this.read(opts, callback)); }; HttpClient.prototype.head = function head(options, callback) { - var opts = this._options('HEAD', options); + var opts = this._options('HEAD', options); - return (this.read(opts, callback)); + return (this.read(opts, callback)); }; HttpClient.prototype.opts = function http_options(options, callback) { - var _opts = this._options('OPTIONS', options); + var _opts = this._options('OPTIONS', options); - return (this.read(_opts, callback)); + return (this.read(_opts, callback)); }; HttpClient.prototype.post = function post(options, callback) { - var opts = this._options('POST', options); + var opts = this._options('POST', options); - return (this.request(opts, callback)); + return (this.request(opts, callback)); }; HttpClient.prototype.put = function put(options, callback) { - var opts = this._options('PUT', options); + var opts = this._options('PUT', options); - return (this.request(opts, callback)); + return (this.request(opts, callback)); }; HttpClient.prototype.patch = function patch(options, callback) { - var opts = this._options('PATCH', options); + var opts = this._options('PATCH', options); - return (this.request(opts, callback)); + return (this.request(opts, callback)); }; HttpClient.prototype.read = function read(options, callback) { - var r = this.request(options, function readRequestCallback(err, req) { - if (!err) - req.end(); + var r = this.request(options, function readRequestCallback(err, req) { + if (!err) + req.end(); - return (callback(err, req)); - }); - return (r); + return (callback(err, req)); + }); + return (r); }; HttpClient.prototype.basicAuth = function basicAuth(username, password) { - if (username === false) { - delete this.headers.authorization; - } else { - assert.string(username, 'username'); - assert.string(password, 'password'); - - var buffer = new Buffer(username + ':' + password, 'utf8'); - this.headers.authorization = 'Basic ' + - buffer.toString('base64'); - } - - return (this); + if (username === false) { + delete this.headers.authorization; + } else { + assert.string(username, 'username'); + assert.string(password, 'password'); + + var buffer = new Buffer(username + ':' + password, 'utf8'); + this.headers.authorization = 'Basic ' + + buffer.toString('base64'); + } + + return (this); }; HttpClient.prototype.request = function request(opts, cb) { - assert.object(opts, 'options'); - assert.func(cb, 'callback'); + assert.object(opts, 'options'); + assert.func(cb, 'callback'); - cb = once(cb); + cb = once(cb); - if (opts.retry === false) { - rawRequest(opts, cb); - return; - } + if (opts.retry === false) { + rawRequest(opts, cb); + return; + } - var call; - var retry = cloneRetryOptions(opts.retry); + var call; + var retry = cloneRetryOptions(opts.retry); - opts._keep_alive = this._keep_alive; - call = backoff.call(rawRequest, opts, cb); - call.setStrategy(new backoff.ExponentialStrategy({ - initialDelay: retry.minTimeout, - maxDelay: retry.maxTimeout - })); - call.failAfter(retry.retries); - call.on('backoff', this.emit.bind(this, 'attempt')); + opts._keep_alive = this._keep_alive; + call = backoff.call(rawRequest, opts, cb); + call.setStrategy(new backoff.ExponentialStrategy({ + initialDelay: retry.minTimeout, + maxDelay: retry.maxTimeout + })); + call.failAfter(retry.retries); + call.on('backoff', this.emit.bind(this, 'attempt')); - call.start(); + call.start(); }; HttpClient.prototype._options = function (method, options) { - if (typeof (options) !== 'object') - options = { path: options }; - - var self = this; - var opts = { - agent: options.agent || self.agent, - ca: options.ca || self.ca, - cert: options.cert || self.cert, - ciphers: options.ciphers || self.ciphers, - connectTimeout: options.connectTimeout || self.connectTimeout, - headers: options.headers || {}, - key: options.key || self.key, - log: options.log || self.log, - method: method, - passphrase: options.passphrase || self.passphrase, - path: options.path || self.path, - pfx: options.pfx || self.pfx, - rejectUnauthorized: options.rejectUnauthorized || - self.rejectUnauthorized, - retry: options.retry !== false ? options.retry : false, - signRequest: options.signRequest || self.signRequest - }; - - if (!opts.retry && opts.retry !== false) - opts.retry = self.retry; - - - // Backwards compatibility with restify < 1.0 - if (options.query && - Object.keys(options.query).length && - opts.path.indexOf('?') === -1) { - opts.path += '?' + querystring.stringify(options.query); - } - - if (this.socketPath) - opts.socketPath = this.socketPath; - - Object.keys(this.url).forEach(function (k) { - if (!opts[k]) - opts[k] = self.url[k]; - }); - - Object.keys(self.headers).forEach(function (k) { - if (!opts.headers[k]) - opts.headers[k] = self.headers[k]; - }); - - if (!opts.headers.date) - opts.headers.date = new Date().toUTCString(); - - if (method === 'GET' || method === 'HEAD' || method === 'DELETE') { - if (opts.headers['content-type']) - delete opts.headers['content-type']; - if (opts.headers['content-md5']) - delete opts.headers['content-md5']; - if (opts.headers['content-length'] && method !== 'DELETE') - delete opts.headers['content-length']; - if (opts.headers['transfer-encoding']) - delete opts.headers['transfer-encoding']; - } - - return (opts); + if (typeof (options) !== 'object') + options = { path: options }; + + var self = this; + var opts = { + agent: options.agent || self.agent, + ca: options.ca || self.ca, + cert: options.cert || self.cert, + ciphers: options.ciphers || self.ciphers, + connectTimeout: options.connectTimeout || self.connectTimeout, + headers: options.headers || {}, + key: options.key || self.key, + log: options.log || self.log, + method: method, + passphrase: options.passphrase || self.passphrase, + path: options.path || self.path, + pfx: options.pfx || self.pfx, + rejectUnauthorized: options.rejectUnauthorized || + self.rejectUnauthorized, + retry: options.retry !== false ? options.retry : false, + signRequest: options.signRequest || self.signRequest + }; + + if (!opts.retry && opts.retry !== false) + opts.retry = self.retry; + + + // Backwards compatibility with restify < 1.0 + if (options.query && + Object.keys(options.query).length && + opts.path.indexOf('?') === -1) { + opts.path += '?' + querystring.stringify(options.query); + } + + if (this.socketPath) + opts.socketPath = this.socketPath; + + Object.keys(this.url).forEach(function (k) { + if (!opts[k]) + opts[k] = self.url[k]; + }); + + Object.keys(self.headers).forEach(function (k) { + if (!opts.headers[k]) + opts.headers[k] = self.headers[k]; + }); + + if (!opts.headers.date) + opts.headers.date = new Date().toUTCString(); + + if (method === 'GET' || method === 'HEAD' || method === 'DELETE') { + if (opts.headers['content-type']) + delete opts.headers['content-type']; + if (opts.headers['content-md5']) + delete opts.headers['content-md5']; + if (opts.headers['content-length'] && method !== 'DELETE') + delete opts.headers['content-length']; + if (opts.headers['transfer-encoding']) + delete opts.headers['transfer-encoding']; + } + + return (opts); }; // vim: set ts=8 sts=8 sw=8 et: diff --git a/lib/clients/index.js b/lib/clients/index.js index 153eb20ab..2c195a91e 100644 --- a/lib/clients/index.js +++ b/lib/clients/index.js @@ -8,7 +8,7 @@ var StringClient = require('./string_client'); ///--- Exports module.exports = { - HttpClient: HttpClient, - JsonClient: JsonClient, - StringClient: StringClient + HttpClient: HttpClient, + JsonClient: JsonClient, + StringClient: StringClient }; diff --git a/lib/clients/json_client.js b/lib/clients/json_client.js index fef346c5b..7505159d9 100644 --- a/lib/clients/json_client.js +++ b/lib/clients/json_client.js @@ -10,79 +10,79 @@ var RestError = require('../errors').RestError; var StringClient = require('./string_client'); - ///--- API function JsonClient(options) { - assert.object(options, 'options'); + assert.object(options, 'options'); - options.accept = 'application/json'; - options.name = options.name || 'JsonClient'; - options.contentType = 'application/json'; + options.accept = 'application/json'; + options.name = options.name || 'JsonClient'; + options.contentType = 'application/json'; - StringClient.call(this, options); + StringClient.call(this, options); - this._super = StringClient.prototype; + this._super = StringClient.prototype; } util.inherits(JsonClient, StringClient); module.exports = JsonClient; JsonClient.prototype.write = function write(options, body, callback) { - assert.ok(body !== undefined, 'body'); - assert.object(body, 'body'); + assert.ok(body !== undefined, 'body'); + assert.object(body, 'body'); - body = JSON.stringify(body !== null ? body : {}); - return (this._super.write.call(this, options, body, callback)); + body = JSON.stringify(body !== null ? body : {}); + return (this._super.write.call(this, options, body, callback)); }; JsonClient.prototype.parse = function parse(req, callback) { - var log = this.log; - function parseResponse(err, req2, res, data) { - var obj; - try { - if (data && !/^\s*$/.test(data)) - obj = JSON.parse(data); - } catch (e) { - // Not really sure what else we can do here, besides - // make the client just keep going. - log.trace(e, 'Invalid JSON in response'); - } - obj = obj || {}; - - if (res && res.statusCode >= 400) { - // Upcast error to a RestError (if we can) - // Be nice and handle errors like - // { error: { code: '', message: '' } } - // in addition to { code: '', message: '' }. - if (obj.code || (obj.error && obj.error.code)) { - var _c = obj.code || - (obj.error ? obj.error.code : '') || - ''; - var _m = obj.message || - (obj.error ? obj.error.message : '') || - ''; - - err = new RestError({ - message: _m, - restCode: _c, - statusCode: res.statusCode - }); - err.name = err.restCode; - if (!/Error$/.test(err.name)) - err.name += 'Error'; - } else if (!err) { - err = codeToHttpError(res.statusCode, - obj.message || '', data); - } - } - - if (err) - err.body = obj; - - callback((err || null), req2, res, obj); + var log = this.log; + + function parseResponse(err, req2, res, data) { + var obj; + try { + if (data && !/^\s*$/.test(data)) + obj = JSON.parse(data); + } catch (e) { + // Not really sure what else we can do here, besides + // make the client just keep going. + log.trace(e, 'Invalid JSON in response'); + } + obj = obj || {}; + + if (res && res.statusCode >= 400) { + // Upcast error to a RestError (if we can) + // Be nice and handle errors like + // { error: { code: '', message: '' } } + // in addition to { code: '', message: '' }. + if (obj.code || (obj.error && obj.error.code)) { + var _c = obj.code || + (obj.error ? obj.error.code : '') || + ''; + var _m = obj.message || + (obj.error ? obj.error.message : '') || + ''; + + err = new RestError({ + message: _m, + restCode: _c, + statusCode: res.statusCode + }); + err.name = err.restCode; + if (!/Error$/.test(err.name)) + err.name += 'Error'; + } else if (!err) { + err = codeToHttpError(res.statusCode, + obj.message || '', data); + } } - return (this._super.parse.call(this, req, parseResponse)); + if (err) + err.body = obj; + + callback((err || null), req2, res, obj); + } + + return (this._super.parse.call(this, req, parseResponse)); }; diff --git a/lib/clients/string_client.js b/lib/clients/string_client.js index 4f99a0ba0..6ccdf62b1 100644 --- a/lib/clients/string_client.js +++ b/lib/clients/string_client.js @@ -10,182 +10,181 @@ var util = require('util'); var HttpClient = require('./http_client'); - ///--- Helpers ///--- API function StringClient(options) { - assert.object(options, 'options'); - assert.optionalObject(options.gzip, 'options.gzip'); + assert.object(options, 'options'); + assert.optionalObject(options.gzip, 'options.gzip'); - options.accept = options.accept || 'text/plain'; - options.name = options.name || 'StringClient'; - options.contentType = - options.contentType || 'application/x-www-form-urlencoded'; + options.accept = options.accept || 'text/plain'; + options.name = options.name || 'StringClient'; + options.contentType = + options.contentType || 'application/x-www-form-urlencoded'; - HttpClient.call(this, options); - this.gzip = options.gzip; + HttpClient.call(this, options); + this.gzip = options.gzip; } util.inherits(StringClient, HttpClient); module.exports = StringClient; StringClient.prototype.post = function post(options, body, callback) { - var opts = this._options('POST', options); - if (typeof (body) === 'function') { - callback = body; - body = null; - } + var opts = this._options('POST', options); + if (typeof (body) === 'function') { + callback = body; + body = null; + } - return (this.write(opts, body, callback)); + return (this.write(opts, body, callback)); }; StringClient.prototype.put = function put(options, body, callback) { - var opts = this._options('PUT', options); - if (typeof (body) === 'function') { - callback = body; - body = null; - } + var opts = this._options('PUT', options); + if (typeof (body) === 'function') { + callback = body; + body = null; + } - return (this.write(opts, body, callback)); + return (this.write(opts, body, callback)); }; StringClient.prototype.patch = function patch(options, body, callback) { - var opts = this._options('PATCH', options); - if (typeof (body) === 'function') { - callback = body; - body = null; - } + var opts = this._options('PATCH', options); + if (typeof (body) === 'function') { + callback = body; + body = null; + } - return (this.write(opts, body, callback)); + return (this.write(opts, body, callback)); }; StringClient.prototype.read = function read(options, callback) { - var self = this; - this.request(options, function _parse(err, req) { - if (err) - return (callback(err, req)); - - req.once('result', self.parse(req, callback)); - return (req.end()); - }); - return (this); + var self = this; + this.request(options, function _parse(err, req) { + if (err) + return (callback(err, req)); + + req.once('result', self.parse(req, callback)); + return (req.end()); + }); + return (this); }; StringClient.prototype.write = function write(options, body, callback) { - if (body !== null && typeof (body) !== 'string') - body = qs.stringify(body); + if (body !== null && typeof (body) !== 'string') + body = qs.stringify(body); - var self = this; + var self = this; - function _write(data) { - if (data) { - var hash = crypto.createHash('md5'); - hash.update(data, 'utf8'); - options.headers['content-md5'] = hash.digest('base64'); - } - - self.request(options, function (err, req) { - if (err) { - callback(err, req); - return; - } - - req.once('result', self.parse(req, callback)); - req.end(data); - }); + function _write(data) { + if (data) { + var hash = crypto.createHash('md5'); + hash.update(data, 'utf8'); + options.headers['content-md5'] = hash.digest('base64'); } - options.headers = options.headers || {}; + self.request(options, function (err, req) { + if (err) { + callback(err, req); + return; + } + + req.once('result', self.parse(req, callback)); + req.end(data); + }); + } - if (this.gzip) - options.headers['accept-encoding'] = 'gzip'; + options.headers = options.headers || {}; - if (body) { - if (this.gzip) { - options.headers['content-encoding'] = 'gzip'; - zlib.gzip(body, function (err, data) { - if (err) { - callback(err, null); - return; - } + if (this.gzip) + options.headers['accept-encoding'] = 'gzip'; - options.headers['content-length'] = data.length; - _write(data); - }); - } else { - options.headers['content-length'] = - Buffer.byteLength(body); - _write(body); + if (body) { + if (this.gzip) { + options.headers['content-encoding'] = 'gzip'; + zlib.gzip(body, function (err, data) { + if (err) { + callback(err, null); + return; } + + options.headers['content-length'] = data.length; + _write(data); + }); } else { - _write(); + options.headers['content-length'] = + Buffer.byteLength(body); + _write(body); } + } else { + _write(); + } - return (this); + return (this); }; StringClient.prototype.parse = function parse(req, callback) { - function parseResponse(err, res) { - if (res) { - function done() { - res.log.trace('body received:\n%s', body); - res.body = body; - if (hash && md5 !== hash.digest('base64')) { - err = new Error('BadDigest'); - callback(err, req, res); - return; - } - - if (err) { - err.body = body; - err.message = body; - } - - callback(err, req, res, body); - } - - var body = ''; - var gz; - var hash; - var md5 = res.headers['content-md5']; - if (md5 && req.method !== 'HEAD') - hash = crypto.createHash('md5'); - - if (res.headers['content-encoding'] === 'gzip') { - gz = zlib.createGunzip(); - gz.on('data', function (chunk) { - body += chunk.toString('utf8'); - }); - gz.once('end', done); - res.once('end', gz.end.bind(gz)); - } else { - res.setEncoding('utf8'); - res.once('end', done); - } - - res.on('data', function onData(chunk) { - if (hash) - hash.update(chunk); - - if (gz) { - gz.write(chunk); - } else { - body += chunk; - } - }); + function parseResponse(err, res) { + if (res) { + function done() { + res.log.trace('body received:\n%s', body); + res.body = body; + if (hash && md5 !== hash.digest('base64')) { + err = new Error('BadDigest'); + callback(err, req, res); + return; + } + + if (err) { + err.body = body; + err.message = body; + } + callback(err, req, res, body); + } + + var body = ''; + var gz; + var hash; + var md5 = res.headers['content-md5']; + if (md5 && req.method !== 'HEAD') + hash = crypto.createHash('md5'); + + if (res.headers['content-encoding'] === 'gzip') { + gz = zlib.createGunzip(); + gz.on('data', function (chunk) { + body += chunk.toString('utf8'); + }); + gz.once('end', done); + res.once('end', gz.end.bind(gz)); + } else { + res.setEncoding('utf8'); + res.once('end', done); + } + + res.on('data', function onData(chunk) { + if (hash) + hash.update(chunk); + + if (gz) { + gz.write(chunk); } else { - callback(err, req, null, null); + body += chunk; } + }); + + } else { + callback(err, req, null, null); } + } - return (parseResponse); + return (parseResponse); }; diff --git a/lib/dtrace.js b/lib/dtrace.js index 9f41d1dee..411eb620b 100644 --- a/lib/dtrace.js +++ b/lib/dtrace.js @@ -1,79 +1,82 @@ // Copyright 2012 Mark Cavage, Inc. All rights reserved. - ///--- Globals var ID = 0; var MAX_INT = Math.pow(2, 32) - 1; var PROBES = { - // server_name, route_name, id, method, url, headers (json) - 'route-start': ['char *', 'char *', 'int', 'char *', 'char *', 'json'], + // server_name, route_name, id, method, url, headers (json) + 'route-start': ['char *', 'char *', 'int', 'char *', 'char *', 'json'], - // server_name, route_name, handler_name, id - 'handler-start': ['char *', 'char *', 'char *', 'int'], + // server_name, route_name, handler_name, id + 'handler-start': ['char *', 'char *', 'char *', 'int'], - // server_name, route_name, handler_name, id - 'handler-done': ['char *', 'char *', 'char *', 'int'], + // server_name, route_name, handler_name, id + 'handler-done': ['char *', 'char *', 'char *', 'int'], - // server_name, route_name, id, statusCode, headers (json) - 'route-done': ['char *', 'char *', 'int', 'int', 'json'], + // server_name, route_name, id, statusCode, headers (json) + 'route-done': ['char *', 'char *', 'int', 'int', 'json'], - // Client probes - // method, url, headers, id - 'client-request': ['char *', 'char *', 'json', 'int'], - // id, statusCode, headers - 'client-response': ['int', 'int', 'json'], - // id, Error.toString() - 'client-error': ['id', 'char *'] + // Client probes + // method, url, headers, id + 'client-request': ['char *', 'char *', 'json', 'int'], + // id, statusCode, headers + 'client-response': ['int', 'int', 'json'], + // id, Error.toString() + 'client-error': ['id', 'char *'] }; var PROVIDER; - ///--- API module.exports = function exportStaticProvider() { - if (!PROVIDER) { - try { - var dtrace = require('dtrace-provider'); - PROVIDER = dtrace.createDTraceProvider('restify'); - } catch (e) { - PROVIDER = { - fire: function () {}, - enable: function () {}, - addProbe: function () { - var p = { - fire: function () {} - }; - return (p); - }, - removeProbe: function () {}, - disable: function () {} - }; + if (!PROVIDER) { + try { + var dtrace = require('dtrace-provider'); + PROVIDER = dtrace.createDTraceProvider('restify'); + } catch (e) { + PROVIDER = { + fire: function () { + }, + enable: function () { + }, + addProbe: function () { + var p = { + fire: function () { + } + }; + return (p); + }, + removeProbe: function () { + }, + disable: function () { } + }; + } - PROVIDER._rstfy_probes = {}; + PROVIDER._rstfy_probes = {}; - Object.keys(PROBES).forEach(function (p) { - var args = PROBES[p].splice(0); - args.unshift(p); + Object.keys(PROBES).forEach(function (p) { + var args = PROBES[p].splice(0); + args.unshift(p); - var probe = PROVIDER.addProbe.apply(PROVIDER, args); - PROVIDER._rstfy_probes[p] = probe; - }); + var probe = PROVIDER.addProbe.apply(PROVIDER, args); + PROVIDER._rstfy_probes[p] = probe; + }); - PROVIDER.enable(); + PROVIDER.enable(); - PROVIDER.nextId = function nextId() { - if (++ID >= MAX_INT) - ID = 1; + PROVIDER.nextId = function nextId() { + if (++ID >= MAX_INT) + ID = 1; - return (ID); - }; - } + return (ID); + }; + } - return (PROVIDER); + return (PROVIDER); }(); diff --git a/lib/errors/http_error.js b/lib/errors/http_error.js index 514bff7df..0cd1c0f05 100644 --- a/lib/errors/http_error.js +++ b/lib/errors/http_error.js @@ -7,127 +7,122 @@ var assert = require('assert-plus'); var WError = require('verror').WError; - ///--- Globals var slice = Function.prototype.call.bind(Array.prototype.slice); - ///--- Helpers function codeToErrorName(code) { - code = parseInt(code, 10); - var status = http.STATUS_CODES[code]; - if (!status) - return (false); + code = parseInt(code, 10); + var status = http.STATUS_CODES[code]; + if (!status) + return (false); - var pieces = status.split(/\s+/); - var str = ''; - pieces.forEach(function (s) { - str += s.charAt(0).toUpperCase() + s.slice(1).toLowerCase(); - }); + var pieces = status.split(/\s+/); + var str = ''; + pieces.forEach(function (s) { + str += s.charAt(0).toUpperCase() + s.slice(1).toLowerCase(); + }); - str = str.replace(/\W+/g, ''); - if (!/\w+Error$/.test(str)) - str += 'Error'; + str = str.replace(/\W+/g, ''); + if (!/\w+Error$/.test(str)) + str += 'Error'; - return (str); + return (str); } - ///--- Error Base class function HttpError(options) { - assert.object(options, 'options'); - - options.constructorOpt = options.constructorOpt || HttpError; - WError.apply(this, arguments); - - var self = this; - var code = parseInt((options.statusCode || 500), 10); - this.statusCode = code; - this.body = options.body || { - code: codeToErrorName(code), - message: options.message || self.message - }; - this.message = options.message || self.message; + assert.object(options, 'options'); + + options.constructorOpt = options.constructorOpt || HttpError; + WError.apply(this, arguments); + + var self = this; + var code = parseInt((options.statusCode || 500), 10); + this.statusCode = code; + this.body = options.body || { + code: codeToErrorName(code), + message: options.message || self.message + }; + this.message = options.message || self.message; } util.inherits(HttpError, WError); - ///--- Exports module.exports = { - HttpError: HttpError, - - codeToHttpError: function codeToHttpError(code, message, body) { - var err; - var name = codeToErrorName(code); - - if (!name) { - err = new HttpError({ - statusCode: code, - message: message, - body: body - }); - err.name = 'Http' + code + 'Error'; - } else { - err = new module.exports[name]({ - body: body, - message: message, - constructorOpt: codeToHttpError, - statusCode: code - }); - } - - return (err); + HttpError: HttpError, + + codeToHttpError: function codeToHttpError(code, message, body) { + var err; + var name = codeToErrorName(code); + + if (!name) { + err = new HttpError({ + statusCode: code, + message: message, + body: body + }); + err.name = 'Http' + code + 'Error'; + } else { + err = new module.exports[name]({ + body: body, + message: message, + constructorOpt: codeToHttpError, + statusCode: code + }); } -}; + return (err); + } +}; // Export all the 4xx and 5xx HTTP Status codes as Errors var codes = Object.keys(http.STATUS_CODES); codes.forEach(function (code) { - if (code < 400) - return; + if (code < 400) + return; - var name = codeToErrorName(code); + var name = codeToErrorName(code); - module.exports[name] = function (cause, message) { - var index = 1; - var opts = { - statusCode: code - }; - - if (cause && cause instanceof Error) { - opts.cause = cause; - opts.constructorOpt = arguments.callee; - } else if (typeof (cause) === 'object') { - opts.body = cause.body; - opts.cause = cause.cause; - opts.constructorOpt = cause.constructorOpt; - opts.message = cause.message; - opts.statusCode = cause.statusCode || code; - } else { - opts.constructorOpt = arguments.callee; - index = 0; - } - - var args = slice(arguments, index); - args.unshift(opts); - HttpError.apply(this, args); + module.exports[name] = function (cause, message) { + var index = 1; + var opts = { + statusCode: code }; - util.inherits(module.exports[name], HttpError); - module.exports[name].displayName = - module.exports[name].prototype.name = - name; + if (cause && cause instanceof Error) { + opts.cause = cause; + opts.constructorOpt = arguments.callee; + } else if (typeof (cause) === 'object') { + opts.body = cause.body; + opts.cause = cause.cause; + opts.constructorOpt = cause.constructorOpt; + opts.message = cause.message; + opts.statusCode = cause.statusCode || code; + } else { + opts.constructorOpt = arguments.callee; + index = 0; + } + + var args = slice(arguments, index); + args.unshift(opts); + HttpError.apply(this, args); + }; + util.inherits(module.exports[name], HttpError); + + module.exports[name].displayName = + module.exports[name].prototype.name = + name; }); diff --git a/lib/errors/index.js b/lib/errors/index.js index a3fc36307..74561ccd0 100644 --- a/lib/errors/index.js +++ b/lib/errors/index.js @@ -7,10 +7,10 @@ var restErrors = require('./rest_error'); module.exports = {}; Object.keys(httpErrors).forEach(function (k) { - module.exports[k] = httpErrors[k]; + module.exports[k] = httpErrors[k]; }); // Note some of the RestErrors overwrite plain HTTP errors. Object.keys(restErrors).forEach(function (k) { - module.exports[k] = restErrors[k]; + module.exports[k] = restErrors[k]; }); diff --git a/lib/errors/rest_error.js b/lib/errors/rest_error.js index 5f45058ac..1bda98720 100644 --- a/lib/errors/rest_error.js +++ b/lib/errors/rest_error.js @@ -7,7 +7,6 @@ var assert = require('assert-plus'); var httpErrors = require('./http_error'); - ///--- Globals var slice = Function.prototype.call.bind(Array.prototype.slice); @@ -15,81 +14,79 @@ var slice = Function.prototype.call.bind(Array.prototype.slice); var HttpError = httpErrors.HttpError; var CODES = { - BadDigest: 400, - BadMethod: 405, - Internal: 500, // Don't have InternalErrorError - InvalidArgument: 409, - InvalidContent: 400, - InvalidCredentials: 401, - InvalidHeader: 400, - InvalidVersion: 400, - MissingParameter: 409, - NotAuthorized: 403, - PreconditionFailed: 412, - RequestExpired: 400, - RequestThrottled: 429, - ResourceNotFound: 404, - WrongAccept: 406 + BadDigest: 400, + BadMethod: 405, + Internal: 500, // Don't have InternalErrorError + InvalidArgument: 409, + InvalidContent: 400, + InvalidCredentials: 401, + InvalidHeader: 400, + InvalidVersion: 400, + MissingParameter: 409, + NotAuthorized: 403, + PreconditionFailed: 412, + RequestExpired: 400, + RequestThrottled: 429, + ResourceNotFound: 404, + WrongAccept: 406 }; - ///--- API function RestError(options) { - assert.object(options, 'options'); + assert.object(options, 'options'); - options.constructorOpt = options.constructorOpt || RestError; - HttpError.apply(this, arguments); + options.constructorOpt = options.constructorOpt || RestError; + HttpError.apply(this, arguments); - var self = this; - this.restCode = options.restCode || 'Error'; - this.body = options.body || { - code: self.restCode, - message: options.message || self.message - }; + var self = this; + this.restCode = options.restCode || 'Error'; + this.body = options.body || { + code: self.restCode, + message: options.message || self.message + }; } util.inherits(RestError, HttpError); - ///--- Exports module.exports = { - RestError: RestError + RestError: RestError }; Object.keys(CODES).forEach(function (k) { - var name = k; - if (!/\w+Error$/.test(name)) - name += 'Error'; - - module.exports[name] = function (cause, message) { - var index = 1; - var opts = { - restCode: (k === 'Internal' ? 'InternalError' : k), - statusCode: CODES[k] - }; - - opts.constructorOpt = arguments.callee; - - if (cause && cause instanceof Error) { - opts.cause = cause; - } else if (typeof (cause) === 'object') { - opts.body = cause.body; - opts.cause = cause.cause; - opts.message = cause.message; - opts.statusCode = cause.statusCode || CODES[k]; - } else { - index = 0; - } - - var args = slice(arguments, index); - args.unshift(opts); - RestError.apply(this, args); + var name = k; + if (!/\w+Error$/.test(name)) + name += 'Error'; + + module.exports[name] = function (cause, message) { + var index = 1; + var opts = { + restCode: (k === 'Internal' ? 'InternalError' : k), + statusCode: CODES[k] }; - util.inherits(module.exports[name], RestError); - module.exports[name].displayName = - module.exports[name].prototype.name = - name; + + opts.constructorOpt = arguments.callee; + + if (cause && cause instanceof Error) { + opts.cause = cause; + } else if (typeof (cause) === 'object') { + opts.body = cause.body; + opts.cause = cause.cause; + opts.message = cause.message; + opts.statusCode = cause.statusCode || CODES[k]; + } else { + index = 0; + } + + var args = slice(arguments, index); + args.unshift(opts); + RestError.apply(this, args); + }; + util.inherits(module.exports[name], RestError); + module.exports[name].displayName = + module.exports[name].prototype.name = + name; }); diff --git a/lib/formatters/binary.js b/lib/formatters/binary.js index 5cd81e67f..d8b4d12e7 100644 --- a/lib/formatters/binary.js +++ b/lib/formatters/binary.js @@ -1,18 +1,17 @@ // Copyright 2012 Mark Cavage, Inc. All rights reserved. - ///--- Exports function formatBinary(req, res, body) { - if (body instanceof Error) - res.statusCode = body.statusCode || 500; + if (body instanceof Error) + res.statusCode = body.statusCode || 500; - if (!Buffer.isBuffer(body)) - body = new Buffer(body.toString()); + if (!Buffer.isBuffer(body)) + body = new Buffer(body.toString()); - res.setHeader('Content-Length', body.length); - return (body); + res.setHeader('Content-Length', body.length); + return (body); } module.exports = formatBinary; \ No newline at end of file diff --git a/lib/formatters/index.js b/lib/formatters/index.js index 4fa9b9a42..290e30ae1 100644 --- a/lib/formatters/index.js +++ b/lib/formatters/index.js @@ -1,12 +1,11 @@ // Copyright 2012 Mark Cavage, Inc. All rights reserved. - ///--- Exports module.exports = { - 'application/javascript; q=0.1': require('./jsonp'), - 'application/json; q=0.4': require('./json'), - 'text/plain; q=0.3': require('./text'), - 'application/octet-stream; q=0.2': require('./binary') + 'application/javascript; q=0.1': require('./jsonp'), + 'application/json; q=0.4': require('./json'), + 'text/plain; q=0.3': require('./text'), + 'application/octet-stream; q=0.2': require('./binary') }; diff --git a/lib/formatters/json.js b/lib/formatters/json.js index 7508e981b..cf4ca6d6e 100644 --- a/lib/formatters/json.js +++ b/lib/formatters/json.js @@ -1,30 +1,29 @@ // Copyright 2012 Mark Cavage, Inc. All rights reserved. - ///--- Exports function formatJSON(req, res, body) { - if (body instanceof Error) { - // snoop for RestError or HttpError, but don't rely on - // instanceof - res.statusCode = body.statusCode || 500; - - if (body.body) { - body = body.body; - } else { - body = { - message: body.message - }; - } - } else if (Buffer.isBuffer(body)) { - body = body.toString('base64'); + if (body instanceof Error) { + // snoop for RestError or HttpError, but don't rely on + // instanceof + res.statusCode = body.statusCode || 500; + + if (body.body) { + body = body.body; + } else { + body = { + message: body.message + }; } + } else if (Buffer.isBuffer(body)) { + body = body.toString('base64'); + } - var data = JSON.stringify(body); - res.setHeader('Content-Length', Buffer.byteLength(data)); + var data = JSON.stringify(body); + res.setHeader('Content-Length', Buffer.byteLength(data)); - return (data); + return (data); } module.exports = formatJSON; diff --git a/lib/formatters/jsonp.js b/lib/formatters/jsonp.js index 96b71216c..cb8729bb0 100644 --- a/lib/formatters/jsonp.js +++ b/lib/formatters/jsonp.js @@ -1,38 +1,37 @@ // Copyright 2012 Mark Cavage, Inc. All rights reserved. - ///--- Exports function formatJSONP(req, res, body) { - if (!body) { - res.setHeader('Content-Length', 0); - return (null); - } - - if (body instanceof Error) { - if ((body.restCode || body.httpCode) && body.body) { - body = body.body; - } else { - body = { - message: body.message - }; - } + if (!body) { + res.setHeader('Content-Length', 0); + return (null); + } + + if (body instanceof Error) { + if ((body.restCode || body.httpCode) && body.body) { + body = body.body; + } else { + body = { + message: body.message + }; } + } - if (Buffer.isBuffer(body)) - body = body.toString('base64'); + if (Buffer.isBuffer(body)) + body = body.toString('base64'); - var cb = req.query.callback || req.query.jsonp; - var data; - if (cb) { - data = cb + '(' + JSON.stringify(body) + ');'; - } else { - data = JSON.stringify(body); - } + var cb = req.query.callback || req.query.jsonp; + var data; + if (cb) { + data = cb + '(' + JSON.stringify(body) + ');'; + } else { + data = JSON.stringify(body); + } - res.setHeader('Content-Length', Buffer.byteLength(data)); - return (data); + res.setHeader('Content-Length', Buffer.byteLength(data)); + return (data); } module.exports = formatJSONP; diff --git a/lib/formatters/text.js b/lib/formatters/text.js index d59817fcc..4a8564153 100644 --- a/lib/formatters/text.js +++ b/lib/formatters/text.js @@ -1,21 +1,20 @@ // Copyright 2012 Mark Cavage, Inc. All rights reserved. - ///--- Exports function formatText(req, res, body) { - if (body instanceof Error) { - res.statusCode = body.statusCode || 500; - body = body.message; - } else if (typeof (body) === 'object') { - body = JSON.stringify(body); - } else { - body = body.toString(); - } + if (body instanceof Error) { + res.statusCode = body.statusCode || 500; + body = body.message; + } else if (typeof (body) === 'object') { + body = JSON.stringify(body); + } else { + body = body.toString(); + } - res.setHeader('Content-Length', Buffer.byteLength(body)); - return (body); + res.setHeader('Content-Length', Buffer.byteLength(body)); + return (body); } module.exports = formatText; diff --git a/lib/http_date.js b/lib/http_date.js index 18ab3fbfa..c74398e45 100644 --- a/lib/http_date.js +++ b/lib/http_date.js @@ -1,8 +1,8 @@ // Copyright 2012 Mark Cavage, Inc. All rights reserved. module.exports = function httpDate(now) { - if (!now) - now = new Date(); + if (!now) + now = new Date(); - return (now.toUTCString()); + return (now.toUTCString()); }; diff --git a/lib/index.js b/lib/index.js index bb16f3a19..0ced3d8e5 100644 --- a/lib/index.js +++ b/lib/index.js @@ -11,84 +11,84 @@ var shallowCopy = require('./utils').shallowCopy; function createClient(options) { - var assert = require('assert-plus'); - var bunyan = require('./bunyan_helper'); - var clients = require('./clients'); + var assert = require('assert-plus'); + var bunyan = require('./bunyan_helper'); + var clients = require('./clients'); - assert.object(options, 'options'); + assert.object(options, 'options'); - var client; - var opts = shallowCopy(options); - opts.agent = options.agent; - opts.name = opts.name || 'restify'; - opts.type = opts.type || 'application/octet-stream'; - opts.log = opts.log || bunyan.createLogger(opts.name); + var client; + var opts = shallowCopy(options); + opts.agent = options.agent; + opts.name = opts.name || 'restify'; + opts.type = opts.type || 'application/octet-stream'; + opts.log = opts.log || bunyan.createLogger(opts.name); - switch (opts.type) { + switch (opts.type) { case 'json': - client = new clients.JsonClient(opts); - break; + client = new clients.JsonClient(opts); + break; case 'string': - client = new clients.StringClient(opts); - break; + client = new clients.StringClient(opts); + break; case 'http': default: - client = new clients.HttpClient(opts); - break; - } + client = new clients.HttpClient(opts); + break; + } - return (client); + return (client); } function createJsonClient(options) { - options = options ? shallowCopy(options) : {}; - options.type = 'json'; - return (createClient(options)); + options = options ? shallowCopy(options) : {}; + options.type = 'json'; + return (createClient(options)); } function createStringClient(options) { - options = options ? shallowCopy(options) : {}; - options.type = 'string'; - return (createClient(options)); + options = options ? shallowCopy(options) : {}; + options.type = 'string'; + return (createClient(options)); } function createHttpClient(options) { - options = options ? shallowCopy(options) : {}; - options.type = 'http'; - return (createClient(options)); + options = options ? shallowCopy(options) : {}; + options.type = 'http'; + return (createClient(options)); } function createServer(options) { - var bunyan = require('./bunyan_helper'); - var InternalError = require('./errors').InternalError; - var Router = require('./router'); - var Server = require('./server'); - - var opts = shallowCopy(options || {}); - var server; - - opts.name = opts.name || 'restify'; - opts.log = opts.log || bunyan.createLogger(opts.name); - opts.router = opts.router || new Router(opts); - - server = new Server(opts); - server.on('uncaughtException', function (req, res, route, e) { - if (this.listeners('uncaughtException').length > 1 || - res._headerSent) { - return (false); - } - - res.send(new InternalError(e, e.message || 'unexpected error')); - return (true); - }); - - return (server); + var bunyan = require('./bunyan_helper'); + var InternalError = require('./errors').InternalError; + var Router = require('./router'); + var Server = require('./server'); + + var opts = shallowCopy(options || {}); + var server; + + opts.name = opts.name || 'restify'; + opts.log = opts.log || bunyan.createLogger(opts.name); + opts.router = opts.router || new Router(opts); + + server = new Server(opts); + server.on('uncaughtException', function (req, res, route, e) { + if (this.listeners('uncaughtException').length > 1 || + res._headerSent) { + return (false); + } + + res.send(new InternalError(e, e.message || 'unexpected error')); + return (true); + }); + + return (server); } @@ -101,60 +101,59 @@ function createServer(options) { * @param {Object} a hash of parameter names to values for substitution. */ function realizeUrl(pattern, params) { - var p = pattern.replace(/\/:([^/]+)/g, function (match, k) { - return (params.hasOwnProperty(k) ? '/' + params[k] : match); - }); + var p = pattern.replace(/\/:([^/]+)/g, function (match, k) { + return (params.hasOwnProperty(k) ? '/' + params[k] : match); + }); - return (require('./utils').sanitizePath(p)); + return (require('./utils').sanitizePath(p)); } - ///--- Exports module.exports = { - // Client API - createClient: createClient, - createJsonClient: createJsonClient, - createJSONClient: createJsonClient, - createStringClient: createStringClient, - createHttpClient: createHttpClient, - get HttpClient() { - return (require('./clients').HttpClient); - }, - get JsonClient() { - return (require('./clients').JsonClient); - }, - get StringClient() { - return (require('./clients').StringClient); - }, - - // Miscellaneous API - get bunyan() { - return (require('./bunyan_helper')); - }, - - errors: {} + // Client API + createClient: createClient, + createJsonClient: createJsonClient, + createJSONClient: createJsonClient, + createStringClient: createStringClient, + createHttpClient: createHttpClient, + get HttpClient() { + return (require('./clients').HttpClient); + }, + get JsonClient() { + return (require('./clients').JsonClient); + }, + get StringClient() { + return (require('./clients').StringClient); + }, + + // Miscellaneous API + get bunyan() { + return (require('./bunyan_helper')); + }, + + errors: {} }; var errors = require('./errors'); Object.keys(errors).forEach(function (k) { - module.exports.errors[k] = errors[k]; - module.exports[k] = errors[k]; + module.exports.errors[k] = errors[k]; + module.exports[k] = errors[k]; }); if (!process.env.RESTIFY_CLIENT_ONLY) { - module.exports.createServer = createServer; - module.exports.httpDate = require('./http_date'); - module.exports.realizeUrl = realizeUrl; - module.exports.formatters = require('./formatters'); - module.exports.plugins = {}; - var plugins = require('./plugins'); - Object.keys(plugins).forEach(function (k) { - module.exports.plugins[k] = plugins[k]; - module.exports[k] = plugins[k]; - }); + module.exports.createServer = createServer; + module.exports.httpDate = require('./http_date'); + module.exports.realizeUrl = realizeUrl; + module.exports.formatters = require('./formatters'); + module.exports.plugins = {}; + var plugins = require('./plugins'); + Object.keys(plugins).forEach(function (k) { + module.exports.plugins[k] = plugins[k]; + module.exports[k] = plugins[k]; + }); } diff --git a/lib/plugins/accept.js b/lib/plugins/accept.js index 0ac16d6ce..e75b176b9 100644 --- a/lib/plugins/accept.js +++ b/lib/plugins/accept.js @@ -18,31 +18,31 @@ var NotAcceptableError = require('../errors').NotAcceptableError; * @throws {TypeError} on bad input */ function acceptParser(acceptable) { - if (!Array.isArray(acceptable)) - acceptable = [acceptable]; - assert.arrayOfString(acceptable, 'acceptable'); - - acceptable = acceptable.filter(function (a) { - return (a); - }).map(function (a) { - return ((a.indexOf('/') === -1) ? mime.lookup(a) : a); + if (!Array.isArray(acceptable)) + acceptable = [acceptable]; + assert.arrayOfString(acceptable, 'acceptable'); + + acceptable = acceptable.filter(function (a) { + return (a); + }).map(function (a) { + return ((a.indexOf('/') === -1) ? mime.lookup(a) : a); }).filter(function (a) { - return (a); + return (a); }); - var e = new NotAcceptableError('Server accepts: ' + acceptable.join()); + var e = new NotAcceptableError('Server accepts: ' + acceptable.join()); - function parseAccept(req, res, next) { - if (req.accepts(acceptable)) { - next(); - return; - } - - res.json(e); - next(false); + function parseAccept(req, res, next) { + if (req.accepts(acceptable)) { + next(); + return; } - return (parseAccept); + res.json(e); + next(false); + } + + return (parseAccept); } module.exports = acceptParser; diff --git a/lib/plugins/audit.js b/lib/plugins/audit.js index eae888e86..fef658300 100644 --- a/lib/plugins/audit.js +++ b/lib/plugins/audit.js @@ -20,87 +20,86 @@ var HttpError = require('../errors').HttpError; * @return {Function} to be used in server.after. */ function auditLogger(options) { - assert.object(options, 'options'); - assert.object(options.log, 'options.log'); - - var log = options.log.child({ - audit: true, - serializers: { - err: bunyan.stdSerializers.err, - req: function auditRequestSerializer(req) { - if (!req) - return (false); - - var timers = {}; - (req.timers || []).forEach(function (time) { - var t = time.time; - var _t = Math.floor((1000000 * t[0]) + - (t[1] / 1000)); - timers[time.name] = _t; - }); - return ({ - method: req.method, - url: req.url, - headers: req.headers, - httpVersion: req.httpVersion, - trailers: req.trailers, - version: req.version, - body: options.body === true ? - req.body : undefined, - timers: timers - }); - }, - res: function auditResponseSerializer(res) { - if (!res) - return (false); - - - var body; - if (options.body === true) { - if (res._body instanceof HttpError) { - body = res._body.body; - } else { - body = res._body; - } - } - - return ({ - statusCode: res.statusCode, - headers: res._headers, - trailer: res._trailer || false, - body: body - }); - } + assert.object(options, 'options'); + assert.object(options.log, 'options.log'); + + var log = options.log.child({ + audit: true, + serializers: { + err: bunyan.stdSerializers.err, + req: function auditRequestSerializer(req) { + if (!req) + return (false); + + var timers = {}; + (req.timers || []).forEach(function (time) { + var t = time.time; + var _t = Math.floor((1000000 * t[0]) + + (t[1] / 1000)); + timers[time.name] = _t; + }); + return ({ + method: req.method, + url: req.url, + headers: req.headers, + httpVersion: req.httpVersion, + trailers: req.trailers, + version: req.version, + body: options.body === true ? + req.body : undefined, + timers: timers + }); + }, + res: function auditResponseSerializer(res) { + if (!res) + return (false); + + + var body; + if (options.body === true) { + if (res._body instanceof HttpError) { + body = res._body.body; + } else { + body = res._body; + } } - }); - - function audit(req, res, route, err) { - var latency = res.get('Response-Time'); - if (typeof (latency) !== 'number') - latency = Date.now() - req._time; - - var obj = { - remoteAddress: req.connection.remoteAddress, - remotePort: req.connection.remotePort, - req_id: req.getId(), - req: req, - res: res, - err: err, - latency: latency, - secure: req.secure, - _audit: true - }; - - log.info(obj, 'handled: %d', res.statusCode); - - return (true); - } - return (audit); + return ({ + statusCode: res.statusCode, + headers: res._headers, + trailer: res._trailer || false, + body: body + }); + } + } + }); + + function audit(req, res, route, err) { + var latency = res.get('Response-Time'); + if (typeof (latency) !== 'number') + latency = Date.now() - req._time; + + var obj = { + remoteAddress: req.connection.remoteAddress, + remotePort: req.connection.remotePort, + req_id: req.getId(), + req: req, + res: res, + err: err, + latency: latency, + secure: req.secure, + _audit: true + }; + + log.info(obj, 'handled: %d', res.statusCode); + + return (true); + } + + return (audit); } - ///-- Exports module.exports = auditLogger; diff --git a/lib/plugins/authorization.js b/lib/plugins/authorization.js index 000f73313..3b225fc99 100644 --- a/lib/plugins/authorization.js +++ b/lib/plugins/authorization.js @@ -5,74 +5,71 @@ var httpSignature = require('http-signature'); var errors = require('../errors'); - ///--- Globals var InvalidHeaderError = errors.InvalidHeaderError; var OPTIONS = { - algorithms: [ - 'rsa-sha1', - 'rsa-sha256', - 'rsa-sha512', - 'dsa-sha1', - 'hmac-sha1', - 'hmac-sha256', - 'hmac-sha512' - ] + algorithms: [ + 'rsa-sha1', + 'rsa-sha256', + 'rsa-sha512', + 'dsa-sha1', + 'hmac-sha1', + 'hmac-sha256', + 'hmac-sha512' + ] }; - ///--- Helpers function parseBasic(string) { - var decoded; - var index; - var pieces; - - decoded = (new Buffer(string, 'base64')).toString('utf8'); - if (!decoded) - throw new InvalidHeaderError('Authorization header invalid'); - - index = decoded.indexOf(':'); - if (index === -1) { - pieces = [decoded]; - } else { - pieces = [decoded.slice(0, index), decoded.slice(index + 1)]; - } - - if (!pieces || typeof (pieces[0]) !== 'string') - throw new InvalidHeaderError('Authorization header invalid'); - - // Allows for usernameless authentication - if (!pieces[0]) - pieces[0] = null; - - // Allows for passwordless authentication - if (!pieces[1]) - pieces[1] = null; - - return ({ - username: pieces[0], - password: pieces[1] - }); + var decoded; + var index; + var pieces; + + decoded = (new Buffer(string, 'base64')).toString('utf8'); + if (!decoded) + throw new InvalidHeaderError('Authorization header invalid'); + + index = decoded.indexOf(':'); + if (index === -1) { + pieces = [decoded]; + } else { + pieces = [decoded.slice(0, index), decoded.slice(index + 1)]; + } + + if (!pieces || typeof (pieces[0]) !== 'string') + throw new InvalidHeaderError('Authorization header invalid'); + + // Allows for usernameless authentication + if (!pieces[0]) + pieces[0] = null; + + // Allows for passwordless authentication + if (!pieces[1]) + pieces[1] = null; + + return ({ + username: pieces[0], + password: pieces[1] + }); } function parseSignature(request) { - try { - return (httpSignature.parseRequest(request, { - algorithms: OPTIONS.algorithms - })); - } catch (e) { - throw new InvalidHeaderError('Authorization header invalid: ' + - e.message); - } + try { + return (httpSignature.parseRequest(request, { + algorithms: OPTIONS.algorithms + })); + } catch (e) { + throw new InvalidHeaderError('Authorization header invalid: ' + + e.message); + } } - /** * Returns a plugin that will parse the client's Authorization header. * @@ -94,48 +91,48 @@ function parseSignature(request) { */ function authorizationParser() { - function parseAuthorization(req, res, next) { - req.authorization = {}; - req.username = 'anonymous'; - - if (!req.headers.authorization) - return (next()); - - var pieces = req.headers.authorization.split(' ', 2); - if (!pieces || pieces.length !== 2) { - var e = new InvalidHeaderError('BasicAuth content ' + - 'is invalid.'); - return (next(e)); - } - - req.authorization.scheme = pieces[0]; - req.authorization.credentials = pieces[1]; - - try { - switch (pieces[0].toLowerCase()) { - case 'basic': - req.authorization.basic = parseBasic(pieces[1]); - req.username = req.authorization.basic.username; - break; - - case 'signature': - req.authorization.signature = - parseSignature(req); - req.username = - req.authorization.signature.keyId; - break; - - default: - break; - } - } catch (e2) { - return (next(e2)); - } - - return (next()); + function parseAuthorization(req, res, next) { + req.authorization = {}; + req.username = 'anonymous'; + + if (!req.headers.authorization) + return (next()); + + var pieces = req.headers.authorization.split(' ', 2); + if (!pieces || pieces.length !== 2) { + var e = new InvalidHeaderError('BasicAuth content ' + + 'is invalid.'); + return (next(e)); + } + + req.authorization.scheme = pieces[0]; + req.authorization.credentials = pieces[1]; + + try { + switch (pieces[0].toLowerCase()) { + case 'basic': + req.authorization.basic = parseBasic(pieces[1]); + req.username = req.authorization.basic.username; + break; + + case 'signature': + req.authorization.signature = + parseSignature(req); + req.username = + req.authorization.signature.keyId; + break; + + default: + break; + } + } catch (e2) { + return (next(e2)); } - return (parseAuthorization); + return (next()); + } + + return (parseAuthorization); } module.exports = authorizationParser; diff --git a/lib/plugins/body_parser.js b/lib/plugins/body_parser.js index f3bc8c6fa..851921f36 100644 --- a/lib/plugins/body_parser.js +++ b/lib/plugins/body_parser.js @@ -11,81 +11,79 @@ var multipartParser = require('./multipart_parser'); var fieldedTextParser = require('./fielded_text_body_parser.js'); - ///--- Globals var UnsupportedMediaTypeError = errors.UnsupportedMediaTypeError; - ///--- API function bodyParser(options) { - assert.optionalObject(options, 'options'); - options = options || {}; - options.bodyReader = true; - - var read = bodyReader(options); - var parseForm = formParser(options); - var parseJson = jsonParser(options); - var parseMultipart = multipartParser(options); - var parseFieldedText = fieldedTextParser(options); - - function parseBody(req, res, next) { - // Allow use of 'requestBodyOnGet' flag to allow for merging of - // the request body of a GET request into req.params - if (req.method === 'HEAD') { - next(); - return; - } - if (req.method === 'GET') { - if (!options.requestBodyOnGet) { - next(); - return; - } - } - - if (req.contentLength() === 0 && !req.isChunked()) { - next(); - return; - } - - var parser; - var type = req.contentType(); - switch (type) { - case 'application/json': - parser = parseJson[0]; - break; - case 'application/x-www-form-urlencoded': - parser = parseForm[0]; - break; - case 'multipart/form-data': - parser = parseMultipart; - break; - case 'text/tsv': - parser = parseFieldedText; - break; - case 'text/tab-separated-values': - parser = parseFieldedText; - break; - case 'text/csv': - parser = parseFieldedText; - break; - - default: - break; - } - - if (parser) { - parser(req, res, next); - } else if (options && options.rejectUnknown) { - next(new UnsupportedMediaTypeError(type)); - } else { - next(); - } + assert.optionalObject(options, 'options'); + options = options || {}; + options.bodyReader = true; + + var read = bodyReader(options); + var parseForm = formParser(options); + var parseJson = jsonParser(options); + var parseMultipart = multipartParser(options); + var parseFieldedText = fieldedTextParser(options); + + function parseBody(req, res, next) { + // Allow use of 'requestBodyOnGet' flag to allow for merging of + // the request body of a GET request into req.params + if (req.method === 'HEAD') { + next(); + return; + } + if (req.method === 'GET') { + if (!options.requestBodyOnGet) { + next(); + return; + } + } + + if (req.contentLength() === 0 && !req.isChunked()) { + next(); + return; + } + + var parser; + var type = req.contentType(); + switch (type) { + case 'application/json': + parser = parseJson[0]; + break; + case 'application/x-www-form-urlencoded': + parser = parseForm[0]; + break; + case 'multipart/form-data': + parser = parseMultipart; + break; + case 'text/tsv': + parser = parseFieldedText; + break; + case 'text/tab-separated-values': + parser = parseFieldedText; + break; + case 'text/csv': + parser = parseFieldedText; + break; + + default: + break; + } + + if (parser) { + parser(req, res, next); + } else if (options && options.rejectUnknown) { + next(new UnsupportedMediaTypeError(type)); + } else { + next(); } + } - return ([read, parseBody]); + return ([read, parseBody]); } module.exports = bodyParser; diff --git a/lib/plugins/body_reader.js b/lib/plugins/body_reader.js index 1950fb64d..3b02812e0 100644 --- a/lib/plugins/body_reader.js +++ b/lib/plugins/body_reader.js @@ -8,7 +8,6 @@ var assert = require('assert-plus'); var errors = require('../errors'); - ///--- Globals var BadDigestError = errors.BadDigestError; @@ -18,110 +17,108 @@ var RequestEntityTooLargeError = errors.RequestEntityTooLargeError; var MD5_MSG = 'Content-MD5 \'%s\' didn\'t match \'%s\''; - ///--- Helpers function createBodyWriter(req) { - var contentType = req.contentType(); - if (!contentType || - contentType === 'application/json' || - contentType === 'application/x-www-form-urlencoded' || - contentType === 'multipart/form-data' || - contentType.substr(0, 5) === 'text/') { - - req.body = ''; - return (function (chunk) { - req.body += chunk.toString('utf8'); - }); - } - - req.body = new Buffer(0); + var contentType = req.contentType(); + if (!contentType || + contentType === 'application/json' || + contentType === 'application/x-www-form-urlencoded' || + contentType === 'multipart/form-data' || + contentType.substr(0, 5) === 'text/') { + + req.body = ''; return (function (chunk) { - req.body = Buffer.concat([req.body, chunk]); + req.body += chunk.toString('utf8'); }); -} + } + req.body = new Buffer(0); + return (function (chunk) { + req.body = Buffer.concat([req.body, chunk]); + }); +} ///--- API function bodyReader(options) { - options = options || {}; - assert.object(options, 'options'); - - var maxBodySize = options.maxBodySize || 0; - - function readBody(req, res, next) { - if ((req.getContentLength() === 0 && !req.isChunked()) || - req.contentType() === 'multipart/form-data' || - req.contentType() === 'application/octet-stream') { - next(); - return; - } - var bodyWriter = createBodyWriter(req); - - function done() { - if (maxBodySize && bytesReceived > maxBodySize) { - var msg = 'Request body size exceeds ' + - maxBodySize; - next(new RequestEntityTooLargeError(msg)); - return; - } - - if (!req.body.length) { - next(); - return; - } - - if (hash && md5 !== (digest = hash.digest('base64'))) { - next(new BadDigestError(MD5_MSG, md5, digest)); - return; - } - - next(); - } - - var bytesReceived = 0; - var digest; - var gz; - var hash; - var md5; - if ((md5 = req.headers['content-md5'])) - hash = crypto.createHash('md5'); - - if (req.headers['content-encoding'] === 'gzip') { - gz = zlib.createGunzip(); - gz.on('data', function onData(chunk) { - bodyWriter(chunk); - }); - gz.once('end', done); - req.once('end', gz.end.bind(gz)); - } else { - req.once('end', done); - } - - req.on('data', function onRequestData(chunk) { - if (maxBodySize) { - bytesReceived += chunk.length; - if (bytesReceived > maxBodySize) - return; - } - - if (hash) - hash.update(chunk, 'binary'); - - if (gz) { - gz.write(chunk); - } else { - bodyWriter(chunk); - } - }); - - req.once('error', next); - req.resume(); + options = options || {}; + assert.object(options, 'options'); + + var maxBodySize = options.maxBodySize || 0; + + function readBody(req, res, next) { + if ((req.getContentLength() === 0 && !req.isChunked()) || + req.contentType() === 'multipart/form-data' || + req.contentType() === 'application/octet-stream') { + next(); + return; + } + var bodyWriter = createBodyWriter(req); + + function done() { + if (maxBodySize && bytesReceived > maxBodySize) { + var msg = 'Request body size exceeds ' + + maxBodySize; + next(new RequestEntityTooLargeError(msg)); + return; + } + + if (!req.body.length) { + next(); + return; + } + + if (hash && md5 !== (digest = hash.digest('base64'))) { + next(new BadDigestError(MD5_MSG, md5, digest)); + return; + } + + next(); + } + + var bytesReceived = 0; + var digest; + var gz; + var hash; + var md5; + if ((md5 = req.headers['content-md5'])) + hash = crypto.createHash('md5'); + + if (req.headers['content-encoding'] === 'gzip') { + gz = zlib.createGunzip(); + gz.on('data', function onData(chunk) { + bodyWriter(chunk); + }); + gz.once('end', done); + req.once('end', gz.end.bind(gz)); + } else { + req.once('end', done); } - return (readBody); + req.on('data', function onRequestData(chunk) { + if (maxBodySize) { + bytesReceived += chunk.length; + if (bytesReceived > maxBodySize) + return; + } + + if (hash) + hash.update(chunk, 'binary'); + + if (gz) { + gz.write(chunk); + } else { + bodyWriter(chunk); + } + }); + + req.once('error', next); + req.resume(); + } + + return (readBody); } module.exports = bodyReader; diff --git a/lib/plugins/bunyan.js b/lib/plugins/bunyan.js index 6f4198639..4bc16a7db 100644 --- a/lib/plugins/bunyan.js +++ b/lib/plugins/bunyan.js @@ -5,43 +5,41 @@ var assert = require('assert-plus'); var shallowCopy = require('../utils').shallowCopy; - ///--- API function requestLogger(options) { - assert.optionalObject(options); - options = options || {}; - - var props; - if (options.properties) { - props = shallowCopy(options.properties); - } else { - props = {}; + assert.optionalObject(options); + options = options || {}; + + var props; + if (options.properties) { + props = shallowCopy(options.properties); + } else { + props = {}; + } + if (options.serializers) + props.serializers = options.serializers; + + function bunyan(req, res, next) { + if (!req.log && !options.log) { + next(); + return; } - if (options.serializers) - props.serializers = options.serializers; - - function bunyan(req, res, next) { - if (!req.log && !options.log) { - next(); - return; - } - var log = req.log || options.log; + var log = req.log || options.log; - props.req_id = req.getId(); - req.log = log.child(props, props.serializers ? false : true); - if (props.req_id) - delete props.req_id; + props.req_id = req.getId(); + req.log = log.child(props, props.serializers ? false : true); + if (props.req_id) + delete props.req_id; - next(); - } + next(); + } - return (bunyan); + return (bunyan); } - ///--- Exports module.exports = requestLogger; diff --git a/lib/plugins/conditional_request.js b/lib/plugins/conditional_request.js index 63cc9212f..3e3321be7 100644 --- a/lib/plugins/conditional_request.js +++ b/lib/plugins/conditional_request.js @@ -3,7 +3,6 @@ var errors = require('../errors'); - ///--- Globals var BadRequestError = errors.BadRequestError; @@ -15,165 +14,163 @@ var IF_MOD_FAIL = 'object was modified at \'%s\'; if-modified-since \'%s\''; var IF_UNMOD_FAIL = 'object was modified at \'%s\'; if-unmodified-since \'%s\''; - ///--- API // Reference RFC2616 section 14 for an explanation of what this all does. function checkIfMatch(req, res, next) { - var clientETags; - var cur; - var etag = res.etag || res.getHeader('etag') || ''; - var ifMatch; - var matched = false; - - if ((ifMatch = req.headers['if-match'])) { - /* JSSTYLED */ - clientETags = ifMatch.split(/\s*,\s*/); - - for (var i = 0; i < clientETags.length; i++) { - cur = clientETags[i]; - // only strong comparison - /* JSSTYLED */ - cur = cur.replace(/^W\//, ''); - /* JSSTYLED */ - cur = cur.replace(/^"(\w*)"$/, '$1'); - - if (cur === '*' || cur === etag) { - matched = true; - break; - } - } - - if (!matched) { - var err = new PreconditionFailedError(IF_MATCH_FAIL, - ifMatch, - etag); - return (next(err)); - } + var clientETags; + var cur; + var etag = res.etag || res.getHeader('etag') || ''; + var ifMatch; + var matched = false; + + if ((ifMatch = req.headers['if-match'])) { + /* JSSTYLED */ + clientETags = ifMatch.split(/\s*,\s*/); + + for (var i = 0; i < clientETags.length; i++) { + cur = clientETags[i]; + // only strong comparison + /* JSSTYLED */ + cur = cur.replace(/^W\//, ''); + /* JSSTYLED */ + cur = cur.replace(/^"(\w*)"$/, '$1'); + + if (cur === '*' || cur === etag) { + matched = true; + break; + } } - return (next()); -} - - -function checkIfNoneMatch(req, res, next) { - var clientETags; - var cur; - var etag = res.etag || res.getHeader('etag') || ''; - var ifNoneMatch; - var matched = false; - - if ((ifNoneMatch = req.headers['if-none-match'])) { - /* JSSTYLED */ - clientETags = ifNoneMatch.split(/\s*,\s*/); - - for (var i = 0; i < clientETags.length; i++) { - cur = clientETags[i]; - // ignore weak validation - /* JSSTYLED */ - cur = cur.replace(/^W\//, ''); - /* JSSTYLED */ - cur = cur.replace(/^"(\w*)"$/, '$1'); - - if (cur === '*' || cur === etag) { - matched = true; - break; - } - } - - if (!matched) - return (next()); - - if (req.method !== 'GET' && req.method !== 'HEAD') { - var err = new PreconditionFailedError(IF_NO_MATCH_FAIL, - ifNoneMatch, - etag); - return (next(err)); - } - - res.send(304); - return (next(false)); + if (!matched) { + var err = new PreconditionFailedError(IF_MATCH_FAIL, + ifMatch, + etag); + return (next(err)); } + } - return (next()); + return (next()); } -function checkIfModified(req, res, next) { - var code; - var err; - var ctime = req.header('if-modified-since'); - var mtime = res.mtime || res.header('Last-Modified') || ''; - - if (!mtime || !ctime) { - next(); - return; +function checkIfNoneMatch(req, res, next) { + var clientETags; + var cur; + var etag = res.etag || res.getHeader('etag') || ''; + var ifNoneMatch; + var matched = false; + + if ((ifNoneMatch = req.headers['if-none-match'])) { + /* JSSTYLED */ + clientETags = ifNoneMatch.split(/\s*,\s*/); + + for (var i = 0; i < clientETags.length; i++) { + cur = clientETags[i]; + // ignore weak validation + /* JSSTYLED */ + cur = cur.replace(/^W\//, ''); + /* JSSTYLED */ + cur = cur.replace(/^"(\w*)"$/, '$1'); + + if (cur === '*' || cur === etag) { + matched = true; + break; + } } - try { - // - // TODO handle Range header modifications - // - // Note: this is not technically correct as per 2616 - - // 2616 only specifies semantics for GET requests, not - // any other method - but using if-modified-since with a - // PUT or DELETE seems like returning 412 is sane - // - if (Date.parse(mtime) <= Date.parse(ctime)) { - switch (req.method) { - case 'GET': - case 'HEAD': - code = 304; - break; - - default: - err = new PreconditionFailedError(IF_MOD_FAIL, - mtime, - ctime); - break; - } - } - } catch (e) { - next(new BadRequestError(e.message)); - return; - } + if (!matched) + return (next()); - if (code !== undefined) { - res.send(code); - next(false); - return; + if (req.method !== 'GET' && req.method !== 'HEAD') { + var err = new PreconditionFailedError(IF_NO_MATCH_FAIL, + ifNoneMatch, + etag); + return (next(err)); } - next(err); -} + res.send(304); + return (next(false)); + } + return (next()); +} -function checkIfUnmodified(req, res, next) { - var err; - var ctime = req.headers['if-unmodified-since']; - var mtime = res.mtime || res.header('Last-Modified') || ''; - if (!mtime || !ctime) { - next(); - return; +function checkIfModified(req, res, next) { + var code; + var err; + var ctime = req.header('if-modified-since'); + var mtime = res.mtime || res.header('Last-Modified') || ''; + + if (!mtime || !ctime) { + next(); + return; + } + + try { + // + // TODO handle Range header modifications + // + // Note: this is not technically correct as per 2616 - + // 2616 only specifies semantics for GET requests, not + // any other method - but using if-modified-since with a + // PUT or DELETE seems like returning 412 is sane + // + if (Date.parse(mtime) <= Date.parse(ctime)) { + switch (req.method) { + case 'GET': + case 'HEAD': + code = 304; + break; + + default: + err = new PreconditionFailedError(IF_MOD_FAIL, + mtime, + ctime); + break; + } } + } catch (e) { + next(new BadRequestError(e.message)); + return; + } + + if (code !== undefined) { + res.send(code); + next(false); + return; + } + + next(err); +} + - try { - if (Date.parse(mtime) > Date.parse(ctime)) { - err = new PreconditionFailedError(IF_UNMOD_FAIL, - mtime, - ctime); - } - } catch (e) { - next(new BadRequestError(e.message)); - return; +function checkIfUnmodified(req, res, next) { + var err; + var ctime = req.headers['if-unmodified-since']; + var mtime = res.mtime || res.header('Last-Modified') || ''; + + if (!mtime || !ctime) { + next(); + return; + } + + try { + if (Date.parse(mtime) > Date.parse(ctime)) { + err = new PreconditionFailedError(IF_UNMOD_FAIL, + mtime, + ctime); } + } catch (e) { + next(new BadRequestError(e.message)); + return; + } - next(err); + next(err); } - ///--- Exports /** @@ -183,13 +180,13 @@ function checkIfUnmodified(req, res, next) { * If-Unmodified-Since header. */ function conditionalRequest() { - var chain = [ - checkIfMatch, - checkIfNoneMatch, - checkIfModified, - checkIfUnmodified - ]; - return (chain); + var chain = [ + checkIfMatch, + checkIfNoneMatch, + checkIfModified, + checkIfUnmodified + ]; + return (chain); } module.exports = conditionalRequest; diff --git a/lib/plugins/cors.js b/lib/plugins/cors.js index fb1f19dda..8b69b163c 100644 --- a/lib/plugins/cors.js +++ b/lib/plugins/cors.js @@ -3,27 +3,26 @@ var assert = require('assert-plus'); - ///--- Globals var ALLOW_HEADERS = [ - 'accept', - 'accept-version', - 'content-type', - 'request-id', - 'origin', - 'x-api-version', - 'x-request-id' + 'accept', + 'accept-version', + 'content-type', + 'request-id', + 'origin', + 'x-api-version', + 'x-request-id' ]; var EXPOSE_HEADERS = [ - 'api-version', - 'content-length', - 'content-md5', - 'content-type', - 'date', - 'request-id', - 'response-time' + 'api-version', + 'content-length', + 'content-md5', + 'content-type', + 'date', + 'request-id', + 'response-time' ]; // Normal @@ -32,24 +31,22 @@ var AC_ALLOW_CREDS = 'Access-Control-Allow-Credentials'; var AC_EXPOSE_HEADERS = 'Access-Control-Expose-Headers'; - ///--- Internal Functions function matchOrigin(req, origins) { - var origin = req.headers['origin']; - - function belongs(o) { - if (origin === o || o === '*') { - origin = o; - return (true); - } + var origin = req.headers['origin']; - return (false); + function belongs(o) { + if (origin === o || o === '*') { + origin = o; + return (true); } - return ((origin && origins.some(belongs)) ? origin : false); -} + return (false); + } + return ((origin && origins.some(belongs)) ? origin : false); +} ///--- API @@ -74,47 +71,46 @@ function matchOrigin(req, origins) { // Pre-flight requests are handled by the router internally // function cors(opts) { - assert.optionalObject(opts, 'options'); - opts = opts || {}; - assert.optionalArrayOfString(opts.origins, 'options.origins'); - assert.optionalBool(opts.credentials, 'options.credentials'); - assert.optionalArrayOfString(opts.headers, 'options.headers'); - - var headers = (opts.headers || []).slice(0); - var origins = opts.origins || ['*']; - - EXPOSE_HEADERS.forEach(function (h) { - if (headers.indexOf(h) === -1) - headers.push(h); - }); - - // Handler for simple requests - function restifyCORSSimple(req, res, next) { - var origin; - if (!(origin = matchOrigin(req, origins))) { - next(); - return; - } - - function corsOnHeader() { - origin = req.headers['origin']; - if (opts.credentials) { - res.setHeader(AC_ALLOW_ORIGIN, origin); - res.setHeader(AC_ALLOW_CREDS, 'true'); - } else { - res.setHeader(AC_ALLOW_ORIGIN, origin); - } - - res.setHeader(AC_EXPOSE_HEADERS, headers.join(', ')); - } - - res.once('header', corsOnHeader); - next(); + assert.optionalObject(opts, 'options'); + opts = opts || {}; + assert.optionalArrayOfString(opts.origins, 'options.origins'); + assert.optionalBool(opts.credentials, 'options.credentials'); + assert.optionalArrayOfString(opts.headers, 'options.headers'); + + var headers = (opts.headers || []).slice(0); + var origins = opts.origins || ['*']; + + EXPOSE_HEADERS.forEach(function (h) { + if (headers.indexOf(h) === -1) + headers.push(h); + }); + + // Handler for simple requests + function restifyCORSSimple(req, res, next) { + var origin; + if (!(origin = matchOrigin(req, origins))) { + next(); + return; } - return (restifyCORSSimple); -} + function corsOnHeader() { + origin = req.headers['origin']; + if (opts.credentials) { + res.setHeader(AC_ALLOW_ORIGIN, origin); + res.setHeader(AC_ALLOW_CREDS, 'true'); + } else { + res.setHeader(AC_ALLOW_ORIGIN, origin); + } + + res.setHeader(AC_EXPOSE_HEADERS, headers.join(', ')); + } + res.once('header', corsOnHeader); + next(); + } + + return (restifyCORSSimple); +} ///--- Exports diff --git a/lib/plugins/date.js b/lib/plugins/date.js index 1605d056a..dfae70ea8 100644 --- a/lib/plugins/date.js +++ b/lib/plugins/date.js @@ -5,7 +5,6 @@ var assert = require('assert-plus'); var errors = require('../errors'); - ///--- Globals var InvalidHeaderError = errors.InvalidHeaderError; @@ -15,7 +14,6 @@ var BAD_MSG = 'Date header is invalid'; var OLD_MSG = 'Date header %s is too old'; - ///--- API /** @@ -29,51 +27,51 @@ var OLD_MSG = 'Date header %s is too old'; * @throws {TypeError} on bad input */ function dateParser(clockSkew) { - if (!clockSkew) - clockSkew = 300; - assert.number(clockSkew, 'clockSkew'); + if (!clockSkew) + clockSkew = 300; + assert.number(clockSkew, 'clockSkew'); - clockSkew = clockSkew * 1000; + clockSkew = clockSkew * 1000; - function parseDate(req, res, next) { - if (!req.headers.date) - return (next()); + function parseDate(req, res, next) { + if (!req.headers.date) + return (next()); - var e; - var date = req.headers.date; - var log = req.log; + var e; + var date = req.headers.date; + var log = req.log; - try { - var now = Date.now(); - var sent = new Date(date).getTime(); + try { + var now = Date.now(); + var sent = new Date(date).getTime(); - if (log.trace()) { - log.trace({ - allowedSkew: clockSkew, - now: now, - sent: sent - }, 'Checking clock skew'); - } + if (log.trace()) { + log.trace({ + allowedSkew: clockSkew, + now: now, + sent: sent + }, 'Checking clock skew'); + } - if ((now - sent) > clockSkew) { - e = new RequestExpiredError(OLD_MSG, date); - return (next(e)); - } + if ((now - sent) > clockSkew) { + e = new RequestExpiredError(OLD_MSG, date); + return (next(e)); + } - } catch (err) { - log.trace({ - err: err - }, 'Bad Date header: %s', date); + } catch (err) { + log.trace({ + err: err + }, 'Bad Date header: %s', date); - e = new InvalidHeaderError(BAD_MSG, date); - return (next(e)); - } - - return (next()); + e = new InvalidHeaderError(BAD_MSG, date); + return (next(e)); } - return (parseDate); + return (next()); + } + + return (parseDate); } module.exports = dateParser; diff --git a/lib/plugins/fielded_text_body_parser.js b/lib/plugins/fielded_text_body_parser.js index 3deb09b56..9adbc971b 100644 --- a/lib/plugins/fielded_text_body_parser.js +++ b/lib/plugins/fielded_text_body_parser.js @@ -2,7 +2,7 @@ * Dependencies */ -var csv = require('csv'); +var csv = require('csv'); var assert = require('assert-plus'); var bodyReader = require('./body_reader'); var errors = require('../errors'); @@ -19,64 +19,63 @@ var errors = require('../errors'); function fieldedTextParser(options) { - assert.optionalObject(options, 'options'); - options = options || {}; - - function parseFieldedText(req, res, next) { - - var contentType = req.getContentType(); - - if (contentType !== 'text/csv' && - contentType !== 'text/tsv' && - contentType !== 'text/tab-separated-values' || - !req.body) { - next(); - return; - } - - - var hDelimiter = req.headers['x-content-delimiter']; - var hEscape = req.headers['x-content-escape']; - var hQuote = req.headers['x-content-quote']; - var hColumns = req.headers['x-content-columns']; - - - var delimiter = (contentType === 'text/tsv') ? '\t' : ','; - delimiter = (hDelimiter) ? hDelimiter : delimiter; - var escape = (hEscape) ? hEscape : '\\'; - var quote = (hQuote) ? hQuote : '"'; - var columns = (hColumns) ? hColumns : true; - - var parsedBody = []; - - csv() - .from(req.body, { - delimiter: delimiter, - quote: quote, - escape: escape, - columns: columns - }) - .on('record', function (row, index) { - row.index = index; - parsedBody.push(row); - }) - .on('end', function (count) { - req.body = parsedBody; - return (next()); - }) - .on('error', function (error) { - return (next(error)); - }); + assert.optionalObject(options, 'options'); + options = options || {}; - } + function parseFieldedText(req, res, next) { + + var contentType = req.getContentType(); - var chain = []; - if (!options.bodyReader) { - chain.push(bodyReader(options)); + if (contentType !== 'text/csv' && + contentType !== 'text/tsv' && + contentType !== 'text/tab-separated-values' || !req.body) { + next(); + return; } - chain.push(parseFieldedText); - return (chain); + + var hDelimiter = req.headers['x-content-delimiter']; + var hEscape = req.headers['x-content-escape']; + var hQuote = req.headers['x-content-quote']; + var hColumns = req.headers['x-content-columns']; + + + var delimiter = (contentType === 'text/tsv') ? '\t' : ','; + delimiter = (hDelimiter) ? hDelimiter : delimiter; + var escape = (hEscape) ? hEscape : '\\'; + var quote = (hQuote) ? hQuote : '"'; + var columns = (hColumns) ? hColumns : true; + + var parsedBody = []; + + csv() + .from(req.body, { + delimiter: delimiter, + quote: quote, + escape: escape, + columns: columns + }) + .on('record', function (row, index) { + row.index = index; + parsedBody.push(row); + }) + .on('end', function (count) { + req.body = parsedBody; + return (next()); + }) + .on('error', function (error) { + return (next(error)); + }); + + } + + var chain = []; + if (!options.bodyReader) { + chain.push(bodyReader(options)); + } + chain.push(parseFieldedText); + + return (chain); } diff --git a/lib/plugins/form_body_parser.js b/lib/plugins/form_body_parser.js index 682e2ad20..e880d777e 100644 --- a/lib/plugins/form_body_parser.js +++ b/lib/plugins/form_body_parser.js @@ -9,13 +9,11 @@ var bodyReader = require('./body_reader'); var errors = require('../errors'); - ///--- Globals var MIME_TYPE = 'application/x-www-form-urlencoded'; - ///--- API /** @@ -29,47 +27,47 @@ var MIME_TYPE = 'application/x-www-form-urlencoded'; * @throws {TypeError} on bad input */ function urlEncodedBodyParser(options) { - options = options || {}; - assert.object(options, 'options'); - - var override = options.overrideParams; - - function parseUrlEncodedBody(req, res, next) { - if (req.getContentType() !== MIME_TYPE || !req.body) { - next(); - return; - } - - try { - var params = querystring.parse(req.body); - if (options.mapParams !== false) { - var keys = Object.keys(params); - keys.forEach(function (k) { - var p = req.params[k]; - if (p && !override) - return (false); - - req.params[k] = params[k]; - return (true); - }); - } else { - req._body = req.body; - req.body = params; - } - } catch (e) { - next(new errors.InvalidContentError(e.message)); - return; - } - - req.log.trace('req.params now: %j', req.params); - next(); + options = options || {}; + assert.object(options, 'options'); + + var override = options.overrideParams; + + function parseUrlEncodedBody(req, res, next) { + if (req.getContentType() !== MIME_TYPE || !req.body) { + next(); + return; } - var chain = []; - if (!options.bodyReader) - chain.push(bodyReader(options)); - chain.push(parseUrlEncodedBody); - return (chain); + try { + var params = querystring.parse(req.body); + if (options.mapParams !== false) { + var keys = Object.keys(params); + keys.forEach(function (k) { + var p = req.params[k]; + if (p && !override) + return (false); + + req.params[k] = params[k]; + return (true); + }); + } else { + req._body = req.body; + req.body = params; + } + } catch (e) { + next(new errors.InvalidContentError(e.message)); + return; + } + + req.log.trace('req.params now: %j', req.params); + next(); + } + + var chain = []; + if (!options.bodyReader) + chain.push(bodyReader(options)); + chain.push(parseUrlEncodedBody); + return (chain); } module.exports = urlEncodedBodyParser; diff --git a/lib/plugins/full_response.js b/lib/plugins/full_response.js index 19d298fdf..2335b179b 100644 --- a/lib/plugins/full_response.js +++ b/lib/plugins/full_response.js @@ -5,106 +5,102 @@ var crypto = require('crypto'); var httpDate = require('../http_date'); - ///--- Globals var ALLOW_HEADERS = [ - 'Accept', - 'Accept-Version', - 'Content-Length', - 'Content-MD5', - 'Content-Type', - 'Date', - 'Api-Version', - 'Response-Time' + 'Accept', + 'Accept-Version', + 'Content-Length', + 'Content-MD5', + 'Content-Type', + 'Date', + 'Api-Version', + 'Response-Time' ].join(', '); var EXPOSE_HEADERS = [ - 'Api-Version', - 'Request-Id', - 'Response-Time' + 'Api-Version', + 'Request-Id', + 'Response-Time' ].join(', '); - ///--- API function setHeaders(req, res) { - var hash; - var now = new Date(); - var methods; + var hash; + var now = new Date(); + var methods; - if (!res.getHeader('Access-Control-Allow-Origin')) - res.setHeader('Access-Control-Allow-Origin', '*'); + if (!res.getHeader('Access-Control-Allow-Origin')) + res.setHeader('Access-Control-Allow-Origin', '*'); - if (!res.getHeader('Access-Control-Allow-Headers')) - res.setHeader('Access-Control-Allow-Headers', ALLOW_HEADERS); + if (!res.getHeader('Access-Control-Allow-Headers')) + res.setHeader('Access-Control-Allow-Headers', ALLOW_HEADERS); - if (!res.getHeader('Access-Control-Allow-Methods')) { - if (res.methods && res.methods.length > 0) { - methods = res.methods.join(', '); - res.setHeader('Access-Control-Allow-Methods', methods); - } + if (!res.getHeader('Access-Control-Allow-Methods')) { + if (res.methods && res.methods.length > 0) { + methods = res.methods.join(', '); + res.setHeader('Access-Control-Allow-Methods', methods); } + } - if (!res.getHeader('Access-Control-Expose-Headers')) - res.setHeader('Access-Control-Expose-Headers', EXPOSE_HEADERS); + if (!res.getHeader('Access-Control-Expose-Headers')) + res.setHeader('Access-Control-Expose-Headers', EXPOSE_HEADERS); - if (!res.getHeader('Connection')) { - res.setHeader('Connection', - req.isKeepAlive() ? 'Keep-Alive' : 'close'); - } + if (!res.getHeader('Connection')) { + res.setHeader('Connection', + req.isKeepAlive() ? 'Keep-Alive' : 'close'); + } - if (res._data && !res.getHeader('Content-MD5')) { - hash = crypto.createHash('md5'); - hash.update(res._data); - res.setHeader('Content-MD5', hash.digest('base64')); - } + if (res._data && !res.getHeader('Content-MD5')) { + hash = crypto.createHash('md5'); + hash.update(res._data); + res.setHeader('Content-MD5', hash.digest('base64')); + } - if (!res.getHeader('Date')) - res.setHeader('Date', httpDate(now)); + if (!res.getHeader('Date')) + res.setHeader('Date', httpDate(now)); - if (res.etag && !res.getHeader('Etag')) - res.setHeader('Etag', res.etag); + if (res.etag && !res.getHeader('Etag')) + res.setHeader('Etag', res.etag); - if (!res.getHeader('Server')) - res.setHeader('Server', res.serverName); + if (!res.getHeader('Server')) + res.setHeader('Server', res.serverName); - if (res.version && !res.getHeader('Api-Version')) - res.setHeader('Api-Version', res.version); + if (res.version && !res.getHeader('Api-Version')) + res.setHeader('Api-Version', res.version); - if (!res.getHeader('Request-Id')) - res.setHeader('Request-Id', req.getId()); + if (!res.getHeader('Request-Id')) + res.setHeader('Request-Id', req.getId()); - if (!res.getHeader('Response-Time')) - res.setHeader('Response-Time', now.getTime() - req._time); + if (!res.getHeader('Response-Time')) + res.setHeader('Response-Time', now.getTime() - req._time); } - function fullResponse() { - function restifyResponseHeaders(req, res, next) { - res.once('header', function () { + function restifyResponseHeaders(req, res, next) { + res.once('header', function () { - // Restify 1.0 compatibility - if (res.defaultResponseFormatters) - res.defaultResponseFormatters(res._data); + // Restify 1.0 compatibility + if (res.defaultResponseFormatters) + res.defaultResponseFormatters(res._data); - res.emit('beforeSend', res._data, res._body); + res.emit('beforeSend', res._data, res._body); - // end backwards-compatibility - return (setHeaders(req, res)); - }); + // end backwards-compatibility + return (setHeaders(req, res)); + }); - return (next()); - } + return (next()); + } - return (restifyResponseHeaders); + return (restifyResponseHeaders); } - ///--- Exports module.exports = fullResponse; diff --git a/lib/plugins/gzip.js b/lib/plugins/gzip.js index 6f5d2ea66..855a6ae1b 100644 --- a/lib/plugins/gzip.js +++ b/lib/plugins/gzip.js @@ -6,47 +6,46 @@ var assert = require('assert-plus'); function _writeHead(originalFunction) { - this.removeHeader('Content-Length'); - originalFunction.apply(this, Array.prototype.slice.call(arguments, 1)); + this.removeHeader('Content-Length'); + originalFunction.apply(this, Array.prototype.slice.call(arguments, 1)); } ///--- API function gzipResponse(opts) { - assert.optionalObject(opts, 'options'); - - function gzip(req, res, next) { - if (!req.acceptsEncoding('gzip')) { - next(); - return; - } - - var gz = zlib.createGzip(opts); - - gz.on('data', res.write.bind(res)); - gz.once('end', res.end.bind(res)); - gz.on('drain', res.emit.bind(res, 'drain')); - - var origWrite = res.write; - var origEnd = res.end; - var origWriteHead = res.writeHead; - res.handledGzip = function _handledGzip() { - res.write = origWrite; - res.end = origEnd; - res.writeHead = origWriteHead; - }; - - res.write = gz.write.bind(gz); - res.end = gz.end.bind(gz); - - res.writeHead = _writeHead.bind(res, res.writeHead); - res.setHeader('Content-Encoding', 'gzip'); - next(); + assert.optionalObject(opts, 'options'); + + function gzip(req, res, next) { + if (!req.acceptsEncoding('gzip')) { + next(); + return; } - return (gzip); -} + var gz = zlib.createGzip(opts); + + gz.on('data', res.write.bind(res)); + gz.once('end', res.end.bind(res)); + gz.on('drain', res.emit.bind(res, 'drain')); + + var origWrite = res.write; + var origEnd = res.end; + var origWriteHead = res.writeHead; + res.handledGzip = function _handledGzip() { + res.write = origWrite; + res.end = origEnd; + res.writeHead = origWriteHead; + }; + res.write = gz.write.bind(gz); + res.end = gz.end.bind(gz); + + res.writeHead = _writeHead.bind(res, res.writeHead); + res.setHeader('Content-Encoding', 'gzip'); + next(); + } + + return (gzip); +} ///--- Exports diff --git a/lib/plugins/index.js b/lib/plugins/index.js index 521294f97..c4fc56bc3 100644 --- a/lib/plugins/index.js +++ b/lib/plugins/index.js @@ -3,28 +3,28 @@ ///--- Exports module.exports = { - acceptParser: require('./accept'), - auditLogger: require('./audit'), - authorizationParser: require('./authorization'), - bodyParser: require('./body_parser'), - conditionalRequest: require('./conditional_request'), - CORS: require('./cors'), - dateParser: require('./date'), - jsonp: require('./jsonp'), - urlEncodedBodyParser: require('./form_body_parser'), - requestLogger: require('./bunyan'), - gzipResponse: require('./gzip'), - fullResponse: require('./full_response'), - jsonBodyParser: require('./json_body_parser'), - multipartBodyParser: require('./multipart_parser'), - queryParser: require('./query'), - sanitizePath: require('./pre/pre_path'), - serveStatic: require('./static'), - throttle: require('./throttle'), + acceptParser: require('./accept'), + auditLogger: require('./audit'), + authorizationParser: require('./authorization'), + bodyParser: require('./body_parser'), + conditionalRequest: require('./conditional_request'), + CORS: require('./cors'), + dateParser: require('./date'), + jsonp: require('./jsonp'), + urlEncodedBodyParser: require('./form_body_parser'), + requestLogger: require('./bunyan'), + gzipResponse: require('./gzip'), + fullResponse: require('./full_response'), + jsonBodyParser: require('./json_body_parser'), + multipartBodyParser: require('./multipart_parser'), + queryParser: require('./query'), + sanitizePath: require('./pre/pre_path'), + serveStatic: require('./static'), + throttle: require('./throttle'), - pre: { - pause: require('./pre/pause'), - sanitizePath: require('./pre/pre_path'), - userAgentConnection: require('./pre/user_agent') - } + pre: { + pause: require('./pre/pause'), + sanitizePath: require('./pre/pre_path'), + userAgentConnection: require('./pre/user_agent') + } }; diff --git a/lib/plugins/json_body_parser.js b/lib/plugins/json_body_parser.js index 579820048..1a43c0809 100644 --- a/lib/plugins/json_body_parser.js +++ b/lib/plugins/json_body_parser.js @@ -6,7 +6,6 @@ var bodyReader = require('./body_reader'); var errors = require('../errors'); - ///--- API /** @@ -20,54 +19,54 @@ var errors = require('../errors'); * @throws {TypeError} on bad input */ function jsonBodyParser(options) { - assert.optionalObject(options, 'options'); - options = options || {}; + assert.optionalObject(options, 'options'); + options = options || {}; - var override = options.overrideParams; + var override = options.overrideParams; - function parseJson(req, res, next) { - if (req.getContentType() !== 'application/json' || !req.body) { - next(); - return; - } + function parseJson(req, res, next) { + if (req.getContentType() !== 'application/json' || !req.body) { + next(); + return; + } - var params; - try { - params = JSON.parse(req.body); - } catch (e) { - next(new errors.InvalidContentError('Invalid JSON: ' + - e.message)); - return; - } + var params; + try { + params = JSON.parse(req.body); + } catch (e) { + next(new errors.InvalidContentError('Invalid JSON: ' + + e.message)); + return; + } - if (options.mapParams !== false) { - if (Array.isArray(params)) { - req.params = params; - } else if (typeof (params) === 'object') { - Object.keys(params).forEach(function (k) { - var p = req.params[k]; - if (p && !override) - return (false); - req.params[k] = params[k]; - return (true); - }); - } else { - req.params = params; - } - } else { - req._body = req.body; - } + if (options.mapParams !== false) { + if (Array.isArray(params)) { + req.params = params; + } else if (typeof (params) === 'object') { + Object.keys(params).forEach(function (k) { + var p = req.params[k]; + if (p && !override) + return (false); + req.params[k] = params[k]; + return (true); + }); + } else { + req.params = params; + } + } else { + req._body = req.body; + } - req.body = params; + req.body = params; - next(); - } + next(); + } - var chain = []; - if (!options.bodyReader) - chain.push(bodyReader(options)); - chain.push(parseJson); - return (chain); + var chain = []; + if (!options.bodyReader) + chain.push(bodyReader(options)); + chain.push(parseJson); + return (chain); } module.exports = jsonBodyParser; diff --git a/lib/plugins/jsonp.js b/lib/plugins/jsonp.js index 28eee2da8..f14b94719 100644 --- a/lib/plugins/jsonp.js +++ b/lib/plugins/jsonp.js @@ -3,24 +3,23 @@ var qs = require('qs'); - ///--- API function jsonp() { - function _jsonp(req, res, next) { - var q = req.getQuery(); + function _jsonp(req, res, next) { + var q = req.getQuery(); - // If the query plugin wasn't used, we need to hack it in now - if (typeof (q) === 'string') - req.query = qs.parse(q); + // If the query plugin wasn't used, we need to hack it in now + if (typeof (q) === 'string') + req.query = qs.parse(q); - if (req.query.callback || req.query.jsonp) - res.setHeader('Content-Type', 'application/javascript'); + if (req.query.callback || req.query.jsonp) + res.setHeader('Content-Type', 'application/javascript'); - next(); - } + next(); + } - return (_jsonp); + return (_jsonp); } diff --git a/lib/plugins/multipart_parser.js b/lib/plugins/multipart_parser.js index 4e875e775..722830795 100644 --- a/lib/plugins/multipart_parser.js +++ b/lib/plugins/multipart_parser.js @@ -6,13 +6,11 @@ var formidable = require('formidable'); var errors = require('../errors'); - ///--- Globals var BadRequestError = errors.BadRequestError; - ///--- API /** @@ -26,61 +24,62 @@ var BadRequestError = errors.BadRequestError; * @throws {TypeError} on bad input */ function multipartBodyParser(options) { - if (!options) - options = {}; - assert.object(options, 'options'); - - var override = options.overrideParams; - function parseMultipartBody(req, res, next) { - if (req.getContentType() !== 'multipart/form-data' || - (req.getContentLength() === 0 && !req.isChunked())) - return (next()); - - var form = new formidable.IncomingForm(); - form.keepExtensions = options.keepExtensions ? true : false; - if (options.uploadDir) - form.uploadDir = options.uploadDir; - - form.parse(req, function (err, fields, files) { - if (err) - return (next(new BadRequestError(err.message))); - - req.body = fields; - req.files = files; - - if (options.mapParams !== false) { - Object.keys(fields).forEach(function (k) { - if (req.params[k] && !override) - return (false); - - req.params[k] = fields[k]; - return (true); - }); - - Object.keys(files).forEach(function (f) { - if (req.params[f] && !override) - return (false); - var fs = require('fs'); - return fs.readFile( - files[f].path, - 'utf8', - function (ex, data) { - if (ex) { - return (false); - } - req.params[f] = data; - return (true); - }); - }); - } - - return (next()); + if (!options) + options = {}; + assert.object(options, 'options'); + + var override = options.overrideParams; + + function parseMultipartBody(req, res, next) { + if (req.getContentType() !== 'multipart/form-data' || + (req.getContentLength() === 0 && !req.isChunked())) + return (next()); + + var form = new formidable.IncomingForm(); + form.keepExtensions = options.keepExtensions ? true : false; + if (options.uploadDir) + form.uploadDir = options.uploadDir; + + form.parse(req, function (err, fields, files) { + if (err) + return (next(new BadRequestError(err.message))); + + req.body = fields; + req.files = files; + + if (options.mapParams !== false) { + Object.keys(fields).forEach(function (k) { + if (req.params[k] && !override) + return (false); + + req.params[k] = fields[k]; + return (true); }); - return (false); - } + Object.keys(files).forEach(function (f) { + if (req.params[f] && !override) + return (false); + var fs = require('fs'); + return fs.readFile( + files[f].path, + 'utf8', + function (ex, data) { + if (ex) { + return (false); + } + req.params[f] = data; + return (true); + }); + }); + } + + return (next()); + }); + + return (false); + } - return (parseMultipartBody); + return (parseMultipartBody); } module.exports = multipartBodyParser; diff --git a/lib/plugins/pre/pause.js b/lib/plugins/pre/pause.js index 3e74d2550..2daf5e6ff 100644 --- a/lib/plugins/pre/pause.js +++ b/lib/plugins/pre/pause.js @@ -1,54 +1,52 @@ // Copyright 2012 Mark Cavage, Inc. All rights reserved. - ///--- Helpers function pauseStream(stream) { - function _buffer(chunk) { - stream.__buffered.push(chunk); - } - - function _catchEnd(chunk) { - stream.__rstfy_ended = true; - } - - stream.__rstfy_ended = false; - stream.__rstfy_paused = true; - stream.__buffered = []; - stream.on('data', _buffer); - stream.once('end', _catchEnd); - stream.pause(); - - stream._resume = stream.resume; - stream.resume = function _rstfy_resume() { - if (!stream.__rstfy_paused) - return; - - stream.removeListener('data', _buffer); - stream.removeListener('end', _catchEnd); - - stream.__buffered.forEach(stream.emit.bind(stream, 'data')); - stream.__buffered.length = 0; - - stream._resume(); - stream.resume = stream._resume; - - if (stream.__rstfy_ended) - stream.emit('end'); - }; + function _buffer(chunk) { + stream.__buffered.push(chunk); + } + + function _catchEnd(chunk) { + stream.__rstfy_ended = true; + } + + stream.__rstfy_ended = false; + stream.__rstfy_paused = true; + stream.__buffered = []; + stream.on('data', _buffer); + stream.once('end', _catchEnd); + stream.pause(); + + stream._resume = stream.resume; + stream.resume = function _rstfy_resume() { + if (!stream.__rstfy_paused) + return; + + stream.removeListener('data', _buffer); + stream.removeListener('end', _catchEnd); + + stream.__buffered.forEach(stream.emit.bind(stream, 'data')); + stream.__buffered.length = 0; + + stream._resume(); + stream.resume = stream._resume; + + if (stream.__rstfy_ended) + stream.emit('end'); + }; } - ///--- Exports module.exports = function pause() { - function prePause(req, res, next) { - pauseStream(req); - next(); - } + function prePause(req, res, next) { + pauseStream(req); + next(); + } - return (prePause); + return (prePause); }; diff --git a/lib/plugins/pre/pre_path.js b/lib/plugins/pre/pre_path.js index 10807478e..d57a8ab54 100644 --- a/lib/plugins/pre/pre_path.js +++ b/lib/plugins/pre/pre_path.js @@ -1,7 +1,6 @@ // Copyright 2012 Mark Cavage, Inc. All rights reserved. - ///--- Helpers /** @@ -9,35 +8,34 @@ * */ function strip(path) { - var cur; - var next; - var str = ''; + var cur; + var next; + var str = ''; - for (var i = 0; i < path.length; i++) { - cur = path.charAt(i); - if (i !== path.length - 1) - next = path.charAt(i + 1); + for (var i = 0; i < path.length; i++) { + cur = path.charAt(i); + if (i !== path.length - 1) + next = path.charAt(i + 1); - if (cur === '/' && (next === '/' || (next === '?' && i > 0))) - continue; + if (cur === '/' && (next === '/' || (next === '?' && i > 0))) + continue; - str += cur; - } + str += cur; + } - return (str); + return (str); } - ///--- Exports module.exports = function sanitizePath(options) { - options = options || {}; + options = options || {}; - function _sanitizePath(req, res, next) { - req.url = strip(req.url); - next(); - } + function _sanitizePath(req, res, next) { + req.url = strip(req.url); + next(); + } - return (_sanitizePath); + return (_sanitizePath); }; diff --git a/lib/plugins/pre/user_agent.js b/lib/plugins/pre/user_agent.js index aac310088..2bb1fe3ba 100644 --- a/lib/plugins/pre/user_agent.js +++ b/lib/plugins/pre/user_agent.js @@ -3,7 +3,6 @@ var assert = require('assert-plus'); - ///--- API /** @@ -21,29 +20,29 @@ var assert = require('assert-plus'); * agent regexp, however. */ function userAgentConnection(opts) { - assert.optionalObject(opts, 'options'); - opts = opts || {}; - assert.optionalObject(opts.userAgentRegExp, 'options.userAgentRegExp'); - - var re = opts.userAgentRegExp; - if (!re) - re = /^curl.+/; + assert.optionalObject(opts, 'options'); + opts = opts || {}; + assert.optionalObject(opts.userAgentRegExp, 'options.userAgentRegExp'); - function handleUserAgent(req, res, next) { - var ua = req.headers['user-agent']; + var re = opts.userAgentRegExp; + if (!re) + re = /^curl.+/; - if (ua && re.test(ua)) - res.setHeader('Connection', 'close'); + function handleUserAgent(req, res, next) { + var ua = req.headers['user-agent']; - if (req.method === 'HEAD') { - res.once('header', - res.removeHeader.bind(res, 'content-length')); - } + if (ua && re.test(ua)) + res.setHeader('Connection', 'close'); - next(); + if (req.method === 'HEAD') { + res.once('header', + res.removeHeader.bind(res, 'content-length')); } - return (handleUserAgent); + next(); + } + + return (handleUserAgent); } module.exports = userAgentConnection; \ No newline at end of file diff --git a/lib/plugins/query.js b/lib/plugins/query.js index 9dd592f02..fb9ded901 100644 --- a/lib/plugins/query.js +++ b/lib/plugins/query.js @@ -6,7 +6,6 @@ var url = require('url'); var assert = require('assert-plus'); - /** * Returns a plugin that will parse the query string, and merge the results * into req.params. @@ -15,32 +14,32 @@ var assert = require('assert-plus'); * @throws {TypeError} on bad input */ function queryParser(options) { - if (!options) - options = {}; - assert.object(options, 'options'); - + if (!options) + options = {}; + assert.object(options, 'options'); - function parseQueryString(req, res, next) { - if (!req.getQuery()) { - req.query = {}; - return (next()); - } - req._query = req.query = qs.parse(req.getQuery()); - if (options.mapParams !== false) { - Object.keys(req.query).forEach(function (k) { - if (req.params[k] && !options.overrideParams) - return (false); + function parseQueryString(req, res, next) { + if (!req.getQuery()) { + req.query = {}; + return (next()); + } - req.params[k] = req.query[k]; - return (true); - }); - } + req._query = req.query = qs.parse(req.getQuery()); + if (options.mapParams !== false) { + Object.keys(req.query).forEach(function (k) { + if (req.params[k] && !options.overrideParams) + return (false); - return (next()); + req.params[k] = req.query[k]; + return (true); + }); } - return (parseQueryString); + return (next()); + } + + return (parseQueryString); } module.exports = queryParser; diff --git a/lib/plugins/static.js b/lib/plugins/static.js index 296a113cb..ac5577a80 100644 --- a/lib/plugins/static.js +++ b/lib/plugins/static.js @@ -16,124 +16,123 @@ var NotAuthorizedError = errors.NotAuthorizedError; var ResourceNotFoundError = errors.ResourceNotFoundError; - ///--- Functions function serveStatic(opts) { - opts = opts || {}; - assert.object(opts, 'options'); - assert.string(opts.directory, 'options.directory'); - assert.optionalNumber(opts.maxAge, 'options.maxAge'); - assert.optionalObject(opts.match, 'options.match'); - assert.optionalString(opts.charSet, 'options.charSet'); - - var p = path.normalize(opts.directory).replace(/\\/g, '/'); - var re = new RegExp('^' + escapeRE(p) + '/?.*'); - - function serveFileFromStats(file, err, stats, isGzip, req, res, next) { - if (err) { - next(new ResourceNotFoundError(err, - req.path())); - return; - } else if (!stats.isFile()) { - next(new ResourceNotFoundError(req.path())); - return; - } - - if (res.handledGzip && isGzip) { - res.handledGzip(); - } + opts = opts || {}; + assert.object(opts, 'options'); + assert.string(opts.directory, 'options.directory'); + assert.optionalNumber(opts.maxAge, 'options.maxAge'); + assert.optionalObject(opts.match, 'options.match'); + assert.optionalString(opts.charSet, 'options.charSet'); + + var p = path.normalize(opts.directory).replace(/\\/g, '/'); + var re = new RegExp('^' + escapeRE(p) + '/?.*'); + + function serveFileFromStats(file, err, stats, isGzip, req, res, next) { + if (err) { + next(new ResourceNotFoundError(err, + req.path())); + return; + } else if (!stats.isFile()) { + next(new ResourceNotFoundError(req.path())); + return; + } - var fstream = fs.createReadStream(file + (isGzip ? '.gz' : '')); - var maxAge = opts.maxAge === undefined ? 3600 : opts.maxAge; - fstream.once('open', function (fd) { - res.cache({maxAge: maxAge}); - res.set('Content-Length', stats.size); - res.set('Content-Type', mime.lookup(file)); - res.set('Last-Modified', stats.mtime); - if (opts.charSet) { - var type = res.getHeader('Content-Type') + - '; charset=' + opts.charSet; - res.setHeader('Content-Type', type); - } - if (opts.etag) { - res.set('ETag', opts.etag(stats, opts)); - } - res.writeHead(200); - fstream.pipe(res); - fstream.once('end', function () { - next(false); - }); - }); + if (res.handledGzip && isGzip) { + res.handledGzip(); } - function serveNormal(file, req, res, next) { - fs.stat(file, function (err, stats) { - if (!err && stats.isDirectory() && opts.default) { - // Serve an index.html page or similar - file = path.join(file, opts.default); - fs.stat(file, function (dirErr, dirStats) { - serveFileFromStats(file, - dirErr, - dirStats, - false, - req, - res, - next); - }); - } else { - serveFileFromStats(file, - err, - stats, - false, - req, - res, - next); - } + var fstream = fs.createReadStream(file + (isGzip ? '.gz' : '')); + var maxAge = opts.maxAge === undefined ? 3600 : opts.maxAge; + fstream.once('open', function (fd) { + res.cache({maxAge: maxAge}); + res.set('Content-Length', stats.size); + res.set('Content-Type', mime.lookup(file)); + res.set('Last-Modified', stats.mtime); + if (opts.charSet) { + var type = res.getHeader('Content-Type') + + '; charset=' + opts.charSet; + res.setHeader('Content-Type', type); + } + if (opts.etag) { + res.set('ETag', opts.etag(stats, opts)); + } + res.writeHead(200); + fstream.pipe(res); + fstream.once('end', function () { + next(false); + }); + }); + } + + function serveNormal(file, req, res, next) { + fs.stat(file, function (err, stats) { + if (!err && stats.isDirectory() && opts.default) { + // Serve an index.html page or similar + file = path.join(file, opts.default); + fs.stat(file, function (dirErr, dirStats) { + serveFileFromStats(file, + dirErr, + dirStats, + false, + req, + res, + next); }); + } else { + serveFileFromStats(file, + err, + stats, + false, + req, + res, + next); + } + }); + } + + function serve(req, res, next) { + var file = path.join(opts.directory, + decodeURIComponent(req.path())); + + if (req.method !== 'GET' && req.method !== 'HEAD') { + next(new MethodNotAllowedError(req.method)); + return; } - function serve(req, res, next) { - var file = path.join(opts.directory, - decodeURIComponent(req.path())); - - if (req.method !== 'GET' && req.method !== 'HEAD') { - next(new MethodNotAllowedError(req.method)); - return; - } - - if (!re.test(file.replace(/\\/g, '/'))) { - next(new NotAuthorizedError(req.path())); - return; - } + if (!re.test(file.replace(/\\/g, '/'))) { + next(new NotAuthorizedError(req.path())); + return; + } - if (opts.match && !opts.match.test(file)) { - next(new NotAuthorizedError(req.path())); - return; - } + if (opts.match && !opts.match.test(file)) { + next(new NotAuthorizedError(req.path())); + return; + } - if (opts.gzip && req.acceptsEncoding('gzip')) { - fs.stat(file + '.gz', function (err, stats) { - if (!err) { - res.setHeader('Content-Encoding', 'gzip'); - serveFileFromStats(file, - err, - stats, - true, - req, - res, - next); - } else { - serveNormal(file, req, res, next); - } - }); + if (opts.gzip && req.acceptsEncoding('gzip')) { + fs.stat(file + '.gz', function (err, stats) { + if (!err) { + res.setHeader('Content-Encoding', 'gzip'); + serveFileFromStats(file, + err, + stats, + true, + req, + res, + next); } else { - serveNormal(file, req, res, next); + serveNormal(file, req, res, next); } - + }); + } else { + serveNormal(file, req, res, next); } - return (serve); + } + + return (serve); } module.exports = serveStatic; diff --git a/lib/plugins/throttle.js b/lib/plugins/throttle.js index a62b7f77c..15c10b88c 100644 --- a/lib/plugins/throttle.js +++ b/lib/plugins/throttle.js @@ -8,7 +8,6 @@ var LRU = require('lru-cache'); var errors = require('../errors'); - ///--- Globals var TooManyRequestsError = errors.TooManyRequestsError; @@ -16,22 +15,20 @@ var TooManyRequestsError = errors.TooManyRequestsError; var MESSAGE = 'You have exceeded your request rate of %s r/s.'; - ///--- Helpers function xor() { - var x = false; - for (var i = 0; i < arguments.length; i++) { - if (arguments[i] && !x) - x = true; - else if (arguments[i] && x) - return (false); - } - return (x); + var x = false; + for (var i = 0; i < arguments.length; i++) { + if (arguments[i] && !x) + x = true; + else if (arguments[i] && x) + return (false); + } + return (x); } - ///--- Internal Class (TokenBucket) /** @@ -55,13 +52,13 @@ function xor() { * - {Number} fillRate the rate to refill tokens. */ function TokenBucket(options) { - assert.object(options, 'options'); - assert.number(options.capacity, 'options.capacity'); - assert.number(options.fillRate, 'options.fillRate'); + assert.object(options, 'options'); + assert.number(options.capacity, 'options.capacity'); + assert.number(options.fillRate, 'options.fillRate'); - this.tokens = this.capacity = options.capacity; - this.fillRate = options.fillRate; - this.time = Date.now(); + this.tokens = this.capacity = options.capacity; + this.fillRate = options.fillRate; + this.time = Date.now(); } @@ -74,12 +71,12 @@ function TokenBucket(options) { * @return {Boolean} true if capacity, false otherwise. */ TokenBucket.prototype.consume = function consume(tokens) { - if (tokens <= this._fill()) { - this.tokens -= tokens; - return (true); - } + if (tokens <= this._fill()) { + this.tokens -= tokens; + return (true); + } - return (false); + return (false); }; @@ -95,43 +92,41 @@ TokenBucket.prototype.consume = function consume(tokens) { * @return {Number} the current number of tokens in the bucket. */ TokenBucket.prototype._fill = function _fill() { - var now = Date.now(); - if (now < this.time) // reset account for clock drift (like DST) - this.time = now - 1000; + var now = Date.now(); + if (now < this.time) // reset account for clock drift (like DST) + this.time = now - 1000; - if (this.tokens < this.capacity) { - var delta = this.fillRate * ((now - this.time) / 1000); - this.tokens = Math.min(this.capacity, this.tokens + delta); - } - this.time = now; + if (this.tokens < this.capacity) { + var delta = this.fillRate * ((now - this.time) / 1000); + this.tokens = Math.min(this.capacity, this.tokens + delta); + } + this.time = now; - return (this.tokens); + return (this.tokens); }; - ///--- Internal Class (TokenTable) // Just a wrapper over LRU that supports put/get to store token -> bucket // mappings function TokenTable(options) { - assert.object(options, 'options'); + assert.object(options, 'options'); - this.table = new LRU(options.size || 10000); + this.table = new LRU(options.size || 10000); } TokenTable.prototype.put = function put(key, value) { - this.table.set(key, value); + this.table.set(key, value); }; TokenTable.prototype.get = function get(key) { - return (this.table.get(key)); + return (this.table.get(key)); }; - ///--- Exported API /** @@ -189,79 +184,79 @@ TokenTable.prototype.get = function get(key) { * @throws {TypeError} on bad input. */ function throttle(options) { - assert.object(options, 'options'); - assert.number(options.burst, 'options.burst'); - assert.number(options.rate, 'options.rate'); - if (!xor(options.ip, options.xff, options.username)) - throw new Error('(ip ^ username ^ xff)'); - - var burst = options.burst; - var rate = options.rate; - var table = options.tokensTable || - new TokenTable({size: options.maxKeys}); - - function rateLimit(req, res, next) { - var attr; - if (options.ip) { - attr = req.connection.remoteAddress; - } else if (options.xff) { - attr = req.headers['x-forwarded-for']; - } else if (options.username) { - attr = req.username; - } else { - req.log.warn({config: options}, - 'Invalid throttle configuration'); - return (next()); - } - - // Before bothering with overrides, see if this request - // even matches - if (!attr) - return (next()); - - // Check the overrides - if (options.overrides && - options.overrides[attr] && - options.overrides[attr].burst !== undefined && - options.overrides[attr].rate !== undefined) { - - burst = options.overrides[attr].burst; - rate = options.overrides[attr].rate; - } - - if (!rate || !burst) - return (next()); - - var bucket = table.get(attr); - if (!bucket) { - bucket = new TokenBucket({ - capacity: burst, - fillRate: rate - }); - table.put(attr, bucket); - } - - req.log.trace('Throttle(%s): num_tokens= %d', - attr, bucket.tokens); - - if (!bucket.consume(1)) { - req.log.info({ - address: req.connection.remoteAddress || '?', - method: req.method, - url: req.url, - user: req.username || '?' - }, 'Throttling'); - - - // Until https://github.com/joyent/node/pull/2371 is in - var msg = sprintf(MESSAGE, rate); - return (next(new TooManyRequestsError(msg))); - } - - return (next()); + assert.object(options, 'options'); + assert.number(options.burst, 'options.burst'); + assert.number(options.rate, 'options.rate'); + if (!xor(options.ip, options.xff, options.username)) + throw new Error('(ip ^ username ^ xff)'); + + var burst = options.burst; + var rate = options.rate; + var table = options.tokensTable || + new TokenTable({size: options.maxKeys}); + + function rateLimit(req, res, next) { + var attr; + if (options.ip) { + attr = req.connection.remoteAddress; + } else if (options.xff) { + attr = req.headers['x-forwarded-for']; + } else if (options.username) { + attr = req.username; + } else { + req.log.warn({config: options}, + 'Invalid throttle configuration'); + return (next()); } - return (rateLimit); + // Before bothering with overrides, see if this request + // even matches + if (!attr) + return (next()); + + // Check the overrides + if (options.overrides && + options.overrides[attr] && + options.overrides[attr].burst !== undefined && + options.overrides[attr].rate !== undefined) { + + burst = options.overrides[attr].burst; + rate = options.overrides[attr].rate; + } + + if (!rate || !burst) + return (next()); + + var bucket = table.get(attr); + if (!bucket) { + bucket = new TokenBucket({ + capacity: burst, + fillRate: rate + }); + table.put(attr, bucket); + } + + req.log.trace('Throttle(%s): num_tokens= %d', + attr, bucket.tokens); + + if (!bucket.consume(1)) { + req.log.info({ + address: req.connection.remoteAddress || '?', + method: req.method, + url: req.url, + user: req.username || '?' + }, 'Throttling'); + + + // Until https://github.com/joyent/node/pull/2371 is in + var msg = sprintf(MESSAGE, rate); + return (next(new TooManyRequestsError(msg))); + } + + return (next()); + } + + return (rateLimit); } module.exports = throttle; diff --git a/lib/request.js b/lib/request.js index 1893ed949..a35fe6045 100644 --- a/lib/request.js +++ b/lib/request.js @@ -12,7 +12,6 @@ var uuid = require('node-uuid'); var utils = require('./utils'); - ///--- Globals var Request = http.IncomingMessage; @@ -21,22 +20,21 @@ var parseAccept = utils.parseAccept; var sanitizePath = utils.sanitizePath; - ///-- Helpers function negotiator(req) { - var h = req.headers; - if (!req._negotatiator) { - req._negotiator = new Negotatior({ - headers: { - accept: h.accept || '*/*', - 'accept-encoding': h['accept-encoding'] || - 'identity' - } - }); - } + var h = req.headers; + if (!req._negotatiator) { + req._negotiator = new Negotatior({ + headers: { + accept: h.accept || '*/*', + 'accept-encoding': h['accept-encoding'] || + 'identity' + } + }); + } - return (req._negotiator); + return (req._negotiator); } @@ -45,275 +43,275 @@ function negotiator(req) { ///--- Patches Request.prototype.absoluteUri = function absoluteUri(path) { - assert.string(path, 'path'); + assert.string(path, 'path'); - var protocol = this.secure ? 'https://' : 'http://'; - var hostname = this.headers['host']; - return (url.resolve(protocol + hostname + this.path + '/', path)); + var protocol = this.secure ? 'https://' : 'http://'; + var hostname = this.headers['host']; + return (url.resolve(protocol + hostname + this.path + '/', path)); }; Request.prototype.accepts = function accepts(types) { - if (typeof (types) === 'string') - types = [types]; - - types = types.map(function (t) { - assert.string(t, 'type'); - if (t.indexOf('/') === -1) - t = mime.lookup(t); - return (t); - }); + if (typeof (types) === 'string') + types = [types]; - negotiator(this); + types = types.map(function (t) { + assert.string(t, 'type'); + if (t.indexOf('/') === -1) + t = mime.lookup(t); + return (t); + }); - return (this._negotiator.preferredMediaType(types)); + negotiator(this); + + return (this._negotiator.preferredMediaType(types)); }; Request.prototype.acceptsEncoding = function acceptsEncoding(types) { - if (typeof (types) === 'string') - types = [types]; + if (typeof (types) === 'string') + types = [types]; - assert.arrayOfString(types, 'types'); + assert.arrayOfString(types, 'types'); - negotiator(this); + negotiator(this); - return (this._negotiator.preferredEncoding(types)); + return (this._negotiator.preferredEncoding(types)); }; Request.prototype.getContentLength = function getContentLength() { - if (this._clen !== undefined) - return (this._clen === false ? undefined : this._clen); + if (this._clen !== undefined) + return (this._clen === false ? undefined : this._clen); - // We should not attempt to read and parse the body of an - // Upgrade request, so force Content-Length to zero: - if (this.isUpgradeRequest()) - return (0); + // We should not attempt to read and parse the body of an + // Upgrade request, so force Content-Length to zero: + if (this.isUpgradeRequest()) + return (0); - var len = this.header('content-length'); - if (!len) { - this._clen = false; - } else { - this._clen = parseInt(len, 10); - } + var len = this.header('content-length'); + if (!len) { + this._clen = false; + } else { + this._clen = parseInt(len, 10); + } - return (this._clen === false ? undefined : this._clen); + return (this._clen === false ? undefined : this._clen); }; Request.prototype.contentLength = Request.prototype.getContentLength; Request.prototype.getContentType = function getContentType() { - if (this._contentType !== undefined) - return (this._contentType); + if (this._contentType !== undefined) + return (this._contentType); - var index; - var type = this.headers['content-type']; + var index; + var type = this.headers['content-type']; - if (!type) { - // RFC2616 section 7.2.1 - this._contentType = 'application/octet-stream'; + if (!type) { + // RFC2616 section 7.2.1 + this._contentType = 'application/octet-stream'; + } else { + if ((index = type.indexOf(';')) === -1) { + this._contentType = type; } else { - if ((index = type.indexOf(';')) === -1) { - this._contentType = type; - } else { - this._contentType = type.substring(0, index); - } + this._contentType = type.substring(0, index); } + } - return (this._contentType); + return (this._contentType); }; Request.prototype.contentType = Request.prototype.getContentType; Request.prototype.date = function date() { - if (this._date !== undefined) - return (this._date); - - this._date = new Date(this._time); + if (this._date !== undefined) return (this._date); + + this._date = new Date(this._time); + return (this._date); }; Request.prototype.getHref = function getHref() { - if (this._href !== undefined) - return (this._href); - - this._href = this.getUrl().href; + if (this._href !== undefined) return (this._href); + + this._href = this.getUrl().href; + return (this._href); }; Request.prototype.href = Request.prototype.getHref; Request.prototype.getId = function getId() { - if (this._id !== undefined) - return (this._id); + if (this._id !== undefined) + return (this._id); - this._id = this.headers['request-id'] || - this.headers['x-request-id'] || - uuid.v1(); + this._id = this.headers['request-id'] || + this.headers['x-request-id'] || + uuid.v1(); - return (this._id); + return (this._id); }; Request.prototype.id = Request.prototype.getId; Request.prototype.getPath = function getPath() { - if (this._path !== undefined) - return (this._path); - - this._path = this.getUrl().pathname; + if (this._path !== undefined) return (this._path); + + this._path = this.getUrl().pathname; + return (this._path); }; Request.prototype.path = Request.prototype.getPath; Request.prototype.getQuery = function getQuery() { - if (this._query !== undefined) - return (this._query); - - this._query = this.getUrl().query || {}; + if (this._query !== undefined) return (this._query); + + this._query = this.getUrl().query || {}; + return (this._query); }; Request.prototype.query = Request.prototype.getQuery; Request.prototype.time = function time() { - return (this._time); + return (this._time); }; Request.prototype.getUrl = function getUrl() { - if (this._url !== undefined) - return (this._url); - - this._url = url.parse(this.url); + if (this._url !== undefined) return (this._url); + + this._url = url.parse(this.url); + return (this._url); }; Request.prototype.getVersion = function getVersion() { - if (this._version !== undefined) - return (this._version); + if (this._version !== undefined) + return (this._version); - this._version = - this.headers['accept-version'] || - this.headers['x-api-version'] || - '*'; + this._version = + this.headers['accept-version'] || + this.headers['x-api-version'] || + '*'; - return (this._version); + return (this._version); }; Request.prototype.version = Request.prototype.getVersion; Request.prototype.header = function header(name, value) { - assert.string(name, 'name'); + assert.string(name, 'name'); - name = name.toLowerCase(); + name = name.toLowerCase(); - if (name === 'referer' || name === 'referrer') - name = 'referer'; + if (name === 'referer' || name === 'referrer') + name = 'referer'; - return (this.headers[name] || value); + return (this.headers[name] || value); }; Request.prototype.trailer = function trailer(name, value) { - assert.string(name, 'name'); - name = name.toLowerCase(); + assert.string(name, 'name'); + name = name.toLowerCase(); - if (name === 'referer' || name === 'referrer') - name = 'referer'; + if (name === 'referer' || name === 'referrer') + name = 'referer'; - return ((this.trailers || {})[name] || value); + return ((this.trailers || {})[name] || value); }; Request.prototype.is = function is(type) { - assert.string(type, 'type'); - - var contentType = this.getContentType(); - var matches = true; - if (!contentType) - return (false); - - if (type.indexOf('/') === -1) - type = mime.lookup(type); - - if (type.indexOf('*') !== -1) { - type = type.split('/'); - contentType = contentType.split('/'); - matches &= (type[0] === '*' || type[0] === contentType[0]); - matches &= (type[1] === '*' || type[1] === contentType[1]); - } else { - matches = (contentType === type); - } - - return (matches); + assert.string(type, 'type'); + + var contentType = this.getContentType(); + var matches = true; + if (!contentType) + return (false); + + if (type.indexOf('/') === -1) + type = mime.lookup(type); + + if (type.indexOf('*') !== -1) { + type = type.split('/'); + contentType = contentType.split('/'); + matches &= (type[0] === '*' || type[0] === contentType[0]); + matches &= (type[1] === '*' || type[1] === contentType[1]); + } else { + matches = (contentType === type); + } + + return (matches); }; Request.prototype.isChunked = function isChunked() { - return (this.headers['transfer-encoding'] === 'chunked'); + return (this.headers['transfer-encoding'] === 'chunked'); }; Request.prototype.isKeepAlive = function isKeepAlive() { - if (this._keepAlive !== undefined) - return (this._keepAlive); + if (this._keepAlive !== undefined) + return (this._keepAlive); - if (this.headers.connection) { - this._keepAlive = /keep-alive/i.test(this.headers.connection); - } else { - this._keepAlive = this.httpVersion === '1.0' ? false : true; - } + if (this.headers.connection) { + this._keepAlive = /keep-alive/i.test(this.headers.connection); + } else { + this._keepAlive = this.httpVersion === '1.0' ? false : true; + } - return (this._keepAlive); + return (this._keepAlive); }; Request.prototype.isSecure = function isSecure() { - if (this._secure !== undefined) - return (this._secure); - - this._secure = this.connection.encrypted ? true : false; + if (this._secure !== undefined) return (this._secure); + + this._secure = this.connection.encrypted ? true : false; + return (this._secure); }; Request.prototype.isUpgradeRequest = function isUpgradeRequest() { - if (this._upgradeRequest !== undefined) - return (this._upgradeRequest); - else - return (false); + if (this._upgradeRequest !== undefined) + return (this._upgradeRequest); + else + return (false); }; Request.prototype.isUpload = function isUpload() { - var m = this.method; - return (m === 'PATH' || m === 'POST' || m === 'PUT'); + var m = this.method; + return (m === 'PATH' || m === 'POST' || m === 'PUT'); }; Request.prototype.toString = function toString() { - var headers = ''; - var self = this; - var str; + var headers = ''; + var self = this; + var str; - Object.keys(this.headers).forEach(function (k) { - headers += sprintf('%s: %s\n', k, self.headers[k]); - }); + Object.keys(this.headers).forEach(function (k) { + headers += sprintf('%s: %s\n', k, self.headers[k]); + }); - str = sprintf('%s %s HTTP/%s\n%s', - this.method, - this.url, - this.httpVersion, - headers); + str = sprintf('%s %s HTTP/%s\n%s', + this.method, + this.url, + this.httpVersion, + headers); - return (str); + return (str); }; Request.prototype.userAgent = function userAgent() { - return (this.headers['user-agent']); + return (this.headers['user-agent']); }; diff --git a/lib/response.js b/lib/response.js index 88b03341d..ed4e10bc6 100644 --- a/lib/response.js +++ b/lib/response.js @@ -12,7 +12,6 @@ var errors = require('./errors'); var httpDate = require('./http_date'); - ///--- Globals var HttpError = errors.HttpError; @@ -21,258 +20,257 @@ var RestError = errors.RestError; var Response = http.ServerResponse; - ///--- API Response.prototype.cache = function cache(type, options) { - if (typeof (type) !== 'string') { - options = type; - type = 'public'; - } + if (typeof (type) !== 'string') { + options = type; + type = 'public'; + } - if (options && options.maxAge !== undefined) { - assert.number(options.maxAge, 'options.maxAge'); - type += ', max-age=' + options.maxAge; - } + if (options && options.maxAge !== undefined) { + assert.number(options.maxAge, 'options.maxAge'); + type += ', max-age=' + options.maxAge; + } - return (this.header('Cache-Control', type)); + return (this.header('Cache-Control', type)); }; Response.prototype.charSet = function charSet(type) { - assert.string(type, 'charset'); + assert.string(type, 'charset'); - this._charSet = type; + this._charSet = type; - return (this); + return (this); }; Response.prototype.format = function format(body, cb) { - var log = this.log; - var formatter; - var type = this.contentType || this.getHeader('Content-Type'); - var self = this; + var log = this.log; + var formatter; + var type = this.contentType || this.getHeader('Content-Type'); + var self = this; + + if (!type) { + for (var i = 0; i < this.acceptable.length; i++) { + if (this.req.accepts(this.acceptable[i])) { + type = this.acceptable[i]; + break; + } + } if (!type) { - for (var i = 0; i < this.acceptable.length; i++) { - if (this.req.accepts(this.acceptable[i])) { - type = this.acceptable[i]; - break; - } - } - - if (!type) { - // The importance of a status code outside of the - // 2xx range probably outweighs that of unable being to - // format the response body - if (this.statusCode >= 200 && this.statusCode < 300) - this.statusCode = 406; - - return (null); - } - } else if (type.indexOf(';') !== '-1') { - type = type.split(';')[0]; + // The importance of a status code outside of the + // 2xx range probably outweighs that of unable being to + // format the response body + if (this.statusCode >= 200 && this.statusCode < 300) + this.statusCode = 406; + + return (null); } + } else if (type.indexOf(';') !== '-1') { + type = type.split(';')[0]; + } - if (!(formatter = this.formatters[type])) { - if (type.indexOf('/') === -1) - type = mime.lookup(type); + if (!(formatter = this.formatters[type])) { + if (type.indexOf('/') === -1) + type = mime.lookup(type); - if (this.acceptable.indexOf(type) === -1) - type = 'application/octet-stream'; + if (this.acceptable.indexOf(type) === -1) + type = 'application/octet-stream'; - formatter = this.formatters[type] || this.formatters['*/*']; + formatter = this.formatters[type] || this.formatters['*/*']; - if (!formatter) { - log.warn({ - req: self.req - }, 'no formatter found. Returning 500.'); - this.statusCode = 500; - return (null); - } + if (!formatter) { + log.warn({ + req: self.req + }, 'no formatter found. Returning 500.'); + this.statusCode = 500; + return (null); } + } - if (this._charSet) { - type = type + '; charset=' + this._charSet; - } + if (this._charSet) { + type = type + '; charset=' + this._charSet; + } - this.setHeader('Content-Type', type); + this.setHeader('Content-Type', type); - if (body instanceof Error && body.statusCode !== undefined) - this.statusCode = body.statusCode; - return (formatter.call(this, this.req, this, body, cb)); + if (body instanceof Error && body.statusCode !== undefined) + this.statusCode = body.statusCode; + return (formatter.call(this, this.req, this, body, cb)); }; Response.prototype.get = function get(name) { - assert.string(name, 'name'); + assert.string(name, 'name'); - return (this.getHeader(name)); + return (this.getHeader(name)); }; Response.prototype.getHeaders = function getHeaders() { - return (this._headers || {}); + return (this._headers || {}); }; Response.prototype.headers = Response.prototype.getHeaders; Response.prototype.header = function header(name, value) { - assert.string(name, 'name'); + assert.string(name, 'name'); - if (value === undefined) - return (this.getHeader(name)); + if (value === undefined) + return (this.getHeader(name)); - if (value instanceof Date) { - value = httpDate(value); - } else if (arguments.length > 2) { - // Support res.header('foo', 'bar %s', 'baz'); - var arg = Array.prototype.slice.call(arguments).slice(2); - value = sprintf(value, arg); - } + if (value instanceof Date) { + value = httpDate(value); + } else if (arguments.length > 2) { + // Support res.header('foo', 'bar %s', 'baz'); + var arg = Array.prototype.slice.call(arguments).slice(2); + value = sprintf(value, arg); + } - this.setHeader(name, value); - return (value); + this.setHeader(name, value); + return (value); }; Response.prototype.json = function json(code, object, headers) { - if (!/application\/json/.test(this.header('content-type'))) - this.header('Content-Type', 'application/json'); + if (!/application\/json/.test(this.header('content-type'))) + this.header('Content-Type', 'application/json'); - return (this.send(code, object, headers)); + return (this.send(code, object, headers)); }; Response.prototype.link = function link(l, rel) { - assert.string(l, 'link'); - assert.string(rel, 'rel'); + assert.string(l, 'link'); + assert.string(rel, 'rel'); - var _link = sprintf('<%s>; rel="%s"', l, rel); - return (this.header('Link', _link)); + var _link = sprintf('<%s>; rel="%s"', l, rel); + return (this.header('Link', _link)); }; Response.prototype.send = function send(code, body, headers) { - var isHead = (this.req.method === 'HEAD'); - var log = this.log; - var self = this; - - if (code === undefined) { - this.statusCode = 200; - } else if (code.constructor.name === 'Number') { - this.statusCode = code; - if (body instanceof Error) { - body.statusCode = this.statusCode; - } - } else { - headers = body; - body = code; - code = null; - } + var isHead = (this.req.method === 'HEAD'); + var log = this.log; + var self = this; - headers = headers || {}; - - if (log.trace()) { - var _props = { - code: self.statusCode, - headers: headers - }; - if (body instanceof Error) { - _props.err = body; - } else { - _props.body = body; - } - log.trace(_props, 'response::send entered'); + if (code === undefined) { + this.statusCode = 200; + } else if (code.constructor.name === 'Number') { + this.statusCode = code; + if (body instanceof Error) { + body.statusCode = this.statusCode; } + } else { + headers = body; + body = code; + code = null; + } + + headers = headers || {}; + + if (log.trace()) { + var _props = { + code: self.statusCode, + headers: headers + }; + if (body instanceof Error) { + _props.err = body; + } else { + _props.body = body; + } + log.trace(_props, 'response::send entered'); + } - this._body = body; + this._body = body; - function _cb(err, _body) { - self._data = _body; - Object.keys(headers).forEach(function (k) { - self.setHeader(k, headers[k]); - }); + function _cb(err, _body) { + self._data = _body; + Object.keys(headers).forEach(function (k) { + self.setHeader(k, headers[k]); + }); - self.writeHead(self.statusCode); + self.writeHead(self.statusCode); - if (self._data && !(isHead || code === 204 || code === 304)) - self.write(self._data); + if (self._data && !(isHead || code === 204 || code === 304)) + self.write(self._data); - self.end(); + self.end(); - if (log.trace()) - log.trace({res: self}, 'response sent'); - } + if (log.trace()) + log.trace({res: self}, 'response sent'); + } - if (body) { - var ret = this.format(body, _cb); - if (!(ret instanceof Response)) { - _cb(null, ret); - } - } else { - _cb(null, null); + if (body) { + var ret = this.format(body, _cb); + if (!(ret instanceof Response)) { + _cb(null, ret); } + } else { + _cb(null, null); + } - return (this); + return (this); }; Response.prototype.set = function set(name, val) { - var self = this; + var self = this; - if (arguments.length === 2) { - assert.string(name, 'name'); - this.header(name, val); - } else { - assert.object(name, 'object'); - Object.keys(name).forEach(function (k) { - self.header(k, name[k]); - }); - } + if (arguments.length === 2) { + assert.string(name, 'name'); + this.header(name, val); + } else { + assert.object(name, 'object'); + Object.keys(name).forEach(function (k) { + self.header(k, name[k]); + }); + } - return (this); + return (this); }; Response.prototype.status = function status(code) { - assert.number(code, 'code'); + assert.number(code, 'code'); - this.statusCode = code; - return (code); + this.statusCode = code; + return (code); }; Response.prototype.toString = function toString() { - var headers = this.getHeaders(); - var headerString = ''; - var str; - - Object.keys(headers).forEach(function (k) { - headerString += k + ': ' + headers[k] + '\n'; - }); - str = sprintf('HTTP/1.1 %s %s\n%s', - this.statusCode, - http.STATUS_CODES[this.statusCode], - headerString); - - return (str); + var headers = this.getHeaders(); + var headerString = ''; + var str; + + Object.keys(headers).forEach(function (k) { + headerString += k + ': ' + headers[k] + '\n'; + }); + str = sprintf('HTTP/1.1 %s %s\n%s', + this.statusCode, + http.STATUS_CODES[this.statusCode], + headerString); + + return (str); }; if (!Response.prototype.hasOwnProperty('_writeHead')) - Response.prototype._writeHead = Response.prototype.writeHead; + Response.prototype._writeHead = Response.prototype.writeHead; Response.prototype.writeHead = function restifyWriteHead() { - this.emit('header'); + this.emit('header'); - if (this.statusCode === 204 || this.statusCode === 304) { - this.removeHeader('Content-Length'); - this.removeHeader('Content-MD5'); - this.removeHeader('Content-Type'); - this.removeHeader('Content-Encoding'); - } + if (this.statusCode === 204 || this.statusCode === 304) { + this.removeHeader('Content-Length'); + this.removeHeader('Content-MD5'); + this.removeHeader('Content-Type'); + this.removeHeader('Content-Encoding'); + } - this._writeHead.apply(this, arguments); + this._writeHead.apply(this, arguments); }; diff --git a/lib/router.js b/lib/router.js index bcb6c120c..679c8010d 100644 --- a/lib/router.js +++ b/lib/router.js @@ -15,7 +15,6 @@ var errors = require('./errors'); var utils = require('./utils'); - ///--- Globals var DEF_CT = 'application/octet-stream'; @@ -33,135 +32,133 @@ var UnsupportedMediaTypeError = errors.UnsupportedMediaTypeError; var shallowCopy = utils.shallowCopy; - ///--- Helpers function createCachedRoute(o, path, version, route) { - if (!o.hasOwnProperty(path)) - o[path] = {}; + if (!o.hasOwnProperty(path)) + o[path] = {}; - if (!o[path].hasOwnProperty(version)) - o[path][version] = route; + if (!o[path].hasOwnProperty(version)) + o[path][version] = route; } function matchURL(re, req) { - var i = 0; - var result = re.exec(req.path()); - var params = {}; + var i = 0; + var result = re.exec(req.path()); + var params = {}; - if (!result) - return (false); + if (!result) + return (false); - // This means the user original specified a regexp match, not a url - // string like /:foo/:bar - if (!re.restifyParams) { - for (i = 1; i < result.length; i++) - params[(i - 1)] = result[i]; + // This means the user original specified a regexp match, not a url + // string like /:foo/:bar + if (!re.restifyParams) { + for (i = 1; i < result.length; i++) + params[(i - 1)] = result[i]; - return (params); - } + return (params); + } - // This was a static string, like /foo - if (re.restifyParams.length === 0) - return (params); + // This was a static string, like /foo + if (re.restifyParams.length === 0) + return (params); - // This was the "normal" case, of /foo/:id - re.restifyParams.forEach(function (p) { - if (++i < result.length) - params[p] = decodeURIComponent(result[i]); - }); + // This was the "normal" case, of /foo/:id + re.restifyParams.forEach(function (p) { + if (++i < result.length) + params[p] = decodeURIComponent(result[i]); + }); - return (params); + return (params); } function compileURL(options) { - if (options.url instanceof RegExp) - return (options.url); - assert.string(options.url, 'url'); - - var params = []; - var pattern = '^'; - var re; - var _url = url.parse(options.url).pathname; - _url.split('/').forEach(function (frag) { - if (frag.length <= 0) - return (false); - - pattern += '\\/+'; - if (frag.charAt(0) === ':') { - if (options.urlParamPattern) { - pattern += '(' + options.urlParamPattern + ')'; - } else { - // Strictly adhere to RFC3986 - pattern += '([a-zA-Z0-9-_~\\.%@]+)'; - } - params.push(frag.slice(1)); - } else { - pattern += frag; - } + if (options.url instanceof RegExp) + return (options.url); + assert.string(options.url, 'url'); + + var params = []; + var pattern = '^'; + var re; + var _url = url.parse(options.url).pathname; + _url.split('/').forEach(function (frag) { + if (frag.length <= 0) + return (false); + + pattern += '\\/+'; + if (frag.charAt(0) === ':') { + if (options.urlParamPattern) { + pattern += '(' + options.urlParamPattern + ')'; + } else { + // Strictly adhere to RFC3986 + pattern += '([a-zA-Z0-9-_~\\.%@]+)'; + } + params.push(frag.slice(1)); + } else { + pattern += frag; + } - return (true); - }); + return (true); + }); - if (pattern === '^') - pattern += '\\/'; - pattern += '$'; + if (pattern === '^') + pattern += '\\/'; + pattern += '$'; - re = new RegExp(pattern, options.flags); - re.restifyParams = params; + re = new RegExp(pattern, options.flags); + re.restifyParams = params; - return (re); + return (re); } - ///--- API function Router(options) { - assert.object(options, 'options'); - assert.object(options.log, 'options.log'); - - EventEmitter.call(this); - - this.cache = LRU({max: 100}); - this.contentType = options.contentType || []; - if (!Array.isArray(this.contentType)) - this.contentType = [this.contentType]; - assert.arrayOfString(this.contentType, 'options.contentType'); - - this.log = options.log; - this.mounts = {}; - this.name = 'RestifyRouter'; - - // A list of methods to routes - this.routes = { - DELETE: [], - GET: [], - HEAD: [], - OPTIONS: [], - PATCH: [], - POST: [], - PUT: [] - }; - - // So we can retrun 405 vs 404, we maintain a reverse mapping of URLs - // to method - this.reverse = {}; - - this.versions = options.versions || options.version || []; - if (!Array.isArray(this.versions)) - this.versions = [this.versions]; - assert.arrayOfString(this.versions, 'options.versions'); - - this.versions.forEach(function (v) { - if (semver.valid(v)) - return (true); - - throw new InvalidArgumentError('%s is not a valid semver', v); - }); - this.versions.sort(); + assert.object(options, 'options'); + assert.object(options.log, 'options.log'); + + EventEmitter.call(this); + + this.cache = LRU({max: 100}); + this.contentType = options.contentType || []; + if (!Array.isArray(this.contentType)) + this.contentType = [this.contentType]; + assert.arrayOfString(this.contentType, 'options.contentType'); + + this.log = options.log; + this.mounts = {}; + this.name = 'RestifyRouter'; + + // A list of methods to routes + this.routes = { + DELETE: [], + GET: [], + HEAD: [], + OPTIONS: [], + PATCH: [], + POST: [], + PUT: [] + }; + + // So we can retrun 405 vs 404, we maintain a reverse mapping of URLs + // to method + this.reverse = {}; + + this.versions = options.versions || options.version || []; + if (!Array.isArray(this.versions)) + this.versions = [this.versions]; + assert.arrayOfString(this.versions, 'options.versions'); + + this.versions.forEach(function (v) { + if (semver.valid(v)) + return (true); + + throw new InvalidArgumentError('%s is not a valid semver', v); + }); + this.versions.sort(); } util.inherits(Router, EventEmitter); @@ -169,322 +166,321 @@ module.exports = Router; Router.prototype.mount = function mount(options) { - assert.object(options, 'options'); - assert.string(options.method, 'options.method'); - assert.string(options.name, 'options.name'); - - var exists; - var name = options.name; - var route; - var routes = this.routes[options.method]; - var self = this; - var type = options.contentType || self.contentType; - var versions = options.versions || options.version || self.versions; - - if (type) { - if (!Array.isArray(type)) - type = [type]; - type.filter(function (t) { - return (t); - }).sort().join(); - } - - if (versions) { - if (!Array.isArray(versions)) - versions = [versions]; - versions.sort(); - } - - exists = routes.some(function (r) { - return (r.name === name); - }); - if (exists) - return (false); - - route = { - name: name, - method: options.method, - path: compileURL({ - url: options.path || options.url, - flags: options.flags, - urlParamPattern: options.urlParamPattern - }), - spec: options, - types: type, - versions: versions - }; - routes.push(route); - - if (!this.reverse[route.path.source]) - this.reverse[route.path.source] = []; - - if (this.reverse[route.path.source].indexOf(route.method) === -1) - this.reverse[route.path.source].push(route.method); - - this.mounts[route.name] = route; - - this.emit('mount', - route.method, - route.path, - route.types, - route.versions); - - return (route.name); + assert.object(options, 'options'); + assert.string(options.method, 'options.method'); + assert.string(options.name, 'options.name'); + + var exists; + var name = options.name; + var route; + var routes = this.routes[options.method]; + var self = this; + var type = options.contentType || self.contentType; + var versions = options.versions || options.version || self.versions; + + if (type) { + if (!Array.isArray(type)) + type = [type]; + type.filter(function (t) { + return (t); + }).sort().join(); + } + + if (versions) { + if (!Array.isArray(versions)) + versions = [versions]; + versions.sort(); + } + + exists = routes.some(function (r) { + return (r.name === name); + }); + if (exists) + return (false); + + route = { + name: name, + method: options.method, + path: compileURL({ + url: options.path || options.url, + flags: options.flags, + urlParamPattern: options.urlParamPattern + }), + spec: options, + types: type, + versions: versions + }; + routes.push(route); + + if (!this.reverse[route.path.source]) + this.reverse[route.path.source] = []; + + if (this.reverse[route.path.source].indexOf(route.method) === -1) + this.reverse[route.path.source].push(route.method); + + this.mounts[route.name] = route; + + this.emit('mount', + route.method, + route.path, + route.types, + route.versions); + + return (route.name); }; Router.prototype.unmount = function unmount(name) { - var route = this.mounts[name]; - if (!route) { - this.log.warn('router.unmount(%s): route does not exist', name); - return (false); - } + var route = this.mounts[name]; + if (!route) { + this.log.warn('router.unmount(%s): route does not exist', name); + return (false); + } - var reverse = this.reverse[route.path.source]; - var routes = this.routes[route.method]; - this.routes[route.method] = routes.filter(function (r) { - return (r.name !== route.name); - }); + var reverse = this.reverse[route.path.source]; + var routes = this.routes[route.method]; + this.routes[route.method] = routes.filter(function (r) { + return (r.name !== route.name); + }); - this.reverse[route.path.source] = reverse.filter(function (r) { - return (r !== route.method); - }); + this.reverse[route.path.source] = reverse.filter(function (r) { + return (r !== route.method); + }); - if (this.reverse[route.path.source].length === 0) - delete this.reverse[route.path.source]; + if (this.reverse[route.path.source].length === 0) + delete this.reverse[route.path.source]; - delete this.mounts[name]; + delete this.mounts[name]; - return (name); + return (name); }; Router.prototype.get = function get(name, req, cb) { - var params; - var route = false; - var routes = this.routes[req.method] || []; - - for (var i = 0; i < routes.length; i++) { - if (routes[i].name === name) { - route = routes[i]; - try { - params = matchURL(route.path, req); - } catch (e) {} - break; - } + var params; + var route = false; + var routes = this.routes[req.method] || []; + + for (var i = 0; i < routes.length; i++) { + if (routes[i].name === name) { + route = routes[i]; + try { + params = matchURL(route.path, req); + } catch (e) { + } + break; } + } - if (route) { - cb(null, route, params || {}); - } else { - cb(new InternalError()); - } + if (route) { + cb(null, route, params || {}); + } else { + cb(new InternalError()); + } }; Router.prototype.find = function find(req, res, callback) { - var candidates = []; - var ct = req.headers['content-type'] || DEF_CT; - var cacheKey = req.method + req.url + req.version() + ct; - var cacheVal; - var neg; - var params; - var r; - var reverse; - var routes = this.routes[req.method] || []; - var typed; - var versioned; - - if ((cacheVal = this.cache.get(cacheKey))) { - res.methods = cacheVal.methods.slice(); - callback(null, cacheVal, shallowCopy(cacheVal.params)); - return; + var candidates = []; + var ct = req.headers['content-type'] || DEF_CT; + var cacheKey = req.method + req.url + req.version() + ct; + var cacheVal; + var neg; + var params; + var r; + var reverse; + var routes = this.routes[req.method] || []; + var typed; + var versioned; + + if ((cacheVal = this.cache.get(cacheKey))) { + res.methods = cacheVal.methods.slice(); + callback(null, cacheVal, shallowCopy(cacheVal.params)); + return; + } + + for (var i = 0; i < routes.length; i++) { + try { + params = matchURL(routes[i].path, req); + } catch (e) { + this.log.trace({err: e}, 'error parsing URL'); + callback(new BadRequestError(e.message)); + return; } - for (var i = 0; i < routes.length; i++) { - try { - params = matchURL(routes[i].path, req); - } catch (e) { - this.log.trace({err: e}, 'error parsing URL'); - callback(new BadRequestError(e.message)); - return; - } - - if (params === false) - continue; - - reverse = this.reverse[routes[i].path.source]; - - if (routes[i].types.length && req.isUpload()) { - candidates.push({ - p: params, - r: routes[i] - }); - typed = true; - continue; - } + if (params === false) + continue; - // GH-283: we want to find the latest version for a given route, - // not the first one. However, if neither the client nor - // server specified any version, we're done, because neither - // cared - if (routes[i].versions.length === 0 && req.version() === '*') { - r = routes[i]; - break; - } + reverse = this.reverse[routes[i].path.source]; - if (routes[i].versions.length > 0) { - candidates.push({ - p: params, - r: routes[i] - }); - versioned = true; - } + if (routes[i].types.length && req.isUpload()) { + candidates.push({ + p: params, + r: routes[i] + }); + typed = true; + continue; } - if (!r) { - // If upload and typed - if (typed) { - /* JSSTYLED */ - var _t = ct.split(/\s*,\s*/); - candidates = candidates.filter(function (c) { - neg = new Negotiator({ - headers: { - accept: c.r.types.join(', ') - } - }); - var tmp = neg.preferredMediaType(_t); - return (tmp && tmp.length); - }); - - // Pick the first one in case not versioned - if (candidates.length) { - r = candidates[0].r; - params = candidates[0].p; - } - } - - if (versioned) { - candidates.forEach(function (c) { - var k = c.r.versions; - var v = semver.maxSatisfying(k, req.version()); - - if (v) { - if (!r || - r.versions.some(function (v2) { - return (semver.gt(v, v2)); - })) { - r = c.r; - params = c.p; - } - } - }); - } + // GH-283: we want to find the latest version for a given route, + // not the first one. However, if neither the client nor + // server specified any version, we're done, because neither + // cared + if (routes[i].versions.length === 0 && req.version() === '*') { + r = routes[i]; + break; } - // In order, we check if the route exists, in which case, we're good. - // Otherwise we look to see if ver was set to false; that would tell us - // we indeed did find a matching route (method+url), but the version - // field didn't line up, so we return bad version. If no route and no - // version, we now need to go walk the reverse map and look at whether - // we should return 405 or 404. If it was an OPTIONS request, we need - // to handle this having been a preflight request. - if (params && r) { - cacheVal = { - methods: reverse, - name: r.name, - params: params, - spec: r.spec - }; - this.cache.set(cacheKey, cacheVal); - res.methods = reverse.slice(); - callback(null, cacheVal, shallowCopy(params)); - return; + if (routes[i].versions.length > 0) { + candidates.push({ + p: params, + r: routes[i] + }); + versioned = true; } + } + if (!r) { + // If upload and typed if (typed) { - callback(new UnsupportedMediaTypeError(ct)); - return; - } - if (versioned) { - callback(new InvalidVersionError('%s is not supported by %s %s', - req.version() || '?', - req.method, - req.path())); - return; + /* JSSTYLED */ + var _t = ct.split(/\s*,\s*/); + candidates = candidates.filter(function (c) { + neg = new Negotiator({ + headers: { + accept: c.r.types.join(', ') + } + }); + var tmp = neg.preferredMediaType(_t); + return (tmp && tmp.length); + }); + + // Pick the first one in case not versioned + if (candidates.length) { + r = candidates[0].r; + params = candidates[0].p; + } } - // This is a very generic preflight handler - it does - // not handle requiring authentication, nor does it do - // any special checking for extra user headers. The - // user will need to defined their own .opts handler to - // do that - function preflight(methods) { - var headers = req.headers['access-control-request-headers']; - var method = req.headers['access-control-request-method']; - var origin = req.headers['origin']; - - if (req.method !== 'OPTIONS' || - !origin || - !method || - methods.indexOf(method) === -1) { - return (false); + if (versioned) { + candidates.forEach(function (c) { + var k = c.r.versions; + var v = semver.maxSatisfying(k, req.version()); + + if (v) { + if (!r || + r.versions.some(function (v2) { + return (semver.gt(v, v2)); + })) { + r = c.r; + params = c.p; + } } - // Last, check request-headers - var ok = true; - /* JSSTYLED */ - (headers || '').split(/\s*,\s*/).forEach(function (h) { - if (!h) - return; - - h = h.toLowerCase(); - ok = cors.ALLOW_HEADERS.indexOf(h) !== -1 && ok; - }); - if (!ok) - return (false); - - res.setHeader('Access-Control-Allow-Origin', '*'); - res.setHeader('Access-Control-Allow-Methods', - methods.join(', ')); - res.setHeader('Access-Control-Allow-Headers', - cors.ALLOW_HEADERS.join(', ')); - res.setHeader('Access-Control-Max-Age', 3600); - - return (true); + }); + } + } + + // In order, we check if the route exists, in which case, we're good. + // Otherwise we look to see if ver was set to false; that would tell us + // we indeed did find a matching route (method+url), but the version + // field didn't line up, so we return bad version. If no route and no + // version, we now need to go walk the reverse map and look at whether + // we should return 405 or 404. If it was an OPTIONS request, we need + // to handle this having been a preflight request. + if (params && r) { + cacheVal = { + methods: reverse, + name: r.name, + params: params, + spec: r.spec + }; + this.cache.set(cacheKey, cacheVal); + res.methods = reverse.slice(); + callback(null, cacheVal, shallowCopy(params)); + return; + } + + if (typed) { + callback(new UnsupportedMediaTypeError(ct)); + return; + } + if (versioned) { + callback(new InvalidVersionError('%s is not supported by %s %s', + req.version() || '?', + req.method, + req.path())); + return; + } + + // This is a very generic preflight handler - it does + // not handle requiring authentication, nor does it do + // any special checking for extra user headers. The + // user will need to defined their own .opts handler to + // do that + function preflight(methods) { + var headers = req.headers['access-control-request-headers']; + var method = req.headers['access-control-request-method']; + var origin = req.headers['origin']; + + if (req.method !== 'OPTIONS' || !origin || !method || + methods.indexOf(method) === -1) { + return (false); } + // Last, check request-headers + var ok = true; + /* JSSTYLED */ + (headers || '').split(/\s*,\s*/).forEach(function (h) { + if (!h) + return; - // Check for 405 instead of 404 - var urls = Object.keys(this.reverse); - for (i = 0; i < urls.length; i++) { - if (matchURL(new RegExp(urls[i]), req)) { - res.methods = this.reverse[urls[i]].slice(); - res.setHeader('Allow', res.methods.join(', ')); - if (preflight(res.methods)) { - callback(null, { name: 'preflight' }); - return; - } - var err = new MethodNotAllowedError('%s is not allowed', - req.method); - callback(err); - return; - } + h = h.toLowerCase(); + ok = cors.ALLOW_HEADERS.indexOf(h) !== -1 && ok; + }); + if (!ok) + return (false); + + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Access-Control-Allow-Methods', + methods.join(', ')); + res.setHeader('Access-Control-Allow-Headers', + cors.ALLOW_HEADERS.join(', ')); + res.setHeader('Access-Control-Max-Age', 3600); + + return (true); + } + + // Check for 405 instead of 404 + var urls = Object.keys(this.reverse); + for (i = 0; i < urls.length; i++) { + if (matchURL(new RegExp(urls[i]), req)) { + res.methods = this.reverse[urls[i]].slice(); + res.setHeader('Allow', res.methods.join(', ')); + if (preflight(res.methods)) { + callback(null, { name: 'preflight' }); + return; + } + var err = new MethodNotAllowedError('%s is not allowed', + req.method); + callback(err); + return; } + } - callback(new ResourceNotFoundError('%s does not exist', req.url)); + callback(new ResourceNotFoundError('%s does not exist', req.url)); }; Router.prototype.toString = function toString() { - var self = this; - var str = this.name + ':\n'; - - Object.keys(this.routes).forEach(function (k) { - var routes = self.routes[k].map(function (r) { - return (r.name); - }); + var self = this; + var str = this.name + ':\n'; - str += '\t\t' + k + ': [' + routes.join(', ') + ']\n'; + Object.keys(this.routes).forEach(function (k) { + var routes = self.routes[k].map(function (r) { + return (r.name); }); - return (str); + str += '\t\t' + k + ': [' + routes.join(', ') + ']\n'; + }); + + return (str); }; diff --git a/lib/server.js b/lib/server.js index ced5c544f..babe794d8 100644 --- a/lib/server.js +++ b/lib/server.js @@ -27,7 +27,6 @@ require('./request'); require('./response'); - ///--- Globals var sprintf = util.format; @@ -37,266 +36,264 @@ var InvalidVersionError = errors.InvalidVersionError; var ResourceNotFoundError = errors.ResourceNotFoundError; var PROXY_EVENTS = [ - 'clientError', - 'close', - 'connection', - 'error', - 'listening', - 'secureConnection' + 'clientError', + 'close', + 'connection', + 'error', + 'listening', + 'secureConnection' ]; - ///--- Helpers function argumentsToChain(args, start) { - assert.ok(args); + assert.ok(args); - args = Array.prototype.slice.call(args, start); + args = Array.prototype.slice.call(args, start); - if (args.length < 0) - throw new TypeError('handler (function) required'); + if (args.length < 0) + throw new TypeError('handler (function) required'); - var chain = []; + var chain = []; - function process(handlers) { - for (var i = 0; i < handlers.length; i++) { - if (Array.isArray(handlers[i])) { - process(handlers[i], 0); - } else { - assert.func(handlers[i], 'handler'); - chain.push(handlers[i]); - } - } - - return (chain); + function process(handlers) { + for (var i = 0; i < handlers.length; i++) { + if (Array.isArray(handlers[i])) { + process(handlers[i], 0); + } else { + assert.func(handlers[i], 'handler'); + chain.push(handlers[i]); + } } - return (process(args)); + return (chain); + } + + return (process(args)); } function mergeFormatters(fmt) { - var arr = []; - var defaults = Object.keys(formatters).length; - var i = 0; - var obj = {}; - - function addFormatter(src, k) { - assert.func(src[k], 'formatter'); - - var q; - var t = k; - if (k.indexOf(';') !== -1) { - /* JSSTYLED */ - var tmp = k.split(/\s*;\s*/); - t = tmp[0]; - if (tmp[1].indexOf('q=') !== -1) { - q = parseFloat(tmp[1].split('=')[1], 10) * 10; - } - } - - if (k.indexOf('/') === -1) - k = mime.lookup(k); - - obj[t] = src[k]; - arr.push({ - q: q || (i + defaults), - t: t - }); - i++; + var arr = []; + var defaults = Object.keys(formatters).length; + var i = 0; + var obj = {}; + + function addFormatter(src, k) { + assert.func(src[k], 'formatter'); + + var q; + var t = k; + if (k.indexOf(';') !== -1) { + /* JSSTYLED */ + var tmp = k.split(/\s*;\s*/); + t = tmp[0]; + if (tmp[1].indexOf('q=') !== -1) { + q = parseFloat(tmp[1].split('=')[1], 10) * 10; + } } - Object.keys(formatters).forEach(addFormatter.bind(this, formatters)); - Object.keys(fmt || {}).forEach(addFormatter.bind(this, fmt || {})); + if (k.indexOf('/') === -1) + k = mime.lookup(k); - arr = arr.sort(function (a, b) { - return (b.q - a.q); - }).map(function (a) { - return (a.t); + obj[t] = src[k]; + arr.push({ + q: q || (i + defaults), + t: t }); + i++; + } - return ({ - formatters: obj, - acceptable: arr + Object.keys(formatters).forEach(addFormatter.bind(this, formatters)); + Object.keys(fmt || {}).forEach(addFormatter.bind(this, fmt || {})); + + arr = arr.sort(function (a, b) { + return (b.q - a.q); + }).map(function (a) { + return (a.t); }); + + return ({ + formatters: obj, + acceptable: arr + }); } function ifError(n) { - function _ifError(err) { - if (err) { - err._restify_next = n; - throw err; - } + function _ifError(err) { + if (err) { + err._restify_next = n; + throw err; } - return (_ifError); + } + + return (_ifError); } function emitRouteError(server, req, res, err) { - var name; - if (err.name === 'ResourceNotFoundError') { - name = 'NotFound'; - } else if (err.name === 'InvalidVersionError') { - name = 'VersionNotAllowed'; - } else { - name = err.name.replace(/Error$/, ''); - } - if (server.listeners(name).length > 0) { - server.emit(name, req, res, once(function () { - server.emit('after', req, res, null); - })); - } else { - res.send(err); - server.emit('after', req, res, null); - } + var name; + if (err.name === 'ResourceNotFoundError') { + name = 'NotFound'; + } else if (err.name === 'InvalidVersionError') { + name = 'VersionNotAllowed'; + } else { + name = err.name.replace(/Error$/, ''); + } + if (server.listeners(name).length > 0) { + server.emit(name, req, res, once(function () { + server.emit('after', req, res, null); + })); + } else { + res.send(err); + server.emit('after', req, res, null); + } } function optionsError(err, req, res) { - var code = err.statusCode; - var ok = false; + var code = err.statusCode; + var ok = false; - if (code === 404 && req.method === 'OPTIONS' && req.url === '*') { - res.send(200); - ok = true; - } + if (code === 404 && req.method === 'OPTIONS' && req.url === '*') { + res.send(200); + ok = true; + } - return (ok); + return (ok); } - ///--- API function Server(options) { - assert.object(options, 'options'); - assert.object(options.log, 'options.log'); - assert.object(options.router, 'options.router'); - - var self = this; - - EventEmitter.call(this); - - this.before = []; - this.chain = []; - this.log = options.log; - this.name = options.name || 'restify'; - this.router = options.router; - this.routes = {}; - this.secure = false; - this.versions = options.versions || options.version || []; - - var fmt = mergeFormatters(options.formatters); - this.acceptable = fmt.acceptable; - this.formatters = fmt.formatters; - - if (options.spdy) { - this.spdy = true; - this.server = spdy.createServer(options.spdy); - } else if ((options.cert || options.certificate) && options.key) { - this.ca = options.ca; - this.certificate = options.certificate || options.cert; - this.key = options.key; - this.passphrase = options.passphrase || null; - this.secure = true; - - this.server = https.createServer({ - ca: self.ca, - cert: self.certificate, - key: self.key, - passphrase: self.passphrase, - rejectUnauthorized: options.rejectUnauthorized, - requestCert: options.requestCert, - ciphers: options.ciphers - }); - } else { - this.server = http.createServer(); - } - - this.router.on('mount', this.emit.bind(this, 'mount')); - - if (!options.handleUpgrades && PROXY_EVENTS.indexOf('upgrade') === -1) - PROXY_EVENTS.push('upgrade'); - PROXY_EVENTS.forEach(function (e) { - self.server.on(e, self.emit.bind(self, e)); + assert.object(options, 'options'); + assert.object(options.log, 'options.log'); + assert.object(options.router, 'options.router'); + + var self = this; + + EventEmitter.call(this); + + this.before = []; + this.chain = []; + this.log = options.log; + this.name = options.name || 'restify'; + this.router = options.router; + this.routes = {}; + this.secure = false; + this.versions = options.versions || options.version || []; + + var fmt = mergeFormatters(options.formatters); + this.acceptable = fmt.acceptable; + this.formatters = fmt.formatters; + + if (options.spdy) { + this.spdy = true; + this.server = spdy.createServer(options.spdy); + } else if ((options.cert || options.certificate) && options.key) { + this.ca = options.ca; + this.certificate = options.certificate || options.cert; + this.key = options.key; + this.passphrase = options.passphrase || null; + this.secure = true; + + this.server = https.createServer({ + ca: self.ca, + cert: self.certificate, + key: self.key, + passphrase: self.passphrase, + rejectUnauthorized: options.rejectUnauthorized, + requestCert: options.requestCert, + ciphers: options.ciphers }); + } else { + this.server = http.createServer(); + } + + this.router.on('mount', this.emit.bind(this, 'mount')); + + if (!options.handleUpgrades && PROXY_EVENTS.indexOf('upgrade') === -1) + PROXY_EVENTS.push('upgrade'); + PROXY_EVENTS.forEach(function (e) { + self.server.on(e, self.emit.bind(self, e)); + }); + + // Now the things we can't blindly proxy + this.server.on('checkContinue', function onCheckContinue(req, res) { + if (self.listeners('checkContinue').length > 0) { + self.emit('checkContinue', req, res); + return; + } - // Now the things we can't blindly proxy - this.server.on('checkContinue', function onCheckContinue(req, res) { - if (self.listeners('checkContinue').length > 0) { - self.emit('checkContinue', req, res); - return; - } + if (!options.noWriteContinue) + res.writeContinue(); - if (!options.noWriteContinue) - res.writeContinue(); + self._setupRequest(req, res); + self._handle(req, res, true); + }); - self._setupRequest(req, res); - self._handle(req, res, true); + if (options.handleUpgrades) { + this.server.on('upgrade', function onUpgrade(req, socket, head) { + req._upgradeRequest = true; + var res = upgrade.createResponse(req, socket, head); + self._setupRequest(req, res); + self._handle(req, res); }); + } - if (options.handleUpgrades) { - this.server.on('upgrade', function onUpgrade(req, socket, - head) { - req._upgradeRequest = true; - var res = upgrade.createResponse(req, socket, head); - self._setupRequest(req, res); - self._handle(req, res); - }); - } + this.server.on('request', function onRequest(req, res) { + self.emit('request', req, res); + /* JSSTYLED */ + if (/^\/socket.io.*/.test(req.url)) + return; - this.server.on('request', function onRequest(req, res) { - self.emit('request', req, res); - /* JSSTYLED */ - if (/^\/socket.io.*/.test(req.url)) - return; + self._setupRequest(req, res); + self._handle(req, res); + }); - self._setupRequest(req, res); - self._handle(req, res); - }); + this.__defineGetter__('maxHeadersCount', function () { + return (self.server.maxHeadersCount); + }); - this.__defineGetter__('maxHeadersCount', function () { - return (self.server.maxHeadersCount); - }); + this.__defineSetter__('maxHeadersCount', function (c) { + self.server.maxHeadersCount = c; + return (c); + }); - this.__defineSetter__('maxHeadersCount', function (c) { - self.server.maxHeadersCount = c; - return (c); - }); + this.__defineGetter__('url', function () { + if (self.socketPath) + return ('http://' + self.socketPath); - this.__defineGetter__('url', function () { - if (self.socketPath) - return ('http://' + self.socketPath); - - var addr = self.address(); - var str = ''; - if (self.spdy) { - str += 'spdy://'; - } else if (self.secure) { - str += 'https://'; - } else { - str += 'http://'; - } + var addr = self.address(); + var str = ''; + if (self.spdy) { + str += 'spdy://'; + } else if (self.secure) { + str += 'https://'; + } else { + str += 'http://'; + } - if (addr) { - str += addr.address; - str += ':'; - str += addr.port; - } else { - str += '169.254.0.1:0000'; - } + if (addr) { + str += addr.address; + str += ':'; + str += addr.port; + } else { + str += '169.254.0.1:0000'; + } - return (str); - }); + return (str); + }); } util.inherits(Server, EventEmitter); module.exports = Server; Server.prototype.address = function address() { - return (this.server.address()); + return (this.server.address()); }; /** @@ -311,8 +308,8 @@ Server.prototype.address = function address() { * @throws {TypeError} on bad input. */ Server.prototype.listen = function listen() { - var args = Array.prototype.slice.call(arguments); - return (this.server.listen.apply(this.server, args)); + var args = Array.prototype.slice.call(arguments); + return (this.server.listen.apply(this.server, args)); }; @@ -322,14 +319,14 @@ Server.prototype.listen = function listen() { * @param {Function} callback optional callback to invoke when done. */ Server.prototype.close = function close(callback) { - if (callback) - assert.func(callback, 'callback'); + if (callback) + assert.func(callback, 'callback'); - this.server.once('close', function onClose() { - return (callback ? callback() : false); - }); + this.server.once('close', function onClose() { + return (callback ? callback() : false); + }); - return (this.server.close()); + return (this.server.close()); }; @@ -341,71 +338,71 @@ Server.prototype.close = function close(callback) { * @return {Route} the newly created route. */ [ - 'del', - 'get', - 'head', - 'opts', - 'post', - 'put', - 'patch' + 'del', + 'get', + 'head', + 'opts', + 'post', + 'put', + 'patch' ].forEach(function (method) { Server.prototype[method] = function (opts) { - if (opts instanceof RegExp || typeof (opts) === 'string') { - opts = { - path: opts - }; - } else if (typeof (opts) === 'object') { - opts = shallowCopy(opts); - } else { - throw new TypeError('path (string) required'); - } - - if (arguments.length < 2) - throw new TypeError('handler (function) required'); - - var chain = []; - var route; - var self = this; - - function addHandler(h) { - assert.func(h, 'handler'); + if (opts instanceof RegExp || typeof (opts) === 'string') { + opts = { + path: opts + }; + } else if (typeof (opts) === 'object') { + opts = shallowCopy(opts); + } else { + throw new TypeError('path (string) required'); + } + + if (arguments.length < 2) + throw new TypeError('handler (function) required'); - chain.push(h); + var chain = []; + var route; + var self = this; + + function addHandler(h) { + assert.func(h, 'handler'); + + chain.push(h); + } + + if (method === 'del') + method = 'DELETE'; + if (method === 'opts') + method = 'OPTIONS'; + opts.method = method.toUpperCase(); + opts.versions = opts.versions || opts.version || self.versions; + if (!Array.isArray(opts.versions)) + opts.versions = [opts.versions]; + + if (!opts.name) { + opts.name = method + '-' + (opts.path || opts.url); + if (opts.versions.length > 0) { + opts.name += '-' + opts.versions.join('--'); } - if (method === 'del') - method = 'DELETE'; - if (method === 'opts') - method = 'OPTIONS'; - opts.method = method.toUpperCase(); - opts.versions = opts.versions || opts.version || self.versions; - if (!Array.isArray(opts.versions)) - opts.versions = [opts.versions]; - - if (!opts.name) { - opts.name = method + '-' + (opts.path || opts.url); - if (opts.versions.length > 0) { - opts.name += '-' + opts.versions.join('--'); - } - - opts.name = opts.name.replace(/\W/g, '').toLowerCase(); - if (this.router.mounts[opts.name]) // GH-401 - opts.name += uuid.v4().substr(0, 7); - } else { - opts.name = opts.name.replace(/\W/g, '').toLowerCase(); - } + opts.name = opts.name.replace(/\W/g, '').toLowerCase(); + if (this.router.mounts[opts.name]) // GH-401 + opts.name += uuid.v4().substr(0, 7); + } else { + opts.name = opts.name.replace(/\W/g, '').toLowerCase(); + } - if (!(route = this.router.mount(opts))) - return (false); + if (!(route = this.router.mount(opts))) + return (false); - this.chain.forEach(addHandler); - argumentsToChain(arguments, 1).forEach(addHandler); - this.routes[route] = chain; + this.chain.forEach(addHandler); + argumentsToChain(arguments, 1).forEach(addHandler); + this.routes[route] = chain; - return (route); + return (route); }; -}); + }); /** @@ -426,15 +423,15 @@ Server.prototype.close = function close(callback) { * @param {Function} The middleware function to execute */ Server.prototype.param = function param(name, fn) { - this.use(function _param(req, res, next) { - if (req.params && req.params[name]) { - fn.call(this, req, res, next, req.params[name], name); - } else { - next(); - } - }); + this.use(function _param(req, res, next) { + if (req.params && req.params[name]) { + fn.call(this, req, res, next, req.params[name], name); + } else { + next(); + } + }); - return (this); + return (this); }; @@ -456,27 +453,27 @@ Server.prototype.param = function param(name, fn) { * will be the selected version */ Server.prototype.versionedUse = function versionedUse(versions, fn) { - if (!Array.isArray(versions)) - versions = [versions]; - assert.arrayOfString(versions, 'versions'); - - versions.forEach(function (v) { - if (!semver.valid(v)) - throw new TypeError('%s is not a valid semver', v); - }); - - this.use(function _versionedUse(req, res, next) { - var ver; - if (req.version() === '*' || - (ver = maxSatisfying(versions, - req.version()) || false)) { - fn.call(this, req, res, next, ver); - } else { - next(); - } - }); + if (!Array.isArray(versions)) + versions = [versions]; + assert.arrayOfString(versions, 'versions'); + + versions.forEach(function (v) { + if (!semver.valid(v)) + throw new TypeError('%s is not a valid semver', v); + }); + + this.use(function _versionedUse(req, res, next) { + var ver; + if (req.version() === '*' || + (ver = maxSatisfying(versions, + req.version()) || false)) { + fn.call(this, req, res, next, ver); + } else { + next(); + } + }); - return (this); + return (this); }; @@ -490,11 +487,11 @@ Server.prototype.versionedUse = function versionedUse(versions, fn) { * @throws {TypeError} on bad input. */ Server.prototype.rm = function rm(route) { - var r = this.router.unmount(route); - if (r && this.routes[r]) - delete this.routes[r]; + var r = this.router.unmount(route); + if (r && this.routes[r]) + delete this.routes[r]; - return (r); + return (r); }; @@ -507,13 +504,13 @@ Server.prototype.rm = function rm(route) { * @throws {TypeError} on input error. */ Server.prototype.use = function use() { - var self = this; + var self = this; - (argumentsToChain(arguments) || []).forEach(function (h) { - self.chain.push(h); - }); + (argumentsToChain(arguments) || []).forEach(function (h) { + self.chain.push(h); + }); - return (this); + return (this); }; @@ -523,114 +520,113 @@ Server.prototype.use = function use() { * depends on. Note that req.params will _not_ be set yet. */ Server.prototype.pre = function pre() { - var self = this; + var self = this; - argumentsToChain(arguments).forEach(function (h) { - self.before.push(h); - }); + argumentsToChain(arguments).forEach(function (h) { + self.before.push(h); + }); - return (this); + return (this); }; Server.prototype.toString = function toString() { - var LINE_FMT = '\t%s: %s\n'; - var SUB_LINE_FMT = '\t\t%s: %s\n'; - var self = this; - var str = ''; - - function handlersToString(arr) { - var s = '[' + arr.map(function (b) { - return (b.name || 'function'); - }).join(', ') + ']'; - - return (s); - } - - str += sprintf(LINE_FMT, 'Accepts', this.acceptable.join(', ')); - str += sprintf(LINE_FMT, 'Name', this.name); - str += sprintf(LINE_FMT, 'Pre', handlersToString(this.before)); - str += sprintf(LINE_FMT, 'Router', this.router.toString()); - str += sprintf(LINE_FMT, 'Routes', ''); - Object.keys(this.routes).forEach(function (k) { - var handlers = handlersToString(self.routes[k]); - str += sprintf(SUB_LINE_FMT, k, handlers); - }); - str += sprintf(LINE_FMT, 'Secure', this.secure); - str += sprintf(LINE_FMT, 'Url', this.url); - str += sprintf(LINE_FMT, 'Version', this.versions.join()); - - return (str); + var LINE_FMT = '\t%s: %s\n'; + var SUB_LINE_FMT = '\t\t%s: %s\n'; + var self = this; + var str = ''; + + function handlersToString(arr) { + var s = '[' + arr.map(function (b) { + return (b.name || 'function'); + }).join(', ') + ']'; + + return (s); + } + + str += sprintf(LINE_FMT, 'Accepts', this.acceptable.join(', ')); + str += sprintf(LINE_FMT, 'Name', this.name); + str += sprintf(LINE_FMT, 'Pre', handlersToString(this.before)); + str += sprintf(LINE_FMT, 'Router', this.router.toString()); + str += sprintf(LINE_FMT, 'Routes', ''); + Object.keys(this.routes).forEach(function (k) { + var handlers = handlersToString(self.routes[k]); + str += sprintf(SUB_LINE_FMT, k, handlers); + }); + str += sprintf(LINE_FMT, 'Secure', this.secure); + str += sprintf(LINE_FMT, 'Url', this.url); + str += sprintf(LINE_FMT, 'Version', this.versions.join()); + + return (str); }; - ///--- Private methods Server.prototype._handle = function _handle(req, res) { - var self = this; + var self = this; - function routeAndRun() { - self._route(req, res, function (route, context) { - req.context = req.params = context; - req.route = route.spec; + function routeAndRun() { + self._route(req, res, function (route, context) { + req.context = req.params = context; + req.route = route.spec; - var r = route ? route.name : null; - var chain = self.routes[r]; + var r = route ? route.name : null; + var chain = self.routes[r]; - self._run(req, res, route, chain, function done(e) { - self.emit('after', req, res, route, e); - }); - }); - } + self._run(req, res, route, chain, function done(e) { + self.emit('after', req, res, route, e); + }); + }); + } - if (this.before.length > 0) { - this._run(req, res, null, this.before, function (err) { - if (!err) { - routeAndRun(); - } - }); - } else { + if (this.before.length > 0) { + this._run(req, res, null, this.before, function (err) { + if (!err) { routeAndRun(); - } + } + }); + } else { + routeAndRun(); + } }; Server.prototype._route = function _route(req, res, name, cb) { - var self = this; - - if (typeof (name) === 'function') { - cb = name; - name = null; + var self = this; + + if (typeof (name) === 'function') { + cb = name; + name = null; + } else { + this.router.get(name, req, function (err, route, ctx) { + if (err) { + emitRouteError(self, req, res, err); + } else { + cb(route, ctx); + } + }); + } + + this.router.find(req, res, function onRoute(err, route, ctx) { + var r = route ? route.name : null; + if (err) { + if (optionsError(err, req, res)) { + self.emit('after', req, res, err); + } else { + emitRouteError(self, req, res, err); + } + } else if (r === 'preflight') { + res.writeHead(200); + res.end(); + self.emit('after', req, res, null); + } else if (!r || !self.routes[r]) { + err = new ResourceNotFoundError(req.path()); + emitRouteError(self, res, res, err); } else { - this.router.get(name, req, function (err, route, ctx) { - if (err) { - emitRouteError(self, req, res, err); - } else { - cb(route, ctx); - } - }); + cb(route, ctx); } - - this.router.find(req, res, function onRoute(err, route, ctx) { - var r = route ? route.name : null; - if (err) { - if (optionsError(err, req, res)) { - self.emit('after', req, res, err); - } else { - emitRouteError(self, req, res, err); - } - } else if (r === 'preflight') { - res.writeHead(200); - res.end(); - self.emit('after', req, res, null); - } else if (!r || !self.routes[r]) { - err = new ResourceNotFoundError(req.path()); - emitRouteError(self, res, res, err); - } else { - cb(route, ctx); - } - }); + }); }; @@ -646,151 +642,153 @@ Server.prototype._route = function _route(req, res, name, cb) { // a response was sent and you don't want the chain to keep // going Server.prototype._run = function _run(req, res, route, chain, cb) { - var d; - var i = -1; - var id = dtrace.nextId(); - var log = this.log; - var self = this; - var t; - - function next(arg) { - var done = false; - if (arg) { - if (arg instanceof Error) { - log.trace({err: arg}, 'next(err=%s)', - (arg.name || 'Error')); - res.send(arg); - done = true; - } else if (typeof (arg) === 'string') { - if (req._rstfy_chained_route) { - var _e = new errors.InternalError(); - log.error({ - err: _e - }, 'Multiple next("chain") calls not ' + - 'supported'); - res.send(_e); - return (false); - } - - self._route(req, res, arg, function (r, ctx) { - req.context = req.params = ctx; - req.route = r.spec; - - var _c = chain.slice(0, i + 1); - function _uniq(fn) { - return (_c.indexOf(fn) === -1); - } - - var _routes = self.routes[r.name] || []; - var _chain = _routes.filter(_uniq); - - req._rstfy_chained_route = true; - self._run(req, res, r, _chain, cb); - }); - } + var d; + var i = -1; + var id = dtrace.nextId(); + var log = this.log; + var self = this; + var t; + + function next(arg) { + var done = false; + if (arg) { + if (arg instanceof Error) { + log.trace({err: arg}, 'next(err=%s)', + (arg.name || 'Error')); + res.send(arg); + done = true; + } else if (typeof (arg) === 'string') { + if (req._rstfy_chained_route) { + var _e = new errors.InternalError(); + log.error({ + err: _e + }, 'Multiple next("chain") calls not ' + + 'supported'); + res.send(_e); + return (false); } - if (arg === false) - done = true; - - // Fire DTrace done for the previous handler. - if ((i + 1) > 0 && chain[i] && !chain[i]._skip) { - var _name = chain[i].name || ('handler-' + i); - req.timers.push({ - name: _name, - time: process.hrtime(t) - }); - - dtrace._rstfy_probes['handler-done'].fire(function () { - return ([ - self.name, - route !== null ? route.name : 'pre', - _name, - id - ]); - }); - } + self._route(req, res, arg, function (r, ctx) { + req.context = req.params = ctx; + req.route = r.spec; - // Run the next handler up - if (!done && chain[++i]) { - if (chain[i]._skip) - return (next()); - - t = process.hrtime(); - if (log.trace()) - log.trace('running %s', chain[i].name || '?'); - - dtrace._rstfy_probes['handler-start'].fire(function () { - return ([ - self.name, - route !== null ? route.name : 'pre', - chain[i].name || ('handler-' + i), - id - ]); - }); - - var n = once(next); - n.ifError = ifError(n); - return (chain[i].call(self, req, res, n)); - } + var _c = chain.slice(0, i + 1); - dtrace._rstfy_probes['route-done'].fire(function () { - return ([ - self.name, - route !== null ? route.name : 'pre', - id, - res.statusCode || 200, - res.headers() - ]); + function _uniq(fn) { + return (_c.indexOf(fn) === -1); + } + + var _routes = self.routes[r.name] || []; + var _chain = _routes.filter(_uniq); + + req._rstfy_chained_route = true; + self._run(req, res, r, _chain, cb); }); + } + } - if (route === null) { - self.emit('preDone', req, res); - } else { - self.emit('done', req, res, route); - } + if (arg === false) + done = true; + + // Fire DTrace done for the previous handler. + if ((i + 1) > 0 && chain[i] && !chain[i]._skip) { + var _name = chain[i].name || ('handler-' + i); + req.timers.push({ + name: _name, + time: process.hrtime(t) + }); - return (cb ? cb(arg) : true); + dtrace._rstfy_probes['handler-done'].fire(function () { + return ([ + self.name, + route !== null ? route.name : 'pre', + _name, + id + ]); + }); } - var n1 = once(next); - n1.ifError = ifError(n1); - dtrace._rstfy_probes['route-start'].fire(function () { + // Run the next handler up + if (!done && chain[++i]) { + if (chain[i]._skip) + return (next()); + + t = process.hrtime(); + if (log.trace()) + log.trace('running %s', chain[i].name || '?'); + + dtrace._rstfy_probes['handler-start'].fire(function () { return ([ - self.name, - route !== null ? route.name : 'pre', - id, - req.method, - req.href(), - req.headers + self.name, + route !== null ? route.name : 'pre', + chain[i].name || ('handler-' + i), + id ]); - }); + }); - req.timers = []; - d = domain.create(); - d.add(req); - d.add(res); - d.on('error', function onError(err) { - if (err._restify_next) { - err._restify_next(err); - } else { - log.trace({err: err}, 'uncaughtException'); - self.emit('uncaughtException', req, res, route, err); - } + var n = once(next); + n.ifError = ifError(n); + return (chain[i].call(self, req, res, n)); + } + + dtrace._rstfy_probes['route-done'].fire(function () { + return ([ + self.name, + route !== null ? route.name : 'pre', + id, + res.statusCode || 200, + res.headers() + ]); }); - d.run(n1); + + if (route === null) { + self.emit('preDone', req, res); + } else { + self.emit('done', req, res, route); + } + + return (cb ? cb(arg) : true); + } + + var n1 = once(next); + n1.ifError = ifError(n1); + + dtrace._rstfy_probes['route-start'].fire(function () { + return ([ + self.name, + route !== null ? route.name : 'pre', + id, + req.method, + req.href(), + req.headers + ]); + }); + + req.timers = []; + d = domain.create(); + d.add(req); + d.add(res); + d.on('error', function onError(err) { + if (err._restify_next) { + err._restify_next(err); + } else { + log.trace({err: err}, 'uncaughtException'); + self.emit('uncaughtException', req, res, route, err); + } + }); + d.run(n1); }; Server.prototype._setupRequest = function _setupRequest(req, res) { - req.log = res.log = this.log; - req._time = res._time = Date.now(); - - res.acceptable = this.acceptable; - res.formatters = this.formatters; - res.req = req; - res.serverName = this.name; - res.version = this.router.versions[this.router.versions.length - 1]; + req.log = res.log = this.log; + req._time = res._time = Date.now(); + + res.acceptable = this.acceptable; + res.formatters = this.formatters; + res.req = req; + res.serverName = this.name; + res.version = this.router.versions[this.router.versions.length - 1]; }; // vim: set et ts=8 sts=8 sw=8: diff --git a/lib/upgrade.js b/lib/upgrade.js index 3fbe5fd96..d8c9db2cd 100644 --- a/lib/upgrade.js +++ b/lib/upgrade.js @@ -5,11 +5,11 @@ var util = require('util'); var assert = require('assert-plus'); function InvalidUpgradeStateError(msg) { - if (Error.captureStackTrace) - Error.captureStackTrace(this, InvalidUpgradeStateError); + if (Error.captureStackTrace) + Error.captureStackTrace(this, InvalidUpgradeStateError); - this.message = msg; - this.name = 'InvalidUpgradeStateError'; + this.message = msg; + this.name = 'InvalidUpgradeStateError'; } util.inherits(InvalidUpgradeStateError, Error); @@ -38,144 +38,144 @@ util.inherits(InvalidUpgradeStateError, Error); // other handler, this method will throw. // function createServerUpgradeResponse(req, socket, head) { - return (new ServerUpgradeResponse(socket, head)); + return (new ServerUpgradeResponse(socket, head)); } function ServerUpgradeResponse(socket, head) { - assert.object(socket, 'socket'); - assert.buffer(head, 'head'); + assert.object(socket, 'socket'); + assert.buffer(head, 'head'); - EventEmitter.call(this); + EventEmitter.call(this); - this.sendDate = true; - this.statusCode = 400; + this.sendDate = true; + this.statusCode = 400; - this._upgrade = { - socket: socket, - head: head - }; + this._upgrade = { + socket: socket, + head: head + }; - this._headWritten = false; - this._upgradeClaimed = false; + this._headWritten = false; + this._upgradeClaimed = false; } util.inherits(ServerUpgradeResponse, EventEmitter); function notImplemented(method) { - if (!method.throws) { - return function () { - return (method.returns); - }; - } else { - return function () { - throw (new Error('Method ' + method.name + ' is not ' + - 'implemented!')); - }; - } + if (!method.throws) { + return function () { + return (method.returns); + }; + } else { + return function () { + throw (new Error('Method ' + method.name + ' is not ' + + 'implemented!')); + }; + } } var NOT_IMPLEMENTED = [ - { name: 'writeContinue', throws: true }, - { name: 'setHeader', throws: false, returns: null }, - { name: 'getHeader', throws: false, returns: null }, - { name: 'getHeaders', throws: false, returns: {} }, - { name: 'removeHeader', throws: false, returns: null }, - { name: 'addTrailer', throws: false, returns: null }, - { name: 'cache', throws: false, returns: 'public' }, - { name: 'format', throws: true }, - { name: 'set', throws: false, returns: null }, - { name: 'get', throws: false, returns: null }, - { name: 'headers', throws: false, returns: {} }, - { name: 'header', throws: false, returns: null }, - { name: 'json', throws: false, returns: null }, - { name: 'link', throws: false, returns: null } + { name: 'writeContinue', throws: true }, + { name: 'setHeader', throws: false, returns: null }, + { name: 'getHeader', throws: false, returns: null }, + { name: 'getHeaders', throws: false, returns: {} }, + { name: 'removeHeader', throws: false, returns: null }, + { name: 'addTrailer', throws: false, returns: null }, + { name: 'cache', throws: false, returns: 'public' }, + { name: 'format', throws: true }, + { name: 'set', throws: false, returns: null }, + { name: 'get', throws: false, returns: null }, + { name: 'headers', throws: false, returns: {} }, + { name: 'header', throws: false, returns: null }, + { name: 'json', throws: false, returns: null }, + { name: 'link', throws: false, returns: null } ]; NOT_IMPLEMENTED.forEach(function (method) { - ServerUpgradeResponse.prototype[method.name] = notImplemented(method); + ServerUpgradeResponse.prototype[method.name] = notImplemented(method); }); -ServerUpgradeResponse.prototype._writeHeadImpl = function _writeHeadImpl( -statusCode, reason) { +ServerUpgradeResponse.prototype._writeHeadImpl = + function _writeHeadImpl(statusCode, reason) { if (this._headWritten) - return; + return; this._headWritten = true; if (this._upgradeClaimed) - throw new InvalidUpgradeStateError('Upgrade already claimed!'); + throw new InvalidUpgradeStateError('Upgrade already claimed!'); var head = [ - 'HTTP/1.1 ' + statusCode + ' ' + reason, - 'Connection: close' + 'HTTP/1.1 ' + statusCode + ' ' + reason, + 'Connection: close' ]; if (this.sendDate) - head.push('Date: ' + new Date().toUTCString()); + head.push('Date: ' + new Date().toUTCString()); this._upgrade.socket.write(head.join('\r\n') + '\r\n'); -}; + }; ServerUpgradeResponse.prototype.status = function status(code) { - assert.number(code, 'code'); - this.statusCode = code; - return (code); + assert.number(code, 'code'); + this.statusCode = code; + return (code); }; ServerUpgradeResponse.prototype.send = function send(code, body) { - if (typeof (code) === 'number') - this.statusCode = code; - else - body = code; - - if (typeof (body) === 'object') { - if (typeof (body.statusCode) === 'number') - this.statusCode = body.statusCode; - if (typeof (body.message) === 'string') - this.statusReason = body.message; - } - - return (this.end()); + if (typeof (code) === 'number') + this.statusCode = code; + else + body = code; + + if (typeof (body) === 'object') { + if (typeof (body.statusCode) === 'number') + this.statusCode = body.statusCode; + if (typeof (body.message) === 'string') + this.statusReason = body.message; + } + + return (this.end()); }; ServerUpgradeResponse.prototype.end = function end() { - this._writeHeadImpl(this.statusCode, 'Connection Not Upgraded'); - this._upgrade.socket.end('\r\n'); - return (true); + this._writeHeadImpl(this.statusCode, 'Connection Not Upgraded'); + this._upgrade.socket.end('\r\n'); + return (true); }; ServerUpgradeResponse.prototype.write = function write() { - this._writeHeadImpl(this.statusCode, 'Connection Not Upgraded'); - return (true); + this._writeHeadImpl(this.statusCode, 'Connection Not Upgraded'); + return (true); }; -ServerUpgradeResponse.prototype.writeHead = function writeHead(statusCode, -reason) { +ServerUpgradeResponse.prototype.writeHead = + function writeHead(statusCode, reason) { assert.number(statusCode, 'statusCode'); assert.optionalString(reason, 'reason'); this.statusCode = statusCode; if (!reason) - reason = 'Connection Not Upgraded'; + reason = 'Connection Not Upgraded'; if (this._headWritten) - throw new Error('Head already written!'); + throw new Error('Head already written!'); return (this._writeHeadImpl(statusCode, reason)); -}; + }; ServerUpgradeResponse.prototype.claimUpgrade = function claimUpgrade() { - if (this._upgradeClaimed) - throw new InvalidUpgradeStateError('Upgrade already claimed!'); + if (this._upgradeClaimed) + throw new InvalidUpgradeStateError('Upgrade already claimed!'); - if (this._headWritten) - throw new InvalidUpgradeStateError('Upgrade already aborted!'); + if (this._headWritten) + throw new InvalidUpgradeStateError('Upgrade already aborted!'); - this._upgradeClaimed = true; + this._upgradeClaimed = true; - return (this._upgrade); + return (this._upgrade); }; module.exports = { - createResponse: createServerUpgradeResponse, + createResponse: createServerUpgradeResponse, - InvalidUpgradeStateError: InvalidUpgradeStateError + InvalidUpgradeStateError: InvalidUpgradeStateError }; // vim: set et ts=8 sts=8 sw=8: diff --git a/lib/utils.js b/lib/utils.js index 6349caf68..ce9e0b1b0 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -3,7 +3,6 @@ var assert = require('assert-plus'); - /** * Cleans up sloppy URL paths, like /foo////bar/// to /foo/bar. * @@ -11,39 +10,37 @@ var assert = require('assert-plus'); * @return {String} Cleaned up form of path. */ function sanitizePath(path) { - assert.ok(path); + assert.ok(path); - // Be nice like apache and strip out any //my//foo//bar///blah - path = path.replace(/\/\/+/g, '/'); + // Be nice like apache and strip out any //my//foo//bar///blah + path = path.replace(/\/\/+/g, '/'); - // Kill a trailing '/' - if (path.lastIndexOf('/') === (path.length - 1) && path.length > 1) - path = path.substr(0, path.length - 1); + // Kill a trailing '/' + if (path.lastIndexOf('/') === (path.length - 1) && path.length > 1) + path = path.substr(0, path.length - 1); - return (path); + return (path); } - /** * Return a shallow copy of the given object; */ function shallowCopy(obj) { - if (!obj) { - return (obj); - } - var copy = {}; - Object.keys(obj).forEach(function (k) { - copy[k] = obj[k]; - }); - return (copy); + if (!obj) { + return (obj); + } + var copy = {}; + Object.keys(obj).forEach(function (k) { + copy[k] = obj[k]; + }); + return (copy); } - ///--- Exports module.exports = { - sanitizePath: sanitizePath, - shallowCopy: shallowCopy + sanitizePath: sanitizePath, + shallowCopy: shallowCopy }; diff --git a/test/client.test.js b/test/client.test.js index 512672055..4f815ae97 100644 --- a/test/client.test.js +++ b/test/client.test.js @@ -7,11 +7,10 @@ var uuid = require('node-uuid'); var restify = require('../lib'); if (require.cache[__dirname + '/lib/helper.js']) - delete require.cache[__dirname + '/lib/helper.js']; + delete require.cache[__dirname + '/lib/helper.js']; var helper = require('./lib/helper.js'); - ///--- Globals var after = helper.after; @@ -25,695 +24,693 @@ var RAW_CLIENT; var SERVER; - ///--- Helpers function sendJson(req, res, next) { - res.send({hello: req.params.hello || req.params.name || null}); - next(); + res.send({hello: req.params.hello || req.params.name || null}); + next(); } function sendText(req, res, next) { - res.send('hello ' + (req.params.hello || req.params.name || '')); - next(); + res.send('hello ' + (req.params.hello || req.params.name || '')); + next(); } function sendSignature(req, res, next) { - res.header('content-type', 'text/plain'); - var hdr = req.header('Awesome-Signature'); - if (!hdr) { - res.send('request NOT signed'); - } else { - res.send('ok: ' + hdr); - } + res.header('content-type', 'text/plain'); + var hdr = req.header('Awesome-Signature'); + if (!hdr) { + res.send('request NOT signed'); + } else { + res.send('ok: ' + hdr); + } } function sendWhitespace(req, res, next) { - var body = ' '; - if (req.params.flavor === 'spaces') { - body = ' '; - } else if (req.params.flavor === 'tabs') { - body = ' \t\t '; - } - - // override contentType as otherwise the string is json-ified to - // include quotes. Don't want that for this test. - res.header('content-type', 'text/plain'); - res.send(body); - next(); + var body = ' '; + if (req.params.flavor === 'spaces') { + body = ' '; + } else if (req.params.flavor === 'tabs') { + body = ' \t\t '; + } + + // override contentType as otherwise the string is json-ified to + // include quotes. Don't want that for this test. + res.header('content-type', 'text/plain'); + res.send(body); + next(); } - ///--- Tests before(function (callback) { - try { - SERVER = restify.createServer({ - dtrace: helper.dtrace, - log: helper.getLog('server') - }); - - SERVER.use(restify.acceptParser(['json', 'text/plain'])); - SERVER.use(restify.dateParser()); - SERVER.use(restify.authorizationParser()); - SERVER.use(restify.queryParser()); - SERVER.use(restify.bodyParser()); - - SERVER.get('/signed', sendSignature); - SERVER.get('/whitespace/:flavor', sendWhitespace); - - SERVER.get('/json/boom', function (req, res, next) { - res.set('content-type', 'text/html'); - res.send(200, ''); - next(); - }); - SERVER.del('/contentLengthAllowed', function (req, res, next) { - if (req.header('content-length')) { - res.send(200, 'Allowed'); - } else { - res.send(200, 'Not allowed'); - } - next(); - }); - - SERVER.get('/json/:name', sendJson); - SERVER.head('/json/:name', sendJson); - SERVER.put('/json/:name', sendJson); - SERVER.post('/json/:name', sendJson); - SERVER.patch('/json/:name', sendJson); - - SERVER.del('/str/:name', sendText); - SERVER.get('/str/:name', sendText); - SERVER.head('/str/:name', sendText); - SERVER.put('/str/:name', sendText); - SERVER.post('/str/:name', sendText); - SERVER.patch('/str/:name', sendText); - - SERVER.listen(PORT, '127.0.0.1', function () { - PORT = SERVER.address().port; - - JSON_CLIENT = restify.createJsonClient({ - url: 'http://127.0.0.1:' + PORT, - dtrace: helper.dtrace, - retry: false - }); - STR_CLIENT = restify.createStringClient({ - url: 'http://127.0.0.1:' + PORT, - dtrace: helper.dtrace, - retry: false - }); - RAW_CLIENT = restify.createClient({ - url: 'http://127.0.0.1:' + PORT, - dtrace: helper.dtrace, - retry: false, - headers: { - accept: 'text/plain' - } - }); - process.nextTick(callback); - }); - } catch (e) { - console.error(e.stack); - process.exit(1); - } + try { + SERVER = restify.createServer({ + dtrace: helper.dtrace, + log: helper.getLog('server') + }); + + SERVER.use(restify.acceptParser(['json', 'text/plain'])); + SERVER.use(restify.dateParser()); + SERVER.use(restify.authorizationParser()); + SERVER.use(restify.queryParser()); + SERVER.use(restify.bodyParser()); + + SERVER.get('/signed', sendSignature); + SERVER.get('/whitespace/:flavor', sendWhitespace); + + SERVER.get('/json/boom', function (req, res, next) { + res.set('content-type', 'text/html'); + res.send(200, ''); + next(); + }); + SERVER.del('/contentLengthAllowed', function (req, res, next) { + if (req.header('content-length')) { + res.send(200, 'Allowed'); + } else { + res.send(200, 'Not allowed'); + } + next(); + }); + + SERVER.get('/json/:name', sendJson); + SERVER.head('/json/:name', sendJson); + SERVER.put('/json/:name', sendJson); + SERVER.post('/json/:name', sendJson); + SERVER.patch('/json/:name', sendJson); + + SERVER.del('/str/:name', sendText); + SERVER.get('/str/:name', sendText); + SERVER.head('/str/:name', sendText); + SERVER.put('/str/:name', sendText); + SERVER.post('/str/:name', sendText); + SERVER.patch('/str/:name', sendText); + + SERVER.listen(PORT, '127.0.0.1', function () { + PORT = SERVER.address().port; + + JSON_CLIENT = restify.createJsonClient({ + url: 'http://127.0.0.1:' + PORT, + dtrace: helper.dtrace, + retry: false + }); + STR_CLIENT = restify.createStringClient({ + url: 'http://127.0.0.1:' + PORT, + dtrace: helper.dtrace, + retry: false + }); + RAW_CLIENT = restify.createClient({ + url: 'http://127.0.0.1:' + PORT, + dtrace: helper.dtrace, + retry: false, + headers: { + accept: 'text/plain' + } + }); + process.nextTick(callback); + }); + } catch (e) { + console.error(e.stack); + process.exit(1); + } }); after(function (callback) { - try { - JSON_CLIENT.close(); - STR_CLIENT.close(); - RAW_CLIENT.close(); - SERVER.close(callback); - } catch (e) { - console.error(e.stack); - process.exit(1); - } + try { + JSON_CLIENT.close(); + STR_CLIENT.close(); + RAW_CLIENT.close(); + SERVER.close(callback); + } catch (e) { + console.error(e.stack); + process.exit(1); + } }); test('GET json', function (t) { - JSON_CLIENT.get('/json/mcavage', function (err, req, res, obj) { - t.ifError(err); - t.ok(req); - t.ok(res); - t.deepEqual(obj, {hello: 'mcavage'}); - t.end(); - }); + JSON_CLIENT.get('/json/mcavage', function (err, req, res, obj) { + t.ifError(err); + t.ok(req); + t.ok(res); + t.deepEqual(obj, {hello: 'mcavage'}); + t.end(); + }); }); test('GH-388 GET json, but really HTML', function (t) { - JSON_CLIENT.get('/json/boom', function (err, req, res, obj) { - t.ifError(err); - t.ok(req); - t.ok(res); - t.deepEqual(obj, {}); - t.end(); - }); + JSON_CLIENT.get('/json/boom', function (err, req, res, obj) { + t.ifError(err); + t.ok(req); + t.ok(res); + t.deepEqual(obj, {}); + t.end(); + }); }); test('GH-115 GET path with spaces', function (t) { - // As of node v0.11, this throws, since it's never valid HTTP - try { - JSON_CLIENT.get('/json/foo bar', function (err, req, res, obj) { - t.ok(err); - t.equal(err.code, 'ECONNRESET'); - t.end(); - }); - } catch (err) { - t.ok(err); - t.equal(err.constructor, TypeError); - t.equal(err.message, - 'Request path contains unescaped characters.'); - t.end(); - } + // As of node v0.11, this throws, since it's never valid HTTP + try { + JSON_CLIENT.get('/json/foo bar', function (err, req, res, obj) { + t.ok(err); + t.equal(err.code, 'ECONNRESET'); + t.end(); + }); + } catch (err) { + t.ok(err); + t.equal(err.constructor, TypeError); + t.equal(err.message, + 'Request path contains unescaped characters.'); + t.end(); + } }); test('Check error (404)', function (t) { - JSON_CLIENT.get('/' + uuid(), function (err, req, res, obj) { - t.ok(err); - t.ok(err.message); - t.equal(err.statusCode, 404); - t.ok(req); - t.ok(res); - t.ok(obj); - t.equal(obj.code, 'ResourceNotFound'); - t.ok(obj.message); - t.end(); - }); + JSON_CLIENT.get('/' + uuid(), function (err, req, res, obj) { + t.ok(err); + t.ok(err.message); + t.equal(err.statusCode, 404); + t.ok(req); + t.ok(res); + t.ok(obj); + t.equal(obj.code, 'ResourceNotFound'); + t.ok(obj.message); + t.end(); + }); }); test('HEAD json', function (t) { - JSON_CLIENT.head('/json/mcavage', function (err, req, res) { - t.ifError(err); - t.ok(req); - t.ok(res); - t.end(); - }); + JSON_CLIENT.head('/json/mcavage', function (err, req, res) { + t.ifError(err); + t.ok(req); + t.ok(res); + t.end(); + }); }); test('POST json', function (t) { - var data = { hello: 'foo' }; - JSON_CLIENT.post('/json/mcavage', data, function (err, req, res, obj) { - t.ifError(err); - t.ok(req); - t.ok(res); - t.deepEqual(obj, {hello: 'foo'}); - t.end(); - }); + var data = { hello: 'foo' }; + JSON_CLIENT.post('/json/mcavage', data, function (err, req, res, obj) { + t.ifError(err); + t.ok(req); + t.ok(res); + t.deepEqual(obj, {hello: 'foo'}); + t.end(); + }); }); test('POST json empty body object', function (t) { - var data = {}; - JSON_CLIENT.post('/json/mcavage', data, function (err, req, res, obj) { - t.ifError(err); - t.ok(req); - t.ok(res); - t.deepEqual(obj, {hello: 'mcavage'}); - t.end(); - }); + var data = {}; + JSON_CLIENT.post('/json/mcavage', data, function (err, req, res, obj) { + t.ifError(err); + t.ok(req); + t.ok(res); + t.deepEqual(obj, {hello: 'mcavage'}); + t.end(); + }); }); test('PUT json', function (t) { - var data = { hello: 'foo' }; - JSON_CLIENT.post('/json/mcavage', data, function (err, req, res, obj) { - t.ifError(err); - t.ok(req); - t.ok(res); - t.deepEqual(obj, {hello: 'foo'}); - t.end(); - }); + var data = { hello: 'foo' }; + JSON_CLIENT.post('/json/mcavage', data, function (err, req, res, obj) { + t.ifError(err); + t.ok(req); + t.ok(res); + t.deepEqual(obj, {hello: 'foo'}); + t.end(); + }); }); test('PATCH json', function (t) { - var data = { hello: 'foo' }; - JSON_CLIENT.patch('/json/mcavage', data, function (err, req, res, obj) { - t.ifError(err); - t.ok(req); - t.ok(res); - t.deepEqual(obj, {hello: 'foo'}); - t.end(); - }); + var data = { hello: 'foo' }; + JSON_CLIENT.patch('/json/mcavage', data, function (err, req, res, obj) { + t.ifError(err); + t.ok(req); + t.ok(res); + t.deepEqual(obj, {hello: 'foo'}); + t.end(); + }); }); test('GET text', function (t) { - STR_CLIENT.get('/str/mcavage', function (err, req, res, data) { - t.ifError(err); - t.ok(req); - t.ok(res); - t.equal(res.body, data); - t.equal(data, 'hello mcavage'); - t.end(); - }); + STR_CLIENT.get('/str/mcavage', function (err, req, res, data) { + t.ifError(err); + t.ok(req); + t.ok(res); + t.equal(res.body, data); + t.equal(data, 'hello mcavage'); + t.end(); + }); }); test('HEAD text', function (t) { - STR_CLIENT.head('/str/mcavage', function (err, req, res) { - t.ifError(err); - t.ok(req); - t.ok(res); - t.end(); - }); + STR_CLIENT.head('/str/mcavage', function (err, req, res) { + t.ifError(err); + t.ok(req); + t.ok(res); + t.end(); + }); }); test('Check error (404)', function (t) { - STR_CLIENT.get('/' + uuid(), function (err, req, res, message) { - t.ok(err); - t.ok(err.message); - t.equal(err.statusCode, 404); - t.ok(req); - t.ok(res); - t.ok(message); - t.end(); - }); + STR_CLIENT.get('/' + uuid(), function (err, req, res, message) { + t.ok(err); + t.ok(err.message); + t.equal(err.statusCode, 404); + t.ok(req); + t.ok(res); + t.ok(message); + t.end(); + }); }); test('POST text', function (t) { - var body = 'hello=foo'; - STR_CLIENT.post('/str/mcavage', body, function (err, req, res, data) { - t.ifError(err); - t.ok(req); - t.ok(res); - t.equal(res.body, data); - t.equal(data, 'hello foo'); - t.end(); - }); + var body = 'hello=foo'; + STR_CLIENT.post('/str/mcavage', body, function (err, req, res, data) { + t.ifError(err); + t.ok(req); + t.ok(res); + t.equal(res.body, data); + t.equal(data, 'hello foo'); + t.end(); + }); }); test('PATCH text', function (t) { - var body = 'hello=foo'; - STR_CLIENT.patch('/str/mcavage', body, function (err, req, res, data) { - t.ifError(err); - t.ok(req); - t.ok(res); - t.equal(res.body, data); - t.equal(data, 'hello foo'); - t.end(); - }); + var body = 'hello=foo'; + STR_CLIENT.patch('/str/mcavage', body, function (err, req, res, data) { + t.ifError(err); + t.ok(req); + t.ok(res); + t.equal(res.body, data); + t.equal(data, 'hello foo'); + t.end(); + }); }); test('POST text (object)', function (t) { - var body = {hello: 'foo'}; - STR_CLIENT.post('/str/mcavage', body, function (err, req, res, data) { - t.ifError(err); - t.ok(req); - t.ok(res); - t.equal(res.body, data); - t.equal(data, 'hello foo'); - t.end(); - }); + var body = {hello: 'foo'}; + STR_CLIENT.post('/str/mcavage', body, function (err, req, res, data) { + t.ifError(err); + t.ok(req); + t.ok(res); + t.equal(res.body, data); + t.equal(data, 'hello foo'); + t.end(); + }); }); test('POST text empty body string', function (t) { - var body = ''; - STR_CLIENT.post('/str/mcavage', body, function (err, req, res, data) { - t.ifError(err); - t.ok(req); - t.ok(res); - t.equal(res.body, data); - t.equal(data, 'hello mcavage'); - t.end(); - }); + var body = ''; + STR_CLIENT.post('/str/mcavage', body, function (err, req, res, data) { + t.ifError(err); + t.ok(req); + t.ok(res); + t.equal(res.body, data); + t.equal(data, 'hello mcavage'); + t.end(); + }); }); test('POST text null body', function (t) { - var body = null; - STR_CLIENT.post('/str/mcavage', body, function (err, req, res, data) { - t.ifError(err); - t.ok(req); - t.ok(res); - t.equal(res.body, data); - t.equal(data, 'hello mcavage'); - t.end(); - }); + var body = null; + STR_CLIENT.post('/str/mcavage', body, function (err, req, res, data) { + t.ifError(err); + t.ok(req); + t.ok(res); + t.equal(res.body, data); + t.equal(data, 'hello mcavage'); + t.end(); + }); }); test('POST text empty body object', function (t) { - var body = {}; - STR_CLIENT.post('/str/mcavage', body, function (err, req, res, data) { - t.ifError(err); - t.ok(req); - t.ok(res); - t.equal(res.body, data); - t.equal(data, 'hello mcavage'); - t.end(); - }); + var body = {}; + STR_CLIENT.post('/str/mcavage', body, function (err, req, res, data) { + t.ifError(err); + t.ok(req); + t.ok(res); + t.equal(res.body, data); + t.equal(data, 'hello mcavage'); + t.end(); + }); }); test('PUT text', function (t) { - var body = 'hello=foo'; - STR_CLIENT.put('/str/mcavage', body, function (err, req, res, data) { - t.ifError(err); - t.ok(req); - t.ok(res); - t.equal(res.body, data); - t.equal(data, 'hello foo'); - t.end(); - }); + var body = 'hello=foo'; + STR_CLIENT.put('/str/mcavage', body, function (err, req, res, data) { + t.ifError(err); + t.ok(req); + t.ok(res); + t.equal(res.body, data); + t.equal(data, 'hello foo'); + t.end(); + }); }); test('PUT text empty body string', function (t) { - var body = ''; - STR_CLIENT.put('/str/mcavage', body, function (err, req, res, data) { - t.ifError(err); - t.ok(req); - t.ok(res); - t.equal(res.body, data); - t.equal(data, 'hello mcavage'); - t.end(); - }); + var body = ''; + STR_CLIENT.put('/str/mcavage', body, function (err, req, res, data) { + t.ifError(err); + t.ok(req); + t.ok(res); + t.equal(res.body, data); + t.equal(data, 'hello mcavage'); + t.end(); + }); }); test('DELETE text', function (t) { - STR_CLIENT.del('/str/mcavage', function (err, req, res) { - t.ifError(err); - t.ok(req); - t.ok(res); - t.end(); - }); + STR_CLIENT.del('/str/mcavage', function (err, req, res) { + t.ifError(err); + t.ok(req); + t.ok(res); + t.end(); + }); }); test('DELETE allows content-length', function (t) { - var opts = { - path: '/contentLengthAllowed', - headers: { - 'content-length': '0' - } - }; + var opts = { + path: '/contentLengthAllowed', + headers: { + 'content-length': '0' + } + }; - STR_CLIENT.del(opts, function (err, req, res, obj) { - t.ifError(err); - t.ok(req); - t.ok(res); - t.deepEqual(obj, 'Allowed'); - t.end(); - }); + STR_CLIENT.del(opts, function (err, req, res, obj) { + t.ifError(err); + t.ok(req); + t.ok(res); + t.deepEqual(obj, 'Allowed'); + t.end(); + }); }); test('GET raw', function (t) { - RAW_CLIENT.get('/str/mcavage', function (connectErr, req) { - t.ifError(connectErr); - t.ok(req); - - req.on('result', function (err, res) { - t.ifError(err); - res.body = ''; - res.setEncoding('utf8'); - res.on('data', function (chunk) { - res.body += chunk; - }); - - res.on('end', function () { - t.equal(res.body, 'hello mcavage'); - t.end(); - }); - }); + RAW_CLIENT.get('/str/mcavage', function (connectErr, req) { + t.ifError(connectErr); + t.ok(req); + + req.on('result', function (err, res) { + t.ifError(err); + res.body = ''; + res.setEncoding('utf8'); + res.on('data', function (chunk) { + res.body += chunk; + }); + + res.on('end', function () { + t.equal(res.body, 'hello mcavage'); + t.end(); + }); }); + }); }); test('POST raw', function (t) { - var opts = { - path: '/str/mcavage', - headers: { - 'content-type': 'application/x-www-form-urlencoded' - } - }; - RAW_CLIENT.post(opts, function (connectErr, req) { - t.ifError(connectErr); - - req.write('hello=snoopy'); - req.end(); - - req.on('result', function (err, res) { - t.ifError(err); - res.body = ''; - res.setEncoding('utf8'); - res.on('data', function (chunk) { - res.body += chunk; - }); - - res.on('end', function () { - t.equal(res.body, 'hello snoopy'); - t.end(); - }); - }); + var opts = { + path: '/str/mcavage', + headers: { + 'content-type': 'application/x-www-form-urlencoded' + } + }; + RAW_CLIENT.post(opts, function (connectErr, req) { + t.ifError(connectErr); + + req.write('hello=snoopy'); + req.end(); + + req.on('result', function (err, res) { + t.ifError(err); + res.body = ''; + res.setEncoding('utf8'); + res.on('data', function (chunk) { + res.body += chunk; + }); + + res.on('end', function () { + t.equal(res.body, 'hello snoopy'); + t.end(); + }); }); + }); }); test('GH-20 connectTimeout', function (t) { - var client = restify.createClient({ - url: 'http://169.254.1.10', - type: 'http', - accept: 'text/plain', - connectTimeout: 100, - retry: false, - agent: false - }); + var client = restify.createClient({ + url: 'http://169.254.1.10', + type: 'http', + accept: 'text/plain', + connectTimeout: 100, + retry: false, + agent: false + }); - client.get('/foo', function (err, req) { - t.ok(err); - t.equal(err.name, 'ConnectTimeoutError'); - t.end(); - }); + client.get('/foo', function (err, req) { + t.ok(err); + t.equal(err.name, 'ConnectTimeoutError'); + t.end(); + }); }); test('GH-169 PUT json Content-MD5', function (t) { - var msg = { - '_id': '4ff71172bc148900000010a3', - 'userId': '4f711b377579dbf65e000001', - 'courseId': '4f69021bff338faffa000001', - 'createdByUserId': '4f711b377579dbf65e000001', - 'dateFrom': '2012-06-04', - 'dateTo': '2012-09-30', - 'notes': 'Rates do not include tax & are subject to change ' + - 'without notice\\nRental Clubs are available for $30 ' + - 'per set\\nAll major credit cards accepted', - 'updatedAt': '2012-07-06T17:59:08.581Z', - 'periods': [ - { - 'name': 'morning', - 'weekdayWalking': 1500, - 'weekdayCart': 3000, - 'weekendWalking': 2000, - 'weekendCart': 3500, - 'timeFrom': 0, - 'timeTo': 780, - '_id': '4ff71172bc148900000010a4' - }, - { - 'timeFrom': 780, - 'name': 'twilight', - 'timeTo': 900, - 'weekdayWalking': 1500, - 'weekdayCart': 2500, - 'weekendWalking': 1500, - 'weekendCart': 3000, - '_id': '4ff7276cbc148900000010f4' - }, - { - 'timeFrom': 900, - 'name': 'super twilight', - 'weekdayWalking': 1200, - 'weekdayCart': 2000, - 'weekendWalking': 1200, - 'weekendCart': 2500, - 'timeTo': 1439, - '_id': '4ff7276cbc148900000010f3' - } - ], - 'holidays': [ - { - 'country': 'US', - 'name': 'Flag Day', - 'start': 1339657200000, - 'end': 1339743600000, - 'date': '2012-06-14' - }, - { - 'country': 'US / MX', - 'name': 'Father\'s Day, Día del Padre ' + - '(Father\'s Day)', - 'start': 1340262000000, - 'end': 1340348400000, - 'date': '2012-06-21' - }, - { - 'country': 'US', - 'name': 'Independence Day', - 'start': 1341385200000, - 'end': 1341471600000, - 'date': '2012-07-04' - }, - { - 'country': 'US', - 'name': 'Labor Day', - 'start': 1347001200000, - 'end': 1347087600000, - 'date': '2012-09-07' - } - ], - 'weekdaySunday': false, - 'weekdaySaturday': false, - 'weekdayFriday': false, - 'weekdayThursday': true, - 'weekdayWednesday': true, - 'weekdayTuesday': true, - 'weekdayMonday': true - }; - - JSON_CLIENT.put('/json/md5', msg, function (err, req, res, obj) { - t.ifError(err); - t.ok(req); - t.ok(res); - t.end(); - }); + var msg = { + '_id': '4ff71172bc148900000010a3', + 'userId': '4f711b377579dbf65e000001', + 'courseId': '4f69021bff338faffa000001', + 'createdByUserId': '4f711b377579dbf65e000001', + 'dateFrom': '2012-06-04', + 'dateTo': '2012-09-30', + 'notes': 'Rates do not include tax & are subject to change ' + + 'without notice\\nRental Clubs are available for $30 ' + + 'per set\\nAll major credit cards accepted', + 'updatedAt': '2012-07-06T17:59:08.581Z', + 'periods': [ + { + 'name': 'morning', + 'weekdayWalking': 1500, + 'weekdayCart': 3000, + 'weekendWalking': 2000, + 'weekendCart': 3500, + 'timeFrom': 0, + 'timeTo': 780, + '_id': '4ff71172bc148900000010a4' + }, + { + 'timeFrom': 780, + 'name': 'twilight', + 'timeTo': 900, + 'weekdayWalking': 1500, + 'weekdayCart': 2500, + 'weekendWalking': 1500, + 'weekendCart': 3000, + '_id': '4ff7276cbc148900000010f4' + }, + { + 'timeFrom': 900, + 'name': 'super twilight', + 'weekdayWalking': 1200, + 'weekdayCart': 2000, + 'weekendWalking': 1200, + 'weekendCart': 2500, + 'timeTo': 1439, + '_id': '4ff7276cbc148900000010f3' + } + ], + 'holidays': [ + { + 'country': 'US', + 'name': 'Flag Day', + 'start': 1339657200000, + 'end': 1339743600000, + 'date': '2012-06-14' + }, + { + 'country': 'US / MX', + 'name': 'Father\'s Day, Día del Padre ' + + '(Father\'s Day)', + 'start': 1340262000000, + 'end': 1340348400000, + 'date': '2012-06-21' + }, + { + 'country': 'US', + 'name': 'Independence Day', + 'start': 1341385200000, + 'end': 1341471600000, + 'date': '2012-07-04' + }, + { + 'country': 'US', + 'name': 'Labor Day', + 'start': 1347001200000, + 'end': 1347087600000, + 'date': '2012-09-07' + } + ], + 'weekdaySunday': false, + 'weekdaySaturday': false, + 'weekdayFriday': false, + 'weekdayThursday': true, + 'weekdayWednesday': true, + 'weekdayTuesday': true, + 'weekdayMonday': true + }; + + JSON_CLIENT.put('/json/md5', msg, function (err, req, res, obj) { + t.ifError(err); + t.ok(req); + t.ok(res); + t.end(); + }); }); test('GH-203 GET json, body is whitespace', function (t) { - JSON_CLIENT.get('/whitespace/spaces', function (err, req, res, obj) { - t.ifError(err); - t.ok(req); - t.ok(res); - t.deepEqual(obj, {}); - t.end(); - }); + JSON_CLIENT.get('/whitespace/spaces', function (err, req, res, obj) { + t.ifError(err); + t.ok(req); + t.ok(res); + t.deepEqual(obj, {}); + t.end(); + }); }); test('GH-203 GET json, body is tabs', function (t) { - JSON_CLIENT.get('/whitespace/tabs', function (err, req, res, obj) { - t.ifError(err); - t.ok(req); - t.ok(res); - t.deepEqual(obj, {}); - t.end(); - }); + JSON_CLIENT.get('/whitespace/tabs', function (err, req, res, obj) { + t.ifError(err); + t.ok(req); + t.ok(res); + t.deepEqual(obj, {}); + t.end(); + }); }); test('don\'t sign a request', function (t) { - var client = restify.createClient({ - url: 'http://127.0.0.1:' + PORT, - type: 'string', - accept: 'text/plain', - headers: { 'Gusty-Winds': 'May Exist' }, - agent: false - }); - client.get('/signed', function (err, req, res, data) { - t.ifError(err); - t.ok(data); - t.equal(data, 'request NOT signed'); - t.end(); - }); + var client = restify.createClient({ + url: 'http://127.0.0.1:' + PORT, + type: 'string', + accept: 'text/plain', + headers: { 'Gusty-Winds': 'May Exist' }, + agent: false + }); + client.get('/signed', function (err, req, res, data) { + t.ifError(err); + t.ok(data); + t.equal(data, 'request NOT signed'); + t.end(); + }); }); test('sign a request', function (t) { - var called = 0; - var signer = function sign(request) { - called++; - if (!request || !(request instanceof http.ClientRequest)) - throw new Error('request must be an instance of ' + - 'http.ClientRequest'); - var gw = request.getHeader('Gusty-Winds'); - if (!gw) - throw new Error('Gusty-Winds header was not ' + - 'present in request'); - request.setHeader('Awesome-Signature', 'Gusty Winds ' + gw); - }; - var client = restify.createClient({ - url: 'http://127.0.0.1:' + PORT, - type: 'string', - accept: 'text/plain', - signRequest: signer, - headers: { 'Gusty-Winds': 'May Exist' }, - agent: false - }); - client.get('/signed', function (err, req, res, data) { - t.ifError(err); - t.ok(data); - t.equal(called, 1); - t.equal(data, 'ok: Gusty Winds May Exist'); - t.end(); - }); + var called = 0; + var signer = function sign(request) { + called++; + if (!request || !(request instanceof http.ClientRequest)) + throw new Error('request must be an instance of ' + + 'http.ClientRequest'); + var gw = request.getHeader('Gusty-Winds'); + if (!gw) + throw new Error('Gusty-Winds header was not ' + + 'present in request'); + request.setHeader('Awesome-Signature', 'Gusty Winds ' + gw); + }; + var client = restify.createClient({ + url: 'http://127.0.0.1:' + PORT, + type: 'string', + accept: 'text/plain', + signRequest: signer, + headers: { 'Gusty-Winds': 'May Exist' }, + agent: false + }); + client.get('/signed', function (err, req, res, data) { + t.ifError(err); + t.ok(data); + t.equal(called, 1); + t.equal(data, 'ok: Gusty Winds May Exist'); + t.end(); + }); }); /* BEGIN JSSTYLED */ test('secure client connection with timeout', function (t) { - var server = restify.createServer({ - certificate: '-----BEGIN CERTIFICATE-----\n' + - 'MIICgzCCAewCCQDutc3iIPK88jANBgkqhkiG9w0BAQUFADCBhTELMAkGA1UEBhMC\n' + - 'VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28x\n' + - 'FTATBgNVBAoMDEpveWVudCwgSW5jLjEPMA0GA1UECwwGUG9ydGFsMSEwHwYJKoZI\n' + - 'hvcNAQkBFhJzdXBwb3J0QGpveWVudC5jb20wHhcNMTMxMjAyMjI0NjU3WhcNMTQx\n' + - 'MjAyMjI0NjU3WjCBhTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWEx\n' + - 'FjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xFTATBgNVBAoMDEpveWVudCwgSW5jLjEP\n' + - 'MA0GA1UECwwGUG9ydGFsMSEwHwYJKoZIhvcNAQkBFhJzdXBwb3J0QGpveWVudC5j\n' + - 'b20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANAWr+pYW+AEP4vC48fByPa2\n' + - 'Fw0h8FSSgVO2zHyibH9S6nSFaNSLeHRofFdK+cD7IRt4A6jxp57IItNwjFiNNMjF\n' + - 'CS5NXKIdPE6HMlb1X7ae+/nN3xRy5321Bi8yQZI6p6b9ATwP8mGBcvx4ta165YFt\n' + - 'M2FmaYWLSNbHIwCxTQMJAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAXFT1q/uB3/fg\n' + - 'Iq7iZ6R7q7tBYtd9ttQKp8by8jVToIXP4jUEWZ7vE9TZ1/Wm8ZHxlAPjZtN+rsmS\n' + - 'LvgDV22T/s0LgSrdbB/rpYgjsJarlAGfbUYQ6gZKvCMSiZI7oJfl89HDT3PgCtSx\n' + - 'RqHcNabt4hoSccUuACJ1FXkszJ312fA=\n' + - '-----END CERTIFICATE-----', - key: '-----BEGIN RSA PRIVATE KEY-----\n' + - 'MIICXAIBAAKBgQDQFq/qWFvgBD+LwuPHwcj2thcNIfBUkoFTtsx8omx/Uup0hWjU\n' + - 'i3h0aHxXSvnA+yEbeAOo8aeeyCLTcIxYjTTIxQkuTVyiHTxOhzJW9V+2nvv5zd8U\n' + - 'cud9tQYvMkGSOqem/QE8D/JhgXL8eLWteuWBbTNhZmmFi0jWxyMAsU0DCQIDAQAB\n' + - 'AoGBAJvz9OHAWRMae/mmFYqXfKMSM1J/Vhw8NLrl7HmYTZJbNSYg+kEZSiyMRmwx\n' + - '3963F8f7eVq7yfFhc2BeIIEZSy23J9QJCqVIqzl6m2URP4+Dw7ZS2iWIsiPyy+L8\n' + - 'v8CXPQhRGouOXxU6h7WHpfw+Xy+WPVmIVARMi4UpmmOE52eBAkEA6gui4nD841Ds\n' + - 'UEQDMuxNpCf+B20BWKkt8PNODY1HS4rBVCh81oMbaV7VDSabZM7Ba4wrmTAhb1Sc\n' + - 'm7bc/YOb0QJBAOObuVTMCbJ7WZhAPHVYhGS5ptuL9fkktj2BPDcf/3KyuDsM6oVw\n' + - 'Rs9kUfQrSV+w7YALqxWzNCUgzq+qLYPaGbkCQF5hKuIdph0UuPb1NkUGvZiA+BOO\n' + - 'hYh3UKtlsggM/L8dyTBi01S9sgQf1dJjyy4vohf4gmxX2GPIvw6cAynINMECQEjc\n' + - '7TOMLf6JJmFrDu+x6pAkLppR7+hWLFD8Mj6ja69YL0oYFGurSb/Sqbm0scSEa0N2\n' + - 'eMp1l9fa7M+ndvKiu2ECQGv4W2+yqlbD3Q3Dr14hiWaiYss5350Ohr5HiZZw2L3i\n' + - 's35vQZaHqRxUVZjOi6/MTCZmqvg/RpaVQYHiJHvxGzw=\n' + - '-----END RSA PRIVATE KEY-----' - }); - server.get('/ping', function (req, res) { - res.end('pong'); - }); - server.listen(8443); - - var client = restify.createStringClient({url: 'https://127.0.0.1:8443', connectTimeout: 2000, rejectUnauthorized: false}); - var timeout = setTimeout(function () { - t.ok(false, 'timed out'); - t.end(); - }, 2050); - - client.get('/ping', function (err, req, res, body) { - clearTimeout(timeout); - t.equal(body, 'pong'); - client.close(); - server.close(); - t.end(); - }); + var server = restify.createServer({ + certificate: '-----BEGIN CERTIFICATE-----\n' + + 'MIICgzCCAewCCQDutc3iIPK88jANBgkqhkiG9w0BAQUFADCBhTELMAkGA1UEBhMC\n' + + 'VVMxEzARBgNVBAgMCkNhbGlmb3JuaWExFjAUBgNVBAcMDVNhbiBGcmFuY2lzY28x\n' + + 'FTATBgNVBAoMDEpveWVudCwgSW5jLjEPMA0GA1UECwwGUG9ydGFsMSEwHwYJKoZI\n' + + 'hvcNAQkBFhJzdXBwb3J0QGpveWVudC5jb20wHhcNMTMxMjAyMjI0NjU3WhcNMTQx\n' + + 'MjAyMjI0NjU3WjCBhTELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWEx\n' + + 'FjAUBgNVBAcMDVNhbiBGcmFuY2lzY28xFTATBgNVBAoMDEpveWVudCwgSW5jLjEP\n' + + 'MA0GA1UECwwGUG9ydGFsMSEwHwYJKoZIhvcNAQkBFhJzdXBwb3J0QGpveWVudC5j\n' + + 'b20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANAWr+pYW+AEP4vC48fByPa2\n' + + 'Fw0h8FSSgVO2zHyibH9S6nSFaNSLeHRofFdK+cD7IRt4A6jxp57IItNwjFiNNMjF\n' + + 'CS5NXKIdPE6HMlb1X7ae+/nN3xRy5321Bi8yQZI6p6b9ATwP8mGBcvx4ta165YFt\n' + + 'M2FmaYWLSNbHIwCxTQMJAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAXFT1q/uB3/fg\n' + + 'Iq7iZ6R7q7tBYtd9ttQKp8by8jVToIXP4jUEWZ7vE9TZ1/Wm8ZHxlAPjZtN+rsmS\n' + + 'LvgDV22T/s0LgSrdbB/rpYgjsJarlAGfbUYQ6gZKvCMSiZI7oJfl89HDT3PgCtSx\n' + + 'RqHcNabt4hoSccUuACJ1FXkszJ312fA=\n' + + '-----END CERTIFICATE-----', + key: '-----BEGIN RSA PRIVATE KEY-----\n' + + 'MIICXAIBAAKBgQDQFq/qWFvgBD+LwuPHwcj2thcNIfBUkoFTtsx8omx/Uup0hWjU\n' + + 'i3h0aHxXSvnA+yEbeAOo8aeeyCLTcIxYjTTIxQkuTVyiHTxOhzJW9V+2nvv5zd8U\n' + + 'cud9tQYvMkGSOqem/QE8D/JhgXL8eLWteuWBbTNhZmmFi0jWxyMAsU0DCQIDAQAB\n' + + 'AoGBAJvz9OHAWRMae/mmFYqXfKMSM1J/Vhw8NLrl7HmYTZJbNSYg+kEZSiyMRmwx\n' + + '3963F8f7eVq7yfFhc2BeIIEZSy23J9QJCqVIqzl6m2URP4+Dw7ZS2iWIsiPyy+L8\n' + + 'v8CXPQhRGouOXxU6h7WHpfw+Xy+WPVmIVARMi4UpmmOE52eBAkEA6gui4nD841Ds\n' + + 'UEQDMuxNpCf+B20BWKkt8PNODY1HS4rBVCh81oMbaV7VDSabZM7Ba4wrmTAhb1Sc\n' + + 'm7bc/YOb0QJBAOObuVTMCbJ7WZhAPHVYhGS5ptuL9fkktj2BPDcf/3KyuDsM6oVw\n' + + 'Rs9kUfQrSV+w7YALqxWzNCUgzq+qLYPaGbkCQF5hKuIdph0UuPb1NkUGvZiA+BOO\n' + + 'hYh3UKtlsggM/L8dyTBi01S9sgQf1dJjyy4vohf4gmxX2GPIvw6cAynINMECQEjc\n' + + '7TOMLf6JJmFrDu+x6pAkLppR7+hWLFD8Mj6ja69YL0oYFGurSb/Sqbm0scSEa0N2\n' + + 'eMp1l9fa7M+ndvKiu2ECQGv4W2+yqlbD3Q3Dr14hiWaiYss5350Ohr5HiZZw2L3i\n' + + 's35vQZaHqRxUVZjOi6/MTCZmqvg/RpaVQYHiJHvxGzw=\n' + + '-----END RSA PRIVATE KEY-----' + }); + server.get('/ping', function (req, res) { + res.end('pong'); + }); + server.listen(8443); + + var client = restify.createStringClient({url: 'https://127.0.0.1:8443', connectTimeout: 2000, rejectUnauthorized: false}); + var timeout = setTimeout(function () { + t.ok(false, 'timed out'); + t.end(); + }, 2050); + + client.get('/ping', function (err, req, res, body) { + clearTimeout(timeout); + t.equal(body, 'pong'); + client.close(); + server.close(); + t.end(); + }); }); /* END JSSTYLED */ diff --git a/test/fieldedTextParser.test.js b/test/fieldedTextParser.test.js index 5bb784c45..0b56b5c5f 100644 --- a/test/fieldedTextParser.test.js +++ b/test/fieldedTextParser.test.js @@ -1,10 +1,9 @@ - /** * Module dependencies */ if (require.cache[__dirname + '/lib/helper.js']) { - delete require.cache[__dirname + '/lib/helper.js']; + delete require.cache[__dirname + '/lib/helper.js']; } var restify = require('../lib'); @@ -33,114 +32,114 @@ var OBJECT_TSV = require(__dirname + '/files/object-tsv.json'); */ before(function (callback) { - try { - SERVER = restify.createServer({ + try { + SERVER = restify.createServer({ dtrace: helper.dtrace, log: helper.getLog('server') - }); - SERVER.use(restify.acceptParser(SERVER.acceptable)); - SERVER.use(restify.authorizationParser()); - SERVER.use(restify.dateParser()); - SERVER.use(restify.queryParser()); - SERVER.use(restify.bodyParser()); - SERVER.post('/data', function respond(req, res, next) { + }); + SERVER.use(restify.acceptParser(SERVER.acceptable)); + SERVER.use(restify.authorizationParser()); + SERVER.use(restify.dateParser()); + SERVER.use(restify.queryParser()); + SERVER.use(restify.bodyParser()); + SERVER.post('/data', function respond(req, res, next) { res.send({ - status: 'okay', - parsedReq: req.body + status: 'okay', + parsedReq: req.body }); return (next()); - }); - SERVER.listen(PORT, '127.0.0.1', function () { + }); + SERVER.listen(PORT, '127.0.0.1', function () { CLIENT = restify.createClient({ - url: 'http://127.0.0.1:' + PORT, - dtrace: helper.dtrace, - retry: false, - agent: false + url: 'http://127.0.0.1:' + PORT, + dtrace: helper.dtrace, + retry: false, + agent: false }); callback(); - }); - } catch (e) { - console.error(e.stack); - process.exit(1); - } + }); + } catch (e) { + console.error(e.stack); + process.exit(1); + } }); after(function (callback) { - try { - CLIENT.close(); - SERVER.close(callback); - } catch (e) { - console.error(e.stack); - process.exit(1); - } + try { + CLIENT.close(); + SERVER.close(callback); + } catch (e) { + console.error(e.stack); + process.exit(1); + } }); test('Parse CSV body', function (t) { - var options = { - path: '/data', - headers: { + var options = { + path: '/data', + headers: { 'Content-Type': 'text/csv' - } - }; - CLIENT.post(options, function (err, req) { - if (err) { + } + }; + CLIENT.post(options, function (err, req) { + if (err) { t.end(err); return; - } - req.on('result', function (errReq, res) { + } + req.on('result', function (errReq, res) { if (errReq) { - t.end(errReq); - return; + t.end(errReq); + return; } res.body = ''; res.setEncoding('utf8'); res.on('data', function (chunk) { - res.body += chunk; + res.body += chunk; }); res.on('end', function () { - res.body = JSON.parse(res.body); - var parsedReqStr = JSON.stringify(res.body.parsedReq); - var objectStr = JSON.stringify(OBJECT_CSV); - t.equal(parsedReqStr, objectStr); - t.end(); + res.body = JSON.parse(res.body); + var parsedReqStr = JSON.stringify(res.body.parsedReq); + var objectStr = JSON.stringify(OBJECT_CSV); + t.equal(parsedReqStr, objectStr); + t.end(); }); - }); - req.write(DATA_CSV); - req.end(); }); + req.write(DATA_CSV); + req.end(); + }); }); test('Parse TSV body', function (t) { - var options = { - path: '/data', - headers: { + var options = { + path: '/data', + headers: { 'Content-Type': 'text/tsv' - } - }; - CLIENT.post(options, function (err, req) { - if (err) { + } + }; + CLIENT.post(options, function (err, req) { + if (err) { t.end(err); return; - } - req.on('result', function (errReq, res) { + } + req.on('result', function (errReq, res) { if (errReq) { - t.end(errReq); - return; + t.end(errReq); + return; } res.body = ''; res.setEncoding('utf8'); res.on('data', function (chunk) { - res.body += chunk; + res.body += chunk; }); res.on('end', function () { - res.body = JSON.parse(res.body); - var parsedReqStr = JSON.stringify(res.body.parsedReq); - var objectStr = JSON.stringify(OBJECT_TSV); - t.equal(parsedReqStr, objectStr); - t.end(); + res.body = JSON.parse(res.body); + var parsedReqStr = JSON.stringify(res.body.parsedReq); + var objectStr = JSON.stringify(OBJECT_TSV); + t.equal(parsedReqStr, objectStr); + t.end(); }); - }); - req.write(DATA_TSV); - req.end(); }); + req.write(DATA_TSV); + req.end(); + }); }); diff --git a/test/formatter.test.js b/test/formatter.test.js index fcc24bd18..a1c7c3da5 100644 --- a/test/formatter.test.js +++ b/test/formatter.test.js @@ -5,11 +5,10 @@ var assert = require('assert-plus'); var restify = require('../lib'); if (require.cache[__dirname + '/lib/helper.js']) - delete require.cache[__dirname + '/lib/helper.js']; + delete require.cache[__dirname + '/lib/helper.js']; var helper = require('./lib/helper.js'); - ///--- Globals var after = helper.after; @@ -26,91 +25,91 @@ var isFirstRequest = true; ///--- Tests before(function (callback) { - try { - SERVER = restify.createServer({ - formatters: { - 'text/plain': function (req, res, body, cb) { - if (isFirstRequest) { - process.nextTick(function () { - isFirstRequest = false; - cb(null, 'async fmt'); - }); - return (this); - } - - return ('sync fmt'); - }, - 'application/foo; q=0.9': function () { - return ('foo!'); - } - }, - dtrace: helper.dtrace, - log: helper.getLog('server'), - version: ['2.0.0', '0.5.4', '1.4.3'] - }); - SERVER.listen(PORT, '127.0.0.1', function () { - PORT = SERVER.address().port; - CLIENT = restify.createStringClient({ - url: 'http://127.0.0.1:' + PORT, - dtrace: helper.dtrace, - retry: false, - agent: false - }); - SERVER.get('/tmp', function (req, res) { - res.send('dummy response'); + try { + SERVER = restify.createServer({ + formatters: { + 'text/plain': function (req, res, body, cb) { + if (isFirstRequest) { + process.nextTick(function () { + isFirstRequest = false; + cb(null, 'async fmt'); }); - process.nextTick(callback); - }); - } catch (e) { - console.error(e.stack); - process.exit(1); - } + return (this); + } + + return ('sync fmt'); + }, + 'application/foo; q=0.9': function () { + return ('foo!'); + } + }, + dtrace: helper.dtrace, + log: helper.getLog('server'), + version: ['2.0.0', '0.5.4', '1.4.3'] + }); + SERVER.listen(PORT, '127.0.0.1', function () { + PORT = SERVER.address().port; + CLIENT = restify.createStringClient({ + url: 'http://127.0.0.1:' + PORT, + dtrace: helper.dtrace, + retry: false, + agent: false + }); + SERVER.get('/tmp', function (req, res) { + res.send('dummy response'); + }); + process.nextTick(callback); + }); + } catch (e) { + console.error(e.stack); + process.exit(1); + } }); after(function (callback) { - try { - SERVER.close(callback); - } catch (e) { - console.error(e.stack); - process.exit(1); - } + try { + SERVER.close(callback); + } catch (e) { + console.error(e.stack); + process.exit(1); + } }); test('async formatter', function (t) { - CLIENT.get('/tmp', function (err, req, res, data) { - t.ifError(err); - t.ok(req); - t.ok(res); - t.equal(data, 'async fmt'); - t.end(); - }); + CLIENT.get('/tmp', function (err, req, res, data) { + t.ifError(err); + t.ok(req); + t.ok(res); + t.equal(data, 'async fmt'); + t.end(); + }); }); test('sync formatter', function (t) { - CLIENT.get('/tmp', function (err, req, res, data) { - t.ifError(err); - t.ok(req); - t.ok(res); - t.equal(data, 'sync fmt'); - t.end(); - }); + CLIENT.get('/tmp', function (err, req, res, data) { + t.ifError(err); + t.ok(req); + t.ok(res); + t.equal(data, 'sync fmt'); + t.end(); + }); }); test('q-val priority', function (t) { - var opts = { - path: '/tmp', - headers: { - accept: 'application/*' - } - }; - CLIENT.get(opts, function (err, req, res, data) { - t.ifError(err); - t.ok(req); - t.ok(res); - t.equal(data, 'foo!'); - t.end(); - }); + var opts = { + path: '/tmp', + headers: { + accept: 'application/*' + } + }; + CLIENT.get(opts, function (err, req, res, data) { + t.ifError(err); + t.ok(req); + t.ok(res); + t.equal(data, 'foo!'); + t.end(); + }); }); \ No newline at end of file diff --git a/test/index.test.js b/test/index.test.js index 1cbdc0031..2113a5ca9 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -3,37 +3,35 @@ var restify = require('../lib/index'); if (require.cache[__dirname + '/lib/helper.js']) - delete require.cache[__dirname + '/lib/helper.js']; + delete require.cache[__dirname + '/lib/helper.js']; var helper = require('./lib/helper.js'); - ///--- Globals var realizeUrl = restify.realizeUrl; var test = helper.test; - ///--- Tests test('realize', function (t) { - var pattern = '/foo/:bar/:baz'; + var pattern = '/foo/:bar/:baz'; - t.equal(realizeUrl(pattern, {}), '/foo/:bar/:baz'); - t.equal(realizeUrl(pattern, {bar: 'BAR'}), '/foo/BAR/:baz'); - t.equal(realizeUrl(pattern, {bar: 'BAR', baz: 'BAZ'}), '/foo/BAR/BAZ'); - t.equal(realizeUrl(pattern, {bar: 'BAR', baz: 'BAZ', quux: 'QUUX'}), - '/foo/BAR/BAZ'); - t.equal(realizeUrl('/foo////bar///:baz', {baz: 'BAZ'}), - '/foo/bar/BAZ'); + t.equal(realizeUrl(pattern, {}), '/foo/:bar/:baz'); + t.equal(realizeUrl(pattern, {bar: 'BAR'}), '/foo/BAR/:baz'); + t.equal(realizeUrl(pattern, {bar: 'BAR', baz: 'BAZ'}), '/foo/BAR/BAZ'); + t.equal(realizeUrl(pattern, {bar: 'BAR', baz: 'BAZ', quux: 'QUUX'}), + '/foo/BAR/BAZ'); + t.equal(realizeUrl('/foo////bar///:baz', {baz: 'BAZ'}), + '/foo/bar/BAZ'); - t.end(); + t.end(); }); test('httpDate', function (t) { - var d = restify.httpDate(); - var regex = /\w{3}, \d{1,2} \w{3} \d{4} \d{2}:\d{2}:\d{2} GMT/; - t.ok(regex.test(d)); - t.end(); + var d = restify.httpDate(); + var regex = /\w{3}, \d{1,2} \w{3} \d{4} \d{2}:\d{2}:\d{2} GMT/; + t.ok(regex.test(d)); + t.end(); }); diff --git a/test/lib/helper.js b/test/lib/helper.js index 4d968da0a..defdf100f 100644 --- a/test/lib/helper.js +++ b/test/lib/helper.js @@ -12,89 +12,88 @@ var once = require('once'); var restify = require('../../lib'); - ///--- Exports module.exports = { - after: function after(teardown) { - module.parent.exports.tearDown = function _teardown(callback) { - var d = domain.create(); - var self = this; - - d.once('error', function (err) { - console.error('after: uncaught error\n', + - err.stack); - process.exit(1); - }); - - d.run(function () { - teardown.call(self, once(callback)); - }); - }; - }, - - before: function before(setup) { - module.parent.exports.setUp = function _setup(callback) { - var d = domain.create(); - var self = this; - - d.once('error', function (err) { - console.error('before: uncaught error\n' + - err.stack); - process.exit(1); - }); - - d.run(function () { - setup.call(self, once(callback)); - }); + after: function after(teardown) { + module.parent.exports.tearDown = function _teardown(callback) { + var d = domain.create(); + var self = this; + + d.once('error', function (err) { + console.error('after: uncaught error\n', + + err.stack); + process.exit(1); + }); + + d.run(function () { + teardown.call(self, once(callback)); + }); + }; + }, + + before: function before(setup) { + module.parent.exports.setUp = function _setup(callback) { + var d = domain.create(); + var self = this; + + d.once('error', function (err) { + console.error('before: uncaught error\n' + + err.stack); + process.exit(1); + }); + + d.run(function () { + setup.call(self, once(callback)); + }); + }; + }, + + test: function test(name, tester) { + module.parent.exports[name] = function _(t) { + var d = domain.create(); + var self = this; + + d.once('error', function (err) { + t.ifError(err); + t.end(); + }); + + d.add(t); + d.run(function () { + t.end = once(function () { + t.done(); + }); + t.notOk = function notOk(ok, message) { + return (t.ok(!ok, message)); }; - }, - - test: function test(name, tester) { - module.parent.exports[name] = function _(t) { - var d = domain.create(); - var self = this; - - d.once('error', function (err) { - t.ifError(err); - t.end(); - }); - - d.add(t); - d.run(function () { - t.end = once(function () { - t.done(); - }); - t.notOk = function notOk(ok, message) { - return (t.ok(!ok, message)); - }; - - tester.call(self, t); - }); - }; - }, - - getLog: function (name, stream, level) { - return (bunyan.createLogger({ - level: (process.env.LOG_LEVEL || level || 'fatal'), - name: name || process.argv[1], - stream: stream || process.stdout, - src: true, - serializers: restify.bunyan.serializers - })); - }, - - - get dtrace() { - var dtp; - try { - var d = require('dtrace-provider'); - dtp = d.createDTraceProvider('restifyUnitTest'); - } catch (e) { - dtp = null; - } - - return (dtp); + + tester.call(self, t); + }); + }; + }, + + getLog: function (name, stream, level) { + return (bunyan.createLogger({ + level: (process.env.LOG_LEVEL || level || 'fatal'), + name: name || process.argv[1], + stream: stream || process.stdout, + src: true, + serializers: restify.bunyan.serializers + })); + }, + + + get dtrace() { + var dtp; + try { + var d = require('dtrace-provider'); + dtp = d.createDTraceProvider('restifyUnitTest'); + } catch (e) { + dtp = null; } + + return (dtp); + } }; diff --git a/test/plugins.test.js b/test/plugins.test.js index b0c1059b4..08b9a39e1 100644 --- a/test/plugins.test.js +++ b/test/plugins.test.js @@ -9,11 +9,10 @@ var path = require('path'); var fs = require('fs'); if (require.cache[__dirname + '/lib/helper.js']) - delete require.cache[__dirname + '/lib/helper.js']; + delete require.cache[__dirname + '/lib/helper.js']; var helper = require('./lib/helper.js'); - ///--- Globals var after = helper.after; @@ -30,755 +29,764 @@ var DIRS_TO_DELETE = []; ///--- Tests before(function (callback) { - try { - SERVER = restify.createServer({ - dtrace: helper.dtrace, - log: helper.getLog('server') - }); + try { + SERVER = restify.createServer({ + dtrace: helper.dtrace, + log: helper.getLog('server') + }); - SERVER.use(restify.acceptParser(SERVER.acceptable)); - SERVER.use(restify.authorizationParser()); - SERVER.use(restify.dateParser()); - SERVER.use(restify.queryParser()); + SERVER.use(restify.acceptParser(SERVER.acceptable)); + SERVER.use(restify.authorizationParser()); + SERVER.use(restify.dateParser()); + SERVER.use(restify.queryParser()); - SERVER.get('/foo/:id', function respond(req, res, next) { - res.send(); - next(); - }); + SERVER.get('/foo/:id', function respond(req, res, next) { + res.send(); + next(); + }); - SERVER.listen(PORT, '127.0.0.1', function () { - PORT = SERVER.address().port; - CLIENT = restify.createJsonClient({ - url: 'http://127.0.0.1:' + PORT, - dtrace: helper.dtrace, - retry: false, - agent: false - }); + SERVER.listen(PORT, '127.0.0.1', function () { + PORT = SERVER.address().port; + CLIENT = restify.createJsonClient({ + url: 'http://127.0.0.1:' + PORT, + dtrace: helper.dtrace, + retry: false, + agent: false + }); - process.nextTick(callback); - }); - } catch (e) { - console.error(e.stack); - process.exit(1); - } + process.nextTick(callback); + }); + } catch (e) { + console.error(e.stack); + process.exit(1); + } }); after(function (callback) { - var i; - try { - for (i = 0; i < FILES_TO_DELETE.length; ++i) { - try { fs.unlinkSync(FILES_TO_DELETE[i]); } - catch (err) { /* normal */ } - } - for (i = 0; i < DIRS_TO_DELETE.length; ++i) { - try { fs.rmdirSync(DIRS_TO_DELETE[i]); } - catch (err) { /* normal */ } - } - SERVER.close(callback); - } catch (e) { - console.error(e.stack); - process.exit(1); + var i; + try { + for (i = 0; i < FILES_TO_DELETE.length; ++i) { + try { + fs.unlinkSync(FILES_TO_DELETE[i]); + } + catch (err) { /* normal */ + } + } + for (i = 0; i < DIRS_TO_DELETE.length; ++i) { + try { + fs.rmdirSync(DIRS_TO_DELETE[i]); + } + catch (err) { /* normal */ + } } + SERVER.close(callback); + } catch (e) { + console.error(e.stack); + process.exit(1); + } }); test('accept ok', function (t) { - CLIENT.get('/foo/bar', function (err, _, res) { - t.ifError(err); - t.equal(res.statusCode, 200); - t.end(); - }); + CLIENT.get('/foo/bar', function (err, _, res) { + t.ifError(err); + t.equal(res.statusCode, 200); + t.end(); + }); }); test('406', function (t) { - var opts = { - path: '/foo/bar', - headers: { - accept: 'foo/bar' - } - }; - CLIENT.get(opts, function (err, _, res) { - t.equal(res.statusCode, 406); - t.end(); - }); + var opts = { + path: '/foo/bar', + headers: { + accept: 'foo/bar' + } + }; + CLIENT.get(opts, function (err, _, res) { + t.equal(res.statusCode, 406); + t.end(); + }); }); test('authorization basic ok', function (t) { - var authz = 'Basic ' + new Buffer('user:secret').toString('base64'); - var opts = { - path: '/foo/bar', - headers: { - authorization: authz - } - }; - CLIENT.get(opts, function (err, _, res) { - t.equal(res.statusCode, 200); - t.end(); - }); + var authz = 'Basic ' + new Buffer('user:secret').toString('base64'); + var opts = { + path: '/foo/bar', + headers: { + authorization: authz + } + }; + CLIENT.get(opts, function (err, _, res) { + t.equal(res.statusCode, 200); + t.end(); + }); }); test('authorization basic invalid', function (t) { - var opts = { - path: '/foo/bar', - headers: { - authorization: 'Basic ' - } - }; - CLIENT.get(opts, function (err, _, res) { - t.equal(res.statusCode, 400); - t.end(); - }); + var opts = { + path: '/foo/bar', + headers: { + authorization: 'Basic ' + } + }; + CLIENT.get(opts, function (err, _, res) { + t.equal(res.statusCode, 400); + t.end(); + }); }); test('query ok', function (t) { - SERVER.get('/query/:id', function (req, res, next) { - t.equal(req.params.id, 'foo'); - t.equal(req.params.name, 'markc'); - t.equal(req.params.name, 'markc'); - res.send(); - next(); - }); + SERVER.get('/query/:id', function (req, res, next) { + t.equal(req.params.id, 'foo'); + t.equal(req.params.name, 'markc'); + t.equal(req.params.name, 'markc'); + res.send(); + next(); + }); - CLIENT.get('/query/foo?id=bar&name=markc', function (err, _, res) { - t.ifError(err); - t.equal(res.statusCode, 200); - t.end(); - }); + CLIENT.get('/query/foo?id=bar&name=markc', function (err, _, res) { + t.ifError(err); + t.equal(res.statusCode, 200); + t.end(); + }); }); test('GH-124 query ok no query string', function (t) { - SERVER.get('/query/:id', function (req, res, next) { - t.ok(req.getQuery()); - t.equal(typeof (req.getQuery()), 'object'); - res.send(); - next(); - }); + SERVER.get('/query/:id', function (req, res, next) { + t.ok(req.getQuery()); + t.equal(typeof (req.getQuery()), 'object'); + res.send(); + next(); + }); - CLIENT.get('/query/foo', function (err, _, res) { - t.ifError(err); - t.equal(res.statusCode, 200); - t.end(); - }); + CLIENT.get('/query/foo', function (err, _, res) { + t.ifError(err); + t.equal(res.statusCode, 200); + t.end(); + }); }); test('query object', function (t) { - SERVER.get('/query/:id', function (req, res, next) { - t.equal(req.params.id, 'foo'); - t.ok(req.params.name); - t.equal(req.params.name.first, 'mark'); - t.equal(req.query.name.last, 'cavage'); - res.send(); - next(); - }); + SERVER.get('/query/:id', function (req, res, next) { + t.equal(req.params.id, 'foo'); + t.ok(req.params.name); + t.equal(req.params.name.first, 'mark'); + t.equal(req.query.name.last, 'cavage'); + res.send(); + next(); + }); - var p = '/query/foo?name[first]=mark&name[last]=cavage'; - CLIENT.get(p, function (err, _, res) { - t.ifError(err); - t.equal(res.statusCode, 200); - t.end(); - }); + var p = '/query/foo?name[first]=mark&name[last]=cavage'; + CLIENT.get(p, function (err, _, res) { + t.ifError(err); + t.equal(res.statusCode, 200); + t.end(); + }); }); test('body url-encoded ok', function (t) { - SERVER.post('/bodyurl/:id', - restify.bodyParser(), - function (req, res, next) { - t.equal(req.params.id, 'foo'); - t.equal(req.params.name, 'markc'); - t.equal(req.params.phone, '(206) 555-1212'); - res.send(); - next(); - }); - - var opts = { - hostname: '127.0.0.1', - port: PORT, - path: '/bodyurl/foo?name=markc', - agent: false, - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded' - } - }; - var client = http.request(opts, function (res) { - t.equal(res.statusCode, 200); - t.end(); - }); + SERVER.post('/bodyurl/:id', + restify.bodyParser(), + function (req, res, next) { + t.equal(req.params.id, 'foo'); + t.equal(req.params.name, 'markc'); + t.equal(req.params.phone, '(206) 555-1212'); + res.send(); + next(); + }); + + var opts = { + hostname: '127.0.0.1', + port: PORT, + path: '/bodyurl/foo?name=markc', + agent: false, + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + } + }; + var client = http.request(opts, function (res) { + t.equal(res.statusCode, 200); + t.end(); + }); - client.write('phone=(206)%20555-1212&name=somethingelse'); - client.end(); + client.write('phone=(206)%20555-1212&name=somethingelse'); + client.end(); }); test('body url-encoded ok (no params)', function (t) { - SERVER.post('/bodyurl2/:id', - restify.bodyParser({ mapParams: false }), - function (req, res, next) { - t.equal(req.params.id, 'foo'); - t.equal(req.params.name, 'markc'); - t.notOk(req.params.phone); - t.equal(req.body.phone, '(206) 555-1212'); - res.send(); - next(); - }); - - var opts = { - hostname: '127.0.0.1', - port: PORT, - path: '/bodyurl2/foo?name=markc', - agent: false, - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded' - } - }; - var client = http.request(opts, function (res) { - t.equal(res.statusCode, 200); - t.end(); - }); - client.write('phone=(206)%20555-1212&name=somethingelse'); - client.end(); + SERVER.post('/bodyurl2/:id', + restify.bodyParser({ mapParams: false }), + function (req, res, next) { + t.equal(req.params.id, 'foo'); + t.equal(req.params.name, 'markc'); + t.notOk(req.params.phone); + t.equal(req.body.phone, '(206) 555-1212'); + res.send(); + next(); + }); + + var opts = { + hostname: '127.0.0.1', + port: PORT, + path: '/bodyurl2/foo?name=markc', + agent: false, + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + } + }; + var client = http.request(opts, function (res) { + t.equal(res.statusCode, 200); + t.end(); + }); + client.write('phone=(206)%20555-1212&name=somethingelse'); + client.end(); }); test('body json ok', function (t) { - SERVER.post('/body/:id', - restify.bodyParser(), - function (req, res, next) { - t.equal(req.params.id, 'foo'); - t.equal(req.params.name, 'markc'); - t.equal(req.params.phone, '(206) 555-1212'); - res.send(); - next(); - }); - - var obj = { - phone: '(206) 555-1212', - name: 'somethingelse' - }; - CLIENT.post('/body/foo?name=markc', obj, function (err, _, res) { - t.ifError(err); - t.equal(res.statusCode, 200); - t.end(); + SERVER.post('/body/:id', + restify.bodyParser(), + function (req, res, next) { + t.equal(req.params.id, 'foo'); + t.equal(req.params.name, 'markc'); + t.equal(req.params.phone, '(206) 555-1212'); + res.send(); + next(); }); + var obj = { + phone: '(206) 555-1212', + name: 'somethingelse' + }; + CLIENT.post('/body/foo?name=markc', obj, function (err, _, res) { + t.ifError(err); + t.equal(res.statusCode, 200); + t.end(); + }); + }); test('body json ok (no params)', function (t) { - SERVER.post('/body/:id', - restify.bodyParser({ mapParams: false }), - function (req, res, next) { - t.equal(req.params.id, 'foo'); - t.equal(req.params.name, 'markc'); - t.notOk(req.params.phone); - t.equal(req.body.phone, '(206) 555-1212'); - res.send(); - next(); - }); - - var obj = { - phone: '(206) 555-1212', - name: 'somethingelse' - }; - CLIENT.post('/body/foo?name=markc', obj, function (err, _, res) { - t.ifError(err); - t.equal(res.statusCode, 200); - t.end(); + SERVER.post('/body/:id', + restify.bodyParser({ mapParams: false }), + function (req, res, next) { + t.equal(req.params.id, 'foo'); + t.equal(req.params.name, 'markc'); + t.notOk(req.params.phone); + t.equal(req.body.phone, '(206) 555-1212'); + res.send(); + next(); }); + + var obj = { + phone: '(206) 555-1212', + name: 'somethingelse' + }; + CLIENT.post('/body/foo?name=markc', obj, function (err, _, res) { + t.ifError(err); + t.equal(res.statusCode, 200); + t.end(); + }); }); test('GH-318 get request with body (default)', function (t) { - SERVER.get('/getWithoutBody', - restify.bodyParser({ mapParams: true }), - function (req, res, next) { - t.notEqual(req.params.foo, 'bar'); - res.send(); - next(); - }); - - var request = 'GET /getWithoutBody HTTP/1.1\r\n' + - 'Content-Type: application/json\r\n' + - 'Content-Length: 13\r\n' + - '\r\n' + - '{"foo":"bar"}'; - - var client = net.connect({host: '127.0.0.1', port: PORT}, function () { - client.write(request); - }); - client.once('data', function (data) { - client.end(); - }); - client.once('end', function () { - t.end(); - }); + SERVER.get('/getWithoutBody', + restify.bodyParser({ mapParams: true }), + function (req, res, next) { + t.notEqual(req.params.foo, 'bar'); + res.send(); + next(); + }); + + var request = 'GET /getWithoutBody HTTP/1.1\r\n' + + 'Content-Type: application/json\r\n' + + 'Content-Length: 13\r\n' + + '\r\n' + + '{"foo":"bar"}'; + + var client = net.connect({host: '127.0.0.1', port: PORT}, function () { + client.write(request); + }); + client.once('data', function (data) { + client.end(); + }); + client.once('end', function () { + t.end(); + }); }); test('GH-318 get request with body (requestBodyOnGet=true)', function (t) { - SERVER.get('/getWithBody', - restify.bodyParser({ - mapParams: true, - requestBodyOnGet: true - }), function (req, res, next) { - t.equal(req.params.foo, 'bar'); - res.send(); - next(); - }); - - var request = 'GET /getWithBody HTTP/1.1\r\n' + - 'Content-Type: application/json\r\n' + - 'Content-Length: 13\r\n' + - '\r\n' + - '{"foo":"bar"}'; - var client = net.connect({host: '127.0.0.1', port: PORT}, function () { - client.write(request); - }); - - client.once('data', function (data) { - client.end(); - }); + SERVER.get('/getWithBody', + restify.bodyParser({ + mapParams: true, + requestBodyOnGet: true + }), function (req, res, next) { + t.equal(req.params.foo, 'bar'); + res.send(); + next(); + }); + + var request = 'GET /getWithBody HTTP/1.1\r\n' + + 'Content-Type: application/json\r\n' + + 'Content-Length: 13\r\n' + + '\r\n' + + '{"foo":"bar"}'; + var client = net.connect({host: '127.0.0.1', port: PORT}, function () { + client.write(request); + }); + + client.once('data', function (data) { + client.end(); + }); - client.once('end', function () { - t.end(); - }); + client.once('end', function () { + t.end(); + }); }); test('GH-111 JSON Parser not right for arrays', function (t) { - SERVER.post('/gh111', - restify.bodyParser(), - function (req, res, next) { - t.ok(Array.isArray(req.params)); - t.equal(req.params[0], 'foo'); - t.equal(req.params[1], 'bar'); - res.send(); - next(); - }); - - var obj = ['foo', 'bar']; - CLIENT.post('/gh111', obj, function (err, _, res) { - t.ifError(err); - t.end(); - t.equal(res.statusCode, 200); + SERVER.post('/gh111', + restify.bodyParser(), + function (req, res, next) { + t.ok(Array.isArray(req.params)); + t.equal(req.params[0], 'foo'); + t.equal(req.params[1], 'bar'); + res.send(); + next(); }); + + var obj = ['foo', 'bar']; + CLIENT.post('/gh111', obj, function (err, _, res) { + t.ifError(err); + t.end(); + t.equal(res.statusCode, 200); + }); }); test('GH-279 more JSON Arrays', function (t) { - function respond(req, res, next) { - t.ok(Array.isArray(req.params)); - t.equal(req.params[0].id, '123654'); - t.ok(req.params[0].name, 'mimi'); - t.ok(req.params[1].id, '987654'); - t.ok(req.params[1].name, 'pijama'); - res.send(200); - next(); + function respond(req, res, next) { + t.ok(Array.isArray(req.params)); + t.equal(req.params[0].id, '123654'); + t.ok(req.params[0].name, 'mimi'); + t.ok(req.params[1].id, '987654'); + t.ok(req.params[1].name, 'pijama'); + res.send(200); + next(); + } + + SERVER.post('/gh279', restify.jsonBodyParser(), respond); + + var obj = [ + { + 'id': '123654', + 'name': 'mimi' + }, + { + id: '987654', + name: 'pijama' } - - SERVER.post('/gh279', restify.jsonBodyParser(), respond); - - var obj = [ { - 'id': '123654', - 'name': 'mimi' - }, { - id: '987654', - name: 'pijama' - }]; - CLIENT.post('/gh279', obj, function (err, _, res) { - t.ifError(err); - t.end(); - t.equal(res.statusCode, 200); - }); + ]; + CLIENT.post('/gh279', obj, function (err, _, res) { + t.ifError(err); + t.end(); + t.equal(res.statusCode, 200); + }); }); test('date expired', function (t) { - var opts = { - path: '/foo/bar', - headers: { - date: 'Tue, 15 Nov 1994 08:12:31 GMT' - } - }; - CLIENT.get(opts, function (err, _, res) { - t.ok(err); - t.ok(/Date header .+ is too old/.test(err.message)); - t.equal(res.statusCode, 400); - t.end(); - }); + var opts = { + path: '/foo/bar', + headers: { + date: 'Tue, 15 Nov 1994 08:12:31 GMT' + } + }; + CLIENT.get(opts, function (err, _, res) { + t.ok(err); + t.ok(/Date header .+ is too old/.test(err.message)); + t.equal(res.statusCode, 400); + t.end(); + }); }); test('Conditional Request with correct Etag and headers', function (t) { - SERVER.get('/etag/:id', - function (req, res, next) { - res.etag = 'testETag'; - next(); - }, - restify.conditionalRequest(), - function (req, res, next) { - res.body = 'testing 304'; - res.send(); - next(); - }); - - var opts = { - path: '/etag/foo', - headers: { - 'If-Match': 'testETag', - 'If-None-Match': 'testETag' - } - }; - CLIENT.get(opts, function (err, _, res) { - t.equal(res.statusCode, 304); - t.end(); - }); + SERVER.get('/etag/:id', + function (req, res, next) { + res.etag = 'testETag'; + next(); + }, + restify.conditionalRequest(), + function (req, res, next) { + res.body = 'testing 304'; + res.send(); + next(); + }); + + var opts = { + path: '/etag/foo', + headers: { + 'If-Match': 'testETag', + 'If-None-Match': 'testETag' + } + }; + CLIENT.get(opts, function (err, _, res) { + t.equal(res.statusCode, 304); + t.end(); + }); }); test('Conditional Request with mismatched Etag and If-Match', function (t) { - SERVER.get('/etag/:id', - function setEtag(req, res, next) { - res.etag = 'testEtag'; - next(); - }, - restify.conditionalRequest(), - function respond(req, res, next) { - res.send(); - next(); - }); - - var opts = { - path: '/etag/foo', - headers: { - 'If-Match': 'testETag2' - } - }; - CLIENT.get(opts, function (err, _, res) { - t.equal(res.statusCode, 412); - t.end(); + SERVER.get('/etag/:id', + function setEtag(req, res, next) { + res.etag = 'testEtag'; + next(); + }, + restify.conditionalRequest(), + function respond(req, res, next) { + res.send(); + next(); }); + + var opts = { + path: '/etag/foo', + headers: { + 'If-Match': 'testETag2' + } + }; + CLIENT.get(opts, function (err, _, res) { + t.equal(res.statusCode, 412); + t.end(); + }); }); test('cdntl req If-Modified header & !modified content', function (t) { - var now = new Date(); - var yesterday = new Date(now.setDate(now.getDate() - 1)); - SERVER.get('/etag/:id', - function (req, res, next) { - res.header('Last-Modified', yesterday); - next(); - }, - restify.conditionalRequest(), - function (req, res, next) { - res.send('testing 304'); - next(); - }); - - var opts = { - path: '/etag/foo', - headers: { - 'If-Modified-Since': new Date() - } - }; - CLIENT.get(opts, function (err, _, res) { - t.equal(res.statusCode, 304); - t.end(); - }); + var now = new Date(); + var yesterday = new Date(now.setDate(now.getDate() - 1)); + SERVER.get('/etag/:id', + function (req, res, next) { + res.header('Last-Modified', yesterday); + next(); + }, + restify.conditionalRequest(), + function (req, res, next) { + res.send('testing 304'); + next(); + }); + + var opts = { + path: '/etag/foo', + headers: { + 'If-Modified-Since': new Date() + } + }; + CLIENT.get(opts, function (err, _, res) { + t.equal(res.statusCode, 304); + t.end(); + }); }); test('cdtl req If-Unmodified-Since header,modified content', function (t) { - var now = new Date(); - var yesterday = new Date(now.setDate(now.getDate() - 1)); - SERVER.get('/etag/:id', - function (req, res, next) { - res.header('Last-Modified', new Date()); - next(); - }, - restify.conditionalRequest(), - function (req, res, next) { - res.send('testing 412'); - next(); - }); - - var opts = { - path: '/etag/foo', - headers: { - 'If-Unmodified-Since': yesterday - } - }; - CLIENT.get(opts, function (err, _, res) { - t.equal(res.statusCode, 412); - t.end(); - }); + var now = new Date(); + var yesterday = new Date(now.setDate(now.getDate() - 1)); + SERVER.get('/etag/:id', + function (req, res, next) { + res.header('Last-Modified', new Date()); + next(); + }, + restify.conditionalRequest(), + function (req, res, next) { + res.send('testing 412'); + next(); + }); + + var opts = { + path: '/etag/foo', + headers: { + 'If-Unmodified-Since': yesterday + } + }; + CLIENT.get(opts, function (err, _, res) { + t.equal(res.statusCode, 412); + t.end(); + }); }); test('cdtl req valid headers, ahead time, unmodified OK', function (t) { - var now = new Date(); - var ahead = new Date(now.getTime() + 1000); - SERVER.get('/etag/:id', - function (req, res, next) { - res.header('Last-Modified', now); - next(); - }, - restify.conditionalRequest(), - function (req, res, next) { - res.send(); - next(); - }); - - var opts = { - path: '/etag/foo', - headers: { - 'If-Modified-Since': ahead - } - }; + var now = new Date(); + var ahead = new Date(now.getTime() + 1000); + SERVER.get('/etag/:id', + function (req, res, next) { + res.header('Last-Modified', now); + next(); + }, + restify.conditionalRequest(), + function (req, res, next) { + res.send(); + next(); + }); + + var opts = { + path: '/etag/foo', + headers: { + 'If-Modified-Since': ahead + } + }; - CLIENT.get(opts, function (err, _, res) { - t.equal(res.statusCode, 304); - t.end(); - }); + CLIENT.get(opts, function (err, _, res) { + t.equal(res.statusCode, 304); + t.end(); + }); }); test('cdtl req valid headers, ahead Timezone, modified content', function (t) { - var now = new Date(); - var ahead = new Date(now.setHours(now.getHours() + 5)); - SERVER.get('/etag/:id', - function (req, res, next) { - res.header('Last-Modified', now); - next(); - }, - restify.conditionalRequest(), - function (req, res, next) { - res.send(); - next(); - }); - - var opts = { - path: '/etag/foo', - headers: { - 'If-Unmodified-Since': ahead - } - }; - CLIENT.get(opts, function (err, _, res) { - t.equal(res.statusCode, 200); - t.end(); - }); + var now = new Date(); + var ahead = new Date(now.setHours(now.getHours() + 5)); + SERVER.get('/etag/:id', + function (req, res, next) { + res.header('Last-Modified', now); + next(); + }, + restify.conditionalRequest(), + function (req, res, next) { + res.send(); + next(); + }); + + var opts = { + path: '/etag/foo', + headers: { + 'If-Unmodified-Since': ahead + } + }; + CLIENT.get(opts, function (err, _, res) { + t.equal(res.statusCode, 200); + t.end(); + }); }); test('Conditional PUT with matched Etag and headers', function (t) { - SERVER.put('/etag/:id', - function (req, res, next) { - res.etag = 'testETag'; - next(); - }, - restify.conditionalRequest(), - function (req, res, next) { - res.send(); - next(); - }); - - var opts = { - path: '/etag/foo', - headers: { - 'If-Match': 'testETag', - 'If-None-Match': 'testETag' - } - }; - CLIENT.put(opts, {}, function (err, _, res) { - t.equal(res.statusCode, 412); - t.end(); - }); + SERVER.put('/etag/:id', + function (req, res, next) { + res.etag = 'testETag'; + next(); + }, + restify.conditionalRequest(), + function (req, res, next) { + res.send(); + next(); + }); + + var opts = { + path: '/etag/foo', + headers: { + 'If-Match': 'testETag', + 'If-None-Match': 'testETag' + } + }; + CLIENT.put(opts, {}, function (err, _, res) { + t.equal(res.statusCode, 412); + t.end(); + }); }); test('gzip response', function (t) { - SERVER.get('/gzip/:id', - restify.gzipResponse(), - function (req, res, next) { - res.send({ - hello: 'world' - }); - next(); - }); - - var opts = { - path: '/gzip/foo', - headers: { - 'Accept-Encoding': 'gzip' - } - }; - CLIENT.get(opts, function (err, _, res, obj) { - t.ifError(err); - t.deepEqual({hello: 'world'}, obj); - t.end(); - }); + SERVER.get('/gzip/:id', + restify.gzipResponse(), + function (req, res, next) { + res.send({ + hello: 'world' + }); + next(); + }); + + var opts = { + path: '/gzip/foo', + headers: { + 'Accept-Encoding': 'gzip' + } + }; + CLIENT.get(opts, function (err, _, res, obj) { + t.ifError(err); + t.deepEqual({hello: 'world'}, obj); + t.end(); + }); }); test('gzip large response', function (t) { - var testResponseSize = 65536 * 3; - var TestStream = function () { - this.readable = true; - this.sentSize = 0; - this.totalSize = testResponseSize; - this.interval = null; - }; - require('util').inherits(TestStream, require('stream')); - TestStream.prototype.resume = function () { - var self = this; - if (!this.interval) { - this.interval = setInterval(function () { - var chunkSize = Math.min(self.totalSize - - self.sentSize, 65536); - if (chunkSize > 0) { - var chunk = new Array(chunkSize + 1); - chunk = chunk.join('a'); - self.emit('data', chunk); - self.sentSize += chunkSize; - } else { - self.emit('data', '"}'); - self.emit('end'); - self.pause(); - } - }, 1); + var testResponseSize = 65536 * 3; + var TestStream = function () { + this.readable = true; + this.sentSize = 0; + this.totalSize = testResponseSize; + this.interval = null; + }; + require('util').inherits(TestStream, require('stream')); + TestStream.prototype.resume = function () { + var self = this; + if (!this.interval) { + this.interval = setInterval(function () { + var chunkSize = Math.min(self.totalSize - + self.sentSize, 65536); + if (chunkSize > 0) { + var chunk = new Array(chunkSize + 1); + chunk = chunk.join('a'); + self.emit('data', chunk); + self.sentSize += chunkSize; + } else { + self.emit('data', '"}'); + self.emit('end'); + self.pause(); } - }; + }, 1); + } + }; - TestStream.prototype.pause = function () { - clearInterval(this.interval); - this.interval = null; - }; + TestStream.prototype.pause = function () { + clearInterval(this.interval); + this.interval = null; + }; - var bodyStream = new TestStream(); - - SERVER.get('/gzip/:id', - restify.gzipResponse(), - function (req, res, next) { - bodyStream.resume(); - res.write('{"foo":"'); - bodyStream.pipe(res); - next(); - }); - - var opts = { - path: '/gzip/foo', - headers: { - 'Accept-Encoding': 'gzip' - } - }; - CLIENT.get(opts, function (err, _, res, obj) { - t.ifError(err); - var expectedResponse = { - foo: new Array(testResponseSize + 1).join('a') - }; - t.deepEqual(expectedResponse, obj); - t.end(); + var bodyStream = new TestStream(); + + SERVER.get('/gzip/:id', + restify.gzipResponse(), + function (req, res, next) { + bodyStream.resume(); + res.write('{"foo":"'); + bodyStream.pipe(res); + next(); }); + + var opts = { + path: '/gzip/foo', + headers: { + 'Accept-Encoding': 'gzip' + } + }; + CLIENT.get(opts, function (err, _, res, obj) { + t.ifError(err); + var expectedResponse = { + foo: new Array(testResponseSize + 1).join('a') + }; + t.deepEqual(expectedResponse, obj); + t.end(); + }); }); test('gzip body json ok', function (t) { - SERVER.post('/body/:id', - restify.bodyParser(), - function (req, res, next) { - t.equal(req.params.id, 'foo'); - t.equal(req.params.name, 'markc'); - t.equal(req.params.phone, '(206) 555-1212'); - res.send(); - next(); - }); - - var obj = { - phone: '(206) 555-1212', - name: 'somethingelse' - }; - CLIENT.gzip = {}; - CLIENT.post('/body/foo?name=markc', obj, function (err, _, res) { - t.ifError(err); - t.ok(res); - if (res) - t.equal(res.statusCode, 200); - t.end(); - }); + SERVER.post('/body/:id', + restify.bodyParser(), + function (req, res, next) { + t.equal(req.params.id, 'foo'); + t.equal(req.params.name, 'markc'); + t.equal(req.params.phone, '(206) 555-1212'); + res.send(); + next(); + }); + + var obj = { + phone: '(206) 555-1212', + name: 'somethingelse' + }; + CLIENT.gzip = {}; + CLIENT.post('/body/foo?name=markc', obj, function (err, _, res) { + t.ifError(err); + t.ok(res); + if (res) + t.equal(res.statusCode, 200); + t.end(); + }); }); function serveStaticTest(t, testDefault, tmpDir, regex) { - var staticContent = '{"content": "abcdefg"}'; - var staticObj = JSON.parse(staticContent); - var testDir = 'public'; - var testFileName = 'index.json'; - var routeName = 'GET wildcard'; - var tmpPath = path.join(process.cwd(), tmpDir); - fs.mkdir(tmpPath, function (err) { - DIRS_TO_DELETE.push(tmpPath); - var folderPath = path.join(tmpPath, testDir); - - fs.mkdir(folderPath, function (err2) { - t.ifError(err2); - - DIRS_TO_DELETE.push(folderPath); - var file = path.join(folderPath, testFileName); - - fs.writeFile(file, staticContent, function (err3) { - t.ifError(err3); - FILES_TO_DELETE.push(file); - var opts = { directory: tmpPath }; - if (testDefault) { - opts.defaultFile = testFileName; - routeName += ' with default'; - } - var re = regex || - new RegExp('/' + testDir + '/?.*'); - - SERVER.get({ - path: re, - name: routeName - }, restify.serveStatic(opts)); - - var p = '/' + testDir + '/' + testFileName; - CLIENT.get(p, function (err4, req, res, obj) { - t.ifError(err4); - t.equal(res.headers['cache-control'], - 'public, max-age=3600'); - t.deepEqual(obj, staticObj); - t.end(); - }); - }); + var staticContent = '{"content": "abcdefg"}'; + var staticObj = JSON.parse(staticContent); + var testDir = 'public'; + var testFileName = 'index.json'; + var routeName = 'GET wildcard'; + var tmpPath = path.join(process.cwd(), tmpDir); + fs.mkdir(tmpPath, function (err) { + DIRS_TO_DELETE.push(tmpPath); + var folderPath = path.join(tmpPath, testDir); + + fs.mkdir(folderPath, function (err2) { + t.ifError(err2); + + DIRS_TO_DELETE.push(folderPath); + var file = path.join(folderPath, testFileName); + + fs.writeFile(file, staticContent, function (err3) { + t.ifError(err3); + FILES_TO_DELETE.push(file); + var opts = { directory: tmpPath }; + if (testDefault) { + opts.defaultFile = testFileName; + routeName += ' with default'; + } + var re = regex || + new RegExp('/' + testDir + '/?.*'); + + SERVER.get({ + path: re, + name: routeName + }, restify.serveStatic(opts)); + + var p = '/' + testDir + '/' + testFileName; + CLIENT.get(p, function (err4, req, res, obj) { + t.ifError(err4); + t.equal(res.headers['cache-control'], + 'public, max-age=3600'); + t.deepEqual(obj, staticObj); + t.end(); }); + }); }); + }); } test('static serves static files', function (t) { - serveStaticTest(t, false, '.tmp'); + serveStaticTest(t, false, '.tmp'); }); test('static serves static files in nested folders', function (t) { - serveStaticTest(t, false, '.tmp/folder'); + serveStaticTest(t, false, '.tmp/folder'); }); test('static serves static files in with a root regex', function (t) { - serveStaticTest(t, false, '.tmp', new RegExp('/.*')); + serveStaticTest(t, false, '.tmp', new RegExp('/.*')); }); test('static serves static files with a root, !greedy, regex', function (t) { - serveStaticTest(t, false, '.tmp', new RegExp('/?.*')); + serveStaticTest(t, false, '.tmp', new RegExp('/?.*')); }); test('static serves default file', function (t) { - serveStaticTest(t, true, '.tmp'); + serveStaticTest(t, true, '.tmp'); }); test('GH-379 static serves file with parentheses in path', function (t) { - serveStaticTest(t, false, '.(tmp)'); + serveStaticTest(t, false, '.(tmp)'); }); diff --git a/test/server.test.js b/test/server.test.js index a3ae79e81..6129f4581 100644 --- a/test/server.test.js +++ b/test/server.test.js @@ -12,11 +12,10 @@ var RestError = require('../lib/errors').RestError; var restify = require('../lib'); if (require.cache[__dirname + '/lib/helper.js']) - delete require.cache[__dirname + '/lib/helper.js']; + delete require.cache[__dirname + '/lib/helper.js']; var helper = require('./lib/helper.js'); - ///--- Globals var after = helper.after; @@ -28,1485 +27,1484 @@ var CLIENT; var SERVER; - ///--- Tests before(function (cb) { - try { - SERVER = restify.createServer({ - dtrace: helper.dtrace, - log: helper.getLog('server'), - version: ['2.0.0', '0.5.4', '1.4.3'] - }); - SERVER.listen(PORT, '127.0.0.1', function () { - PORT = SERVER.address().port; - CLIENT = restify.createJsonClient({ - url: 'http://127.0.0.1:' + PORT, - dtrace: helper.dtrace, - retry: false - }); - - cb(); - }); - } catch (e) { - console.error(e.stack); - process.exit(1); - } + try { + SERVER = restify.createServer({ + dtrace: helper.dtrace, + log: helper.getLog('server'), + version: ['2.0.0', '0.5.4', '1.4.3'] + }); + SERVER.listen(PORT, '127.0.0.1', function () { + PORT = SERVER.address().port; + CLIENT = restify.createJsonClient({ + url: 'http://127.0.0.1:' + PORT, + dtrace: helper.dtrace, + retry: false + }); + + cb(); + }); + } catch (e) { + console.error(e.stack); + process.exit(1); + } }); after(function (cb) { - try { - CLIENT.close(); - SERVER.close(function () { - CLIENT = null; - SERVER = null; - cb(); - }); - } catch (e) { - console.error(e.stack); - process.exit(1); - } + try { + CLIENT.close(); + SERVER.close(function () { + CLIENT = null; + SERVER = null; + cb(); + }); + } catch (e) { + console.error(e.stack); + process.exit(1); + } }); test('listen and close (port only)', function (t) { - var server = restify.createServer(); - server.listen(0, function () { - server.close(function () { - t.end(); - }); + var server = restify.createServer(); + server.listen(0, function () { + server.close(function () { + t.end(); }); + }); }); test('listen and close (port only) w/ port number as string', function (t) { - var server = restify.createServer(); - server.listen(String(0), function () { - server.close(function () { - t.end(); - }); + var server = restify.createServer(); + server.listen(String(0), function () { + server.close(function () { + t.end(); }); + }); }); test('listen and close (socketPath)', function (t) { - var server = restify.createServer(); - server.listen('/tmp/.' + uuid(), function () { - server.close(function () { - t.end(); - }); + var server = restify.createServer(); + server.listen('/tmp/.' + uuid(), function () { + server.close(function () { + t.end(); }); + }); }); test('get (path only)', function (t) { - var r = SERVER.get('/foo/:id', function echoId(req, res, next) { - t.ok(req.params); - t.equal(req.params.id, 'bar'); - res.send(); - next(); - }); - - var count = 0; - SERVER.once('after', function (req, res, route) { - t.ok(req); - t.ok(res); - t.equal(r, route.name); - if (++count === 2) - t.end(); - }); - - CLIENT.get('/foo/bar', function (err, _, res) { - t.ifError(err); - t.equal(res.statusCode, 200); - if (++count === 2) - t.end(); - }); + var r = SERVER.get('/foo/:id', function echoId(req, res, next) { + t.ok(req.params); + t.equal(req.params.id, 'bar'); + res.send(); + next(); + }); + + var count = 0; + SERVER.once('after', function (req, res, route) { + t.ok(req); + t.ok(res); + t.equal(r, route.name); + if (++count === 2) + t.end(); + }); + + CLIENT.get('/foo/bar', function (err, _, res) { + t.ifError(err); + t.equal(res.statusCode, 200); + if (++count === 2) + t.end(); + }); }); test('use + get (path only)', function (t) { - SERVER.use(function (req, res, next) { - next(); - }); - SERVER.get('/foo/:id', function tester(req, res, next) { - t.ok(req.params); - t.equal(req.params.id, 'bar'); - res.send(); - next(); - }); - - CLIENT.get('/foo/bar', function (err, _, res) { - t.ifError(err); - t.equal(res.statusCode, 200); - t.end(); - }); + SERVER.use(function (req, res, next) { + next(); + }); + SERVER.get('/foo/:id', function tester(req, res, next) { + t.ok(req.params); + t.equal(req.params.id, 'bar'); + res.send(); + next(); + }); + + CLIENT.get('/foo/bar', function (err, _, res) { + t.ifError(err); + t.equal(res.statusCode, 200); + t.end(); + }); }); test('rm', function (t) { - var route = SERVER.get('/foo/:id', function foosy(req, res, next) { - next(); - }); - - SERVER.get('/bar/:id', function barsy(req, res, next) { - t.ok(req.params); - t.equal(req.params.id, 'foo'); - res.send(); - next(); - }); - - t.ok(SERVER.rm(route)); - - CLIENT.get('/foo/bar', function (err, _, res) { - t.ok(err); - t.equal(res.statusCode, 404); - CLIENT.get('/bar/foo', function (err2, __, res2) { - t.ifError(err2); - t.equal(res2.statusCode, 200); - t.end(); - }); - }); + var route = SERVER.get('/foo/:id', function foosy(req, res, next) { + next(); + }); + + SERVER.get('/bar/:id', function barsy(req, res, next) { + t.ok(req.params); + t.equal(req.params.id, 'foo'); + res.send(); + next(); + }); + + t.ok(SERVER.rm(route)); + + CLIENT.get('/foo/bar', function (err, _, res) { + t.ok(err); + t.equal(res.statusCode, 404); + CLIENT.get('/bar/foo', function (err2, __, res2) { + t.ifError(err2); + t.equal(res2.statusCode, 200); + t.end(); + }); + }); }); test('use - throws TypeError on non function as argument', function (t) { - var err = assert.AssertionError('handler (function) is required'); - - t.throws(function () { - SERVER.use('/nonfn'); - }, err); - - t.throws(function () { - SERVER.use({an:'object'}); - }, err); - - t.throws(function () { - SERVER.use( - function good(req, res, next) { - next(); - }, - '/bad', - { - really:'bad' - }); - }, err); + var err = assert.AssertionError('handler (function) is required'); - t.end(); -}); + t.throws(function () { + SERVER.use('/nonfn'); + }, err); + t.throws(function () { + SERVER.use({an: 'object'}); + }, err); -test('405', function (t) { - SERVER.post('/foo/:id', function posty(req, res, next) { - t.ok(req.params); - t.equal(req.params.id, 'bar'); - res.send(); + t.throws(function () { + SERVER.use( + function good(req, res, next) { next(); - }); + }, + '/bad', + { + really: 'bad' + }); + }, err); + + t.end(); +}); - CLIENT.get('/foo/bar', function (err, _, res) { - t.ok(err); - t.equal(res.statusCode, 405); - t.equal(res.headers.allow, 'POST'); - t.end(); - }); + +test('405', function (t) { + SERVER.post('/foo/:id', function posty(req, res, next) { + t.ok(req.params); + t.equal(req.params.id, 'bar'); + res.send(); + next(); + }); + + CLIENT.get('/foo/bar', function (err, _, res) { + t.ok(err); + t.equal(res.statusCode, 405); + t.equal(res.headers.allow, 'POST'); + t.end(); + }); }); test('PUT ok', function (t) { - SERVER.put('/foo/:id', function tester(req, res, next) { - t.ok(req.params); - t.equal(req.params.id, 'bar'); - res.send(); - next(); - }); - - CLIENT.put('/foo/bar', {}, function (err, _, res) { - t.ifError(err); - t.equal(res.statusCode, 200); - t.end(); - }); + SERVER.put('/foo/:id', function tester(req, res, next) { + t.ok(req.params); + t.equal(req.params.id, 'bar'); + res.send(); + next(); + }); + + CLIENT.put('/foo/bar', {}, function (err, _, res) { + t.ifError(err); + t.equal(res.statusCode, 200); + t.end(); + }); }); test('PATCH ok', function (t) { - SERVER.patch('/foo/:id', function tester(req, res, next) { - t.ok(req.params); - t.equal(req.params.id, 'bar'); - res.send(); - next(); - }); - - var opts = { - hostname: 'localhost', - port: PORT, - path: '/foo/bar', - method: 'PATCH', - agent: false - }; - http.request(opts, function (res) { - t.equal(res.statusCode, 200); - res.on('end', function () { - t.end(); - }); - res.resume(); - }).end(); + SERVER.patch('/foo/:id', function tester(req, res, next) { + t.ok(req.params); + t.equal(req.params.id, 'bar'); + res.send(); + next(); + }); + + var opts = { + hostname: 'localhost', + port: PORT, + path: '/foo/bar', + method: 'PATCH', + agent: false + }; + http.request(opts, function (res) { + t.equal(res.statusCode, 200); + res.on('end', function () { + t.end(); + }); + res.resume(); + }).end(); }); - test('HEAD ok', function (t) { - SERVER.head('/foo/:id', function tester(req, res, next) { - t.ok(req.params); - t.equal(req.params.id, 'bar'); - res.send('hi there'); - next(); - }); - - var opts = { - hostname: 'localhost', - port: PORT, - path: '/foo/bar', - method: 'HEAD', - agent: false - }; - http.request(opts, function (res) { - t.equal(res.statusCode, 200); - res.on('data', function (chunk) { - t.fail('Data was sent on HEAD'); - }); - res.on('end', function () { - t.end(); - }); - }).end(); + SERVER.head('/foo/:id', function tester(req, res, next) { + t.ok(req.params); + t.equal(req.params.id, 'bar'); + res.send('hi there'); + next(); + }); + + var opts = { + hostname: 'localhost', + port: PORT, + path: '/foo/bar', + method: 'HEAD', + agent: false + }; + http.request(opts, function (res) { + t.equal(res.statusCode, 200); + res.on('data', function (chunk) { + t.fail('Data was sent on HEAD'); + }); + res.on('end', function () { + t.end(); + }); + }).end(); }); test('DELETE ok', function (t) { - SERVER.del('/foo/:id', function tester(req, res, next) { - t.ok(req.params); - t.equal(req.params.id, 'bar'); - res.send(204, 'hi there'); - next(); + SERVER.del('/foo/:id', function tester(req, res, next) { + t.ok(req.params); + t.equal(req.params.id, 'bar'); + res.send(204, 'hi there'); + next(); + }); + + var opts = { + hostname: 'localhost', + port: PORT, + path: '/foo/bar', + method: 'DELETE', + agent: false + }; + http.request(opts, function (res) { + t.equal(res.statusCode, 204); + res.on('data', function (chunk) { + t.fail('Data was sent on 204'); }); - - var opts = { - hostname: 'localhost', - port: PORT, - path: '/foo/bar', - method: 'DELETE', - agent: false - }; - http.request(opts, function (res) { - t.equal(res.statusCode, 204); - res.on('data', function (chunk) { - t.fail('Data was sent on 204'); - }); - t.end(); - }).end(); + t.end(); + }).end(); }); test('OPTIONS', function (t) { - ['get', 'post', 'put', 'del'].forEach(function (method) { - SERVER[method]('/foo/:id', function tester(req, res, next) { - t.ok(req.params); - t.equal(req.params.id, 'bar'); - res.send(); - next(); - }); - }); - - var opts = { - hostname: 'localhost', - port: PORT, - path: '*', - method: 'OPTIONS', - agent: false - }; - http.request(opts, function (res) { - t.equal(res.statusCode, 200); - t.end(); - }).end(); + ['get', 'post', 'put', 'del'].forEach(function (method) { + SERVER[method]('/foo/:id', function tester(req, res, next) { + t.ok(req.params); + t.equal(req.params.id, 'bar'); + res.send(); + next(); + }); + }); + + var opts = { + hostname: 'localhost', + port: PORT, + path: '*', + method: 'OPTIONS', + agent: false + }; + http.request(opts, function (res) { + t.equal(res.statusCode, 200); + t.end(); + }).end(); }); test('RegExp ok', function (t) { - SERVER.get(/\/foo/, function tester(req, res, next) { - res.send('hi there'); - next(); - }); - - CLIENT.get('/foo', function (err, _, res, obj) { - t.ifError(err); - t.equal(res.statusCode, 200); - t.equal(obj, 'hi there'); - t.end(); - }); + SERVER.get(/\/foo/, function tester(req, res, next) { + res.send('hi there'); + next(); + }); + + CLIENT.get('/foo', function (err, _, res, obj) { + t.ifError(err); + t.equal(res.statusCode, 200); + t.equal(obj, 'hi there'); + t.end(); + }); }); test('get (path and version ok)', function (t) { - SERVER.get({ - url: '/foo/:id', - version: '1.2.3' - }, function tester(req, res, next) { - t.ok(req.params); - t.equal(req.params.id, 'bar'); - res.send(); - next(); - }); - - var opts = { - path: '/foo/bar', - headers: { - 'accept-version': '~1.2' - } - }; - CLIENT.get(opts, function (err, _, res) { - t.ifError(err); - t.equal(res.statusCode, 200); - t.end(); - }); + SERVER.get({ + url: '/foo/:id', + version: '1.2.3' + }, function tester(req, res, next) { + t.ok(req.params); + t.equal(req.params.id, 'bar'); + res.send(); + next(); + }); + + var opts = { + path: '/foo/bar', + headers: { + 'accept-version': '~1.2' + } + }; + CLIENT.get(opts, function (err, _, res) { + t.ifError(err); + t.equal(res.statusCode, 200); + t.end(); + }); }); test('get (path and version not ok)', function (t) { - function respond(req, res, next) { - res.send(); - next(); + function respond(req, res, next) { + res.send(); + next(); + } + + SERVER.get({ url: '/foo/:id', version: '1.2.3' }, respond); + SERVER.get({ url: '/foo/:id', version: '3.2.1' }, respond); + + var opts = { + path: '/foo/bar', + headers: { + 'accept-version': '~2.1' } - - SERVER.get({ url: '/foo/:id', version: '1.2.3' }, respond); - SERVER.get({ url: '/foo/:id', version: '3.2.1' }, respond); - - var opts = { - path: '/foo/bar', - headers: { - 'accept-version': '~2.1' - } - }; - CLIENT.get(opts, function (err, _, res) { - t.ok(err); - t.equal(err.message, '~2.1 is not supported by GET /foo/bar'); - t.equal(res.statusCode, 400); - t.end(); - }); + }; + CLIENT.get(opts, function (err, _, res) { + t.ok(err); + t.equal(err.message, '~2.1 is not supported by GET /foo/bar'); + t.equal(res.statusCode, 400); + t.end(); + }); }); test('GH-56 streaming with filed (download)', function (t) { - SERVER.get('/', function tester(req, res, next) { - filed(__filename).pipe(res); - }); - - var opts = { - hostname: 'localhost', - port: PORT, - path: '/', - method: 'GET', - agent: false - }; - http.request(opts, function (res) { - t.equal(res.statusCode, 200); - var body = ''; - res.setEncoding('utf8'); - res.on('data', function (chunk) { - body += chunk; - }); - res.on('end', function () { - t.ok(body.length > 0); - t.end(); - }); - }).end(); + SERVER.get('/', function tester(req, res, next) { + filed(__filename).pipe(res); + }); + + var opts = { + hostname: 'localhost', + port: PORT, + path: '/', + method: 'GET', + agent: false + }; + http.request(opts, function (res) { + t.equal(res.statusCode, 200); + var body = ''; + res.setEncoding('utf8'); + res.on('data', function (chunk) { + body += chunk; + }); + res.on('end', function () { + t.ok(body.length > 0); + t.end(); + }); + }).end(); }); test('GH-59 Query params with / result in a 404', function (t) { - SERVER.get('/', function tester(req, res, next) { - res.send('hello world'); - next(); - }); - - CLIENT.get('/?foo=bar/foo', function (err, _, res, obj) { - t.ifError(err); - t.equal(res.statusCode, 200); - t.equal(obj, 'hello world'); - t.end(); - }); + SERVER.get('/', function tester(req, res, next) { + res.send('hello world'); + next(); + }); + + CLIENT.get('/?foo=bar/foo', function (err, _, res, obj) { + t.ifError(err); + t.equal(res.statusCode, 200); + t.equal(obj, 'hello world'); + t.end(); + }); }); test('GH-63 res.send 204 is sending a body', function (t) { - SERVER.del('/hello/:name', function tester(req, res, next) { - res.send(204); - next(); - }); - - var opts = { - hostname: 'localhost', - port: PORT, - path: '/hello/mark', - method: 'DELETE', - agent: false, - headers: { - accept: 'text/plain' - } - }; + SERVER.del('/hello/:name', function tester(req, res, next) { + res.send(204); + next(); + }); + + var opts = { + hostname: 'localhost', + port: PORT, + path: '/hello/mark', + method: 'DELETE', + agent: false, + headers: { + accept: 'text/plain' + } + }; - http.request(opts, function (res) { - t.equal(res.statusCode, 204); - var body = ''; - res.setEncoding('utf8'); - res.on('data', function (chunk) { - body += chunk; - }); - res.on('end', function () { - t.notOk(body); - t.end(); - }); - }).end(); + http.request(opts, function (res) { + t.equal(res.statusCode, 204); + var body = ''; + res.setEncoding('utf8'); + res.on('data', function (chunk) { + body += chunk; + }); + res.on('end', function () { + t.notOk(body); + t.end(); + }); + }).end(); }); test('GH-64 prerouting chain', function (t) { - SERVER.pre(function (req, res, next) { - req.log.debug('testing log is set'); - req.headers.accept = 'application/json'; - next(); - }); - - SERVER.get('/hello/:name', function tester(req, res, next) { - res.send(req.params.name); - next(); - }); - - var opts = { - hostname: 'localhost', - port: PORT, - path: '/hello/mark', - method: 'GET', - agent: false, - headers: { - accept: 'text/plain' - } - }; - http.request(opts, function (res) { - t.equal(res.statusCode, 200); - var body = ''; - res.setEncoding('utf8'); - res.on('data', function (chunk) { - body += chunk; - }); - res.on('end', function () { - t.equal(body, '\"mark\"'); - t.end(); - }); - }).end(); + SERVER.pre(function (req, res, next) { + req.log.debug('testing log is set'); + req.headers.accept = 'application/json'; + next(); + }); + + SERVER.get('/hello/:name', function tester(req, res, next) { + res.send(req.params.name); + next(); + }); + + var opts = { + hostname: 'localhost', + port: PORT, + path: '/hello/mark', + method: 'GET', + agent: false, + headers: { + accept: 'text/plain' + } + }; + http.request(opts, function (res) { + t.equal(res.statusCode, 200); + var body = ''; + res.setEncoding('utf8'); + res.on('data', function (chunk) { + body += chunk; + }); + res.on('end', function () { + t.equal(body, '\"mark\"'); + t.end(); + }); + }).end(); }); test('GH-64 prerouting chain with error', function (t) { - SERVER.pre(function (req, res, next) { - next(new RestError({ - statusCode: 400, - restCode: 'BadRequest' - }, 'screw you client')); - }); - - SERVER.get('/hello/:name', function tester(req, res, next) { - res.send(req.params.name); - next(); - }); - - CLIENT.get('/hello/mark', function (err, _, res) { - t.ok(err); - t.equal(res.statusCode, 400); - t.end(); - }); + SERVER.pre(function (req, res, next) { + next(new RestError({ + statusCode: 400, + restCode: 'BadRequest' + }, 'screw you client')); + }); + + SERVER.get('/hello/:name', function tester(req, res, next) { + res.send(req.params.name); + next(); + }); + + CLIENT.get('/hello/mark', function (err, _, res) { + t.ok(err); + t.equal(res.statusCode, 400); + t.end(); + }); }); test('GH-67 extend access-control headers', function (t) { - SERVER.get('/hello/:name', function tester(req, res, next) { - res.header('Access-Control-Allow-Headers', - (res.header('Access-Control-Allow-Headers') + - ', If-Match, If-None-Match')); - - res.send(req.params.name); - next(); - }); - - CLIENT.get('/hello/mark', function (err, _, res) { - t.ifError(err); - t.equal(res.statusCode, 200); - t.ok(res.headers['access-control-allow-headers'] - .indexOf('If-Match')); - t.end(); - }); + SERVER.get('/hello/:name', function tester(req, res, next) { + res.header('Access-Control-Allow-Headers', + (res.header('Access-Control-Allow-Headers') + + ', If-Match, If-None-Match')); + + res.send(req.params.name); + next(); + }); + + CLIENT.get('/hello/mark', function (err, _, res) { + t.ifError(err); + t.equal(res.statusCode, 200); + t.ok(res.headers['access-control-allow-headers'] + .indexOf('If-Match')); + t.end(); + }); }); test('GH-77 uncaughtException (default behavior)', function (t) { - SERVER.get('/', function (req, res, next) { - throw new Error('Catch me!'); - }); + SERVER.get('/', function (req, res, next) { + throw new Error('Catch me!'); + }); - CLIENT.get('/', function (err, _, res) { - t.ok(err); - t.equal(res.statusCode, 500); - t.end(); - }); + CLIENT.get('/', function (err, _, res) { + t.ok(err); + t.equal(res.statusCode, 500); + t.end(); + }); }); test('GH-77 uncaughtException (with custom handler)', function (t) { - SERVER.on('uncaughtException', function (req, res, route, err) { - res.send(204); - }); - SERVER.get('/', function (req, res, next) { - throw new Error('Catch me!'); - }); - - CLIENT.get('/', function (err, _, res) { - t.ifError(err); - t.equal(res.statusCode, 204); - t.end(); - }); + SERVER.on('uncaughtException', function (req, res, route, err) { + res.send(204); + }); + SERVER.get('/', function (req, res, next) { + throw new Error('Catch me!'); + }); + + CLIENT.get('/', function (err, _, res) { + t.ifError(err); + t.equal(res.statusCode, 204); + t.end(); + }); }); test('GH-97 malformed URI breaks server', function (t) { - SERVER.get('/echo/:name', function (req, res, next) { - res.send(200); - next(); - }); - - CLIENT.get('/echo/mark%', function (err, _, res) { - t.ok(err); - t.equal(res.statusCode, 400); - t.end(); - }); + SERVER.get('/echo/:name', function (req, res, next) { + res.send(200); + next(); + }); + + CLIENT.get('/echo/mark%', function (err, _, res) { + t.ok(err); + t.equal(res.statusCode, 400); + t.end(); + }); }); test('GH-109 RegExp flags not honored', function (t) { - SERVER.get(/\/echo\/(\w+)/i, function (req, res, next) { - res.send(200, req.params[0]); - next(); - }); - - CLIENT.get('/ECHO/mark', function (err, _, res, obj) { - t.ifError(err); - t.equal(res.statusCode, 200); - t.equal(obj, 'mark'); - t.end(); - }); + SERVER.get(/\/echo\/(\w+)/i, function (req, res, next) { + res.send(200, req.params[0]); + next(); + }); + + CLIENT.get('/ECHO/mark', function (err, _, res, obj) { + t.ifError(err); + t.equal(res.statusCode, 200); + t.equal(obj, 'mark'); + t.end(); + }); }); test('upload routing based on content-type ok', function (t) { - var opts = { - path: '/', - contentType: '*/json' - }; - SERVER.put(opts, function (req, res, next) { - res.send(204); - next(); - }); - - CLIENT.put('/', {foo: 'foo'}, function (err, _, res) { - t.ifError(err); - t.equal(res.statusCode, 204); - t.end(); - }); + var opts = { + path: '/', + contentType: '*/json' + }; + SERVER.put(opts, function (req, res, next) { + res.send(204); + next(); + }); + + CLIENT.put('/', {foo: 'foo'}, function (err, _, res) { + t.ifError(err); + t.equal(res.statusCode, 204); + t.end(); + }); }); test('upload routing based on content-type fail', function (t) { - var opts = { - path: '/', - contentType: 'text/*' - }; - SERVER.put(opts, function (req, res, next) { - res.send(204); - next(); - }); - - CLIENT.put('/', {foo: 'foo'}, function (err, _, res) { - t.ok(err); - t.equal(res.statusCode, 415); - t.end(); - }); + var opts = { + path: '/', + contentType: 'text/*' + }; + SERVER.put(opts, function (req, res, next) { + res.send(204); + next(); + }); + + CLIENT.put('/', {foo: 'foo'}, function (err, _, res) { + t.ok(err); + t.equal(res.statusCode, 415); + t.end(); + }); }); test('full response', function (t) { - SERVER.use(restify.fullResponse()); - SERVER.del('/bar/:id', function tester(req, res, next) { - res.send(); - next(); - }); - SERVER.get('/bar/:id', function tester2(req, res, next) { - t.ok(req.params); - t.equal(req.params.id, 'bar'); - res.send(); - next(); - }); - - CLIENT.get('/bar/bar', function (err, _, res) { - t.ifError(err); - t.equal(res.statusCode, 200); - var headers = res.headers; - t.ok(headers, 'headers ok'); - t.ok(headers['access-control-allow-origin']); - t.ok(headers['access-control-allow-headers']); - t.ok(headers['access-control-expose-headers']); - t.ok(headers['access-control-allow-methods']); - t.ok(headers.date); - t.ok(headers['request-id']); - t.ok(headers['response-time'] >= 0); - t.equal(headers.server, 'restify'); - t.equal(headers.connection, 'Keep-Alive'); - t.equal(headers['api-version'], '2.0.0'); - t.end(); - }); + SERVER.use(restify.fullResponse()); + SERVER.del('/bar/:id', function tester(req, res, next) { + res.send(); + next(); + }); + SERVER.get('/bar/:id', function tester2(req, res, next) { + t.ok(req.params); + t.equal(req.params.id, 'bar'); + res.send(); + next(); + }); + + CLIENT.get('/bar/bar', function (err, _, res) { + t.ifError(err); + t.equal(res.statusCode, 200); + var headers = res.headers; + t.ok(headers, 'headers ok'); + t.ok(headers['access-control-allow-origin']); + t.ok(headers['access-control-allow-headers']); + t.ok(headers['access-control-expose-headers']); + t.ok(headers['access-control-allow-methods']); + t.ok(headers.date); + t.ok(headers['request-id']); + t.ok(headers['response-time'] >= 0); + t.equal(headers.server, 'restify'); + t.equal(headers.connection, 'Keep-Alive'); + t.equal(headers['api-version'], '2.0.0'); + t.end(); + }); }); test('GH-149 limit request body size', function (t) { - SERVER.use(restify.bodyParser({maxBodySize: 1024})); - - SERVER.post('/', function (req, res, next) { - res.send(200, {length: req.body.length}); - next(); - }); - - var opts = { - hostname: '127.0.0.1', - port: PORT, - path: '/', - method: 'POST', - agent: false, - headers: { - 'accept': 'application/json', - 'content-type': 'application/x-www-form-urlencoded', - 'transfer-encoding': 'chunked' - } - }; - var client = http.request(opts, function (res) { - t.equal(res.statusCode, 413); - res.once('end', t.end.bind(t)); - res.resume(); - }); - client.write(new Array(1028).join('x')); - client.end(); + SERVER.use(restify.bodyParser({maxBodySize: 1024})); + + SERVER.post('/', function (req, res, next) { + res.send(200, {length: req.body.length}); + next(); + }); + + var opts = { + hostname: '127.0.0.1', + port: PORT, + path: '/', + method: 'POST', + agent: false, + headers: { + 'accept': 'application/json', + 'content-type': 'application/x-www-form-urlencoded', + 'transfer-encoding': 'chunked' + } + }; + var client = http.request(opts, function (res) { + t.equal(res.statusCode, 413); + res.once('end', t.end.bind(t)); + res.resume(); + }); + client.write(new Array(1028).join('x')); + client.end(); }); test('GH-149 limit request body size (json)', function (t) { - SERVER.use(restify.bodyParser({maxBodySize: 1024})); - - SERVER.post('/', function (req, res, next) { - res.send(200, {length: req.body.length}); - next(); - }); - - var opts = { - hostname: '127.0.0.1', - port: PORT, - path: '/', - method: 'POST', - agent: false, - headers: { - 'accept': 'application/json', - 'content-type': 'application/json', - 'transfer-encoding': 'chunked' - } - }; - var client = http.request(opts, function (res) { - t.equal(res.statusCode, 413); - res.once('end', t.end.bind(t)); - res.resume(); - }); - client.write('{"a":[' + new Array(512).join('1,') + '0]}'); - client.end(); + SERVER.use(restify.bodyParser({maxBodySize: 1024})); + + SERVER.post('/', function (req, res, next) { + res.send(200, {length: req.body.length}); + next(); + }); + + var opts = { + hostname: '127.0.0.1', + port: PORT, + path: '/', + method: 'POST', + agent: false, + headers: { + 'accept': 'application/json', + 'content-type': 'application/json', + 'transfer-encoding': 'chunked' + } + }; + var client = http.request(opts, function (res) { + t.equal(res.statusCode, 413); + res.once('end', t.end.bind(t)); + res.resume(); + }); + client.write('{"a":[' + new Array(512).join('1,') + '0]}'); + client.end(); }); test('path+flags ok', function (t) { - SERVER.get({path: '/foo', flags: 'i'}, function (req, res, next) { - res.send('hi'); - next(); - }); - - CLIENT.get('/FoO', function (err, _, res, obj) { - t.ifError(err); - t.equal(res.statusCode, 200); - t.equal(obj, 'hi'); - t.end(); - }); + SERVER.get({path: '/foo', flags: 'i'}, function (req, res, next) { + res.send('hi'); + next(); + }); + + CLIENT.get('/FoO', function (err, _, res, obj) { + t.ifError(err); + t.equal(res.statusCode, 200); + t.equal(obj, 'hi'); + t.end(); + }); }); test('test matches params with custom regex', function (t) { - var Router = require('../lib/router'); - var router = new Router({ - log: helper.getLog() - }); - t.ok(router); - router.mount({ - method: 'GET', - name: 'test', - url: '/foo/:bar', - urlParamPattern: '[a-zA-Z0-9-_~%!;@=+\\$\\*\\.]+' - }); + var Router = require('../lib/router'); + var router = new Router({ + log: helper.getLog() + }); + t.ok(router); + router.mount({ + method: 'GET', + name: 'test', + url: '/foo/:bar', + urlParamPattern: '[a-zA-Z0-9-_~%!;@=+\\$\\*\\.]+' + }); + + var count = 0; + var done = 0; + + function find(p, exp) { + count++; + var obj = { + headers: {}, + method: 'GET', + contentType: function () { + }, + path: function () { + return (p); + }, + version: function () { + return ('*'); + }, + url: p + }; - var count = 0; - var done = 0; - function find(p, exp) { - count++; - var obj = { - headers: {}, - method: 'GET', - contentType: function () {}, - path: function () { - return (p); - }, - version: function () { - return ('*'); - }, - url: p - }; - - process.nextTick(function () { - router.find(obj, {}, function (err, r, ctx) { - if (exp) { - t.ifError(err); - t.ok(r); - t.ok(ctx); - t.deepEqual(ctx, {bar: exp}); - } else { - t.ok(err); - } - if (++done === count) - t.end(); - }); - }); + process.nextTick(function () { + router.find(obj, {}, function (err, r, ctx) { + if (exp) { + t.ifError(err); + t.ok(r); + t.ok(ctx); + t.deepEqual(ctx, {bar: exp}); + } else { + t.ok(err); + } + if (++done === count) + t.end(); + }); + }); - } + } - find('/foo/a%40b.com', 'a@b.com'); - find('/foo/a@b.com', 'a@b.com'); - find('/foo/a*b.com', 'a*b.com'); - find('/foo/a%40b.com/bar', false); + find('/foo/a%40b.com', 'a@b.com'); + find('/foo/a@b.com', 'a@b.com'); + find('/foo/a*b.com', 'a*b.com'); + find('/foo/a%40b.com/bar', false); }); test('GH-180 can parse DELETE body', function (t) { - SERVER.use(restify.bodyParser({mapParams: false})); - - SERVER.del('/', function (req, res, next) { - res.send(200, req.body); - next(); - }); - - var opts = { - hostname: 'localhost', - port: PORT, - path: '/', - method: 'DELETE', - agent: false, - headers: { - 'accept': 'application/json', - 'content-type': 'application/json', - 'transfer-encoding': 'chunked' - } - }; - http.request(opts, function (res) { - t.equal(res.statusCode, 200); - res.setEncoding('utf8'); - res.body = ''; - res.on('data', function (chunk) { - res.body += chunk; - }); - res.on('end', function () { - t.equal(res.body, '{"param1":1234}'); - t.end(); - }); - }).end('{"param1": 1234}'); + SERVER.use(restify.bodyParser({mapParams: false})); + + SERVER.del('/', function (req, res, next) { + res.send(200, req.body); + next(); + }); + + var opts = { + hostname: 'localhost', + port: PORT, + path: '/', + method: 'DELETE', + agent: false, + headers: { + 'accept': 'application/json', + 'content-type': 'application/json', + 'transfer-encoding': 'chunked' + } + }; + http.request(opts, function (res) { + t.equal(res.statusCode, 200); + res.setEncoding('utf8'); + res.body = ''; + res.on('data', function (chunk) { + res.body += chunk; + }); + res.on('end', function () { + t.equal(res.body, '{"param1":1234}'); + t.end(); + }); + }).end('{"param1": 1234}'); }); test('returning error from a handler (with domains)', function (t) { - SERVER.get('/', function (req, res, next) { - next(new Error('bah!')); - }); + SERVER.get('/', function (req, res, next) { + next(new Error('bah!')); + }); - CLIENT.get('/', function (err, _, res) { - t.ok(err); - t.equal(res.statusCode, 500); - t.end(); - }); + CLIENT.get('/', function (err, _, res) { + t.ok(err); + t.equal(res.statusCode, 500); + t.end(); + }); }); test('emitting error from a handler (with domains)', function (t) { - SERVER.get('/', function (req, res, next) { - req.emit('error', new Error('bah!')); - }); + SERVER.get('/', function (req, res, next) { + req.emit('error', new Error('bah!')); + }); - CLIENT.get('/', function (err, _, res) { - t.ok(err); - t.equal(res.statusCode, 500); - t.end(); - }); + CLIENT.get('/', function (err, _, res) { + t.ok(err); + t.equal(res.statusCode, 500); + t.end(); + }); }); test('throwing error from a handler (with domains)', function (t) { - SERVER.get('/', function (req, res, next) { - process.nextTick(function () { - throw new Error('bah!'); - }); + SERVER.get('/', function (req, res, next) { + process.nextTick(function () { + throw new Error('bah!'); }); + }); - CLIENT.get('/', function (err, _, res) { - t.ok(err); - t.equal(res.statusCode, 500); - t.end(); - }); + CLIENT.get('/', function (err, _, res) { + t.ok(err); + t.equal(res.statusCode, 500); + t.end(); + }); }); test('gh-278 missing router error events (404)', function (t) { - SERVER.once('NotFound', function (req, res) { - res.send(404, 'foo'); - }); - - CLIENT.get('/' + uuid.v4(), function (err, _, res) { - t.ok(err); - t.equal(err.message, '"foo"'); - t.equal(res.statusCode, 404); - t.end(); - }); + SERVER.once('NotFound', function (req, res) { + res.send(404, 'foo'); + }); + + CLIENT.get('/' + uuid.v4(), function (err, _, res) { + t.ok(err); + t.equal(err.message, '"foo"'); + t.equal(res.statusCode, 404); + t.end(); + }); }); test('gh-278 missing router error events (405)', function (t) { - var p = '/' + uuid.v4(); - SERVER.post(p, function (req, res, next) { - res.send(201); - next(); - }); - SERVER.once('MethodNotAllowed', function (req, res) { - res.send(405, 'foo'); - }); - - CLIENT.get(p, function (err, _, res) { - t.ok(err); - t.equal(err.message, '"foo"'); - t.equal(res.statusCode, 405); - t.end(); - }); + var p = '/' + uuid.v4(); + SERVER.post(p, function (req, res, next) { + res.send(201); + next(); + }); + SERVER.once('MethodNotAllowed', function (req, res) { + res.send(405, 'foo'); + }); + + CLIENT.get(p, function (err, _, res) { + t.ok(err); + t.equal(err.message, '"foo"'); + t.equal(res.statusCode, 405); + t.end(); + }); }); test('gh-278 missing router error events invalid version', function (t) { - var p = '/' + uuid.v4(); - SERVER.get({ - path: p, - version: '1.2.3' - }, function (req, res, next) { - res.send(200); - next(); - }); - SERVER.once('VersionNotAllowed', function (req, res) { - res.send(449, 'foo'); - }); - - var opts = { - path: p, - headers: { - 'accept-version': '3.2.1' - } - }; - CLIENT.get(opts, function (err, _, res) { - t.ok(err); - t.equal(err.message, '"foo"'); - t.equal(res.statusCode, 449); - t.end(); - }); + var p = '/' + uuid.v4(); + SERVER.get({ + path: p, + version: '1.2.3' + }, function (req, res, next) { + res.send(200); + next(); + }); + SERVER.once('VersionNotAllowed', function (req, res) { + res.send(449, 'foo'); + }); + + var opts = { + path: p, + headers: { + 'accept-version': '3.2.1' + } + }; + CLIENT.get(opts, function (err, _, res) { + t.ok(err); + t.equal(err.message, '"foo"'); + t.equal(res.statusCode, 449); + t.end(); + }); }); test('gh-278 missing router error events (415)', function (t) { - var p = '/' + uuid.v4(); - SERVER.post({ - path: p, - contentType: 'text/xml' - }, function (req, res, next) { - res.send(200); - next(); - }); - - SERVER.once('UnsupportedMediaType', function (req, res) { - res.send(415, 'foo'); - }); - - CLIENT.post(p, {}, function (err, _, res) { - t.ok(err); - t.equal(err.message, '"foo"'); - t.equal(res.statusCode, 415); - t.end(); - }); + var p = '/' + uuid.v4(); + SERVER.post({ + path: p, + contentType: 'text/xml' + }, function (req, res, next) { + res.send(200); + next(); + }); + + SERVER.once('UnsupportedMediaType', function (req, res) { + res.send(415, 'foo'); + }); + + CLIENT.post(p, {}, function (err, _, res) { + t.ok(err); + t.equal(err.message, '"foo"'); + t.equal(res.statusCode, 415); + t.end(); + }); }); test('next.ifError', function (t) { - SERVER.use(function (req, res, next) { - next.ifError(null); - next(); - }); - - SERVER.get('/foo/:id', function tester(req, res, next) { - process.nextTick(function () { - var e = new RestError({ - statusCode: 400, - restCode: 'Foo' - }, 'screw you client'); - next.ifError(e); - t.notOk(true); - res.send(200); - next(); - }); - }); - - CLIENT.get('/foo/bar', function (err) { - t.ok(err); - t.equal(err.statusCode, 400); - t.equal(err.message, 'screw you client'); - t.end(); - }); + SERVER.use(function (req, res, next) { + next.ifError(null); + next(); + }); + + SERVER.get('/foo/:id', function tester(req, res, next) { + process.nextTick(function () { + var e = new RestError({ + statusCode: 400, + restCode: 'Foo' + }, 'screw you client'); + next.ifError(e); + t.notOk(true); + res.send(200); + next(); + }); + }); + + CLIENT.get('/foo/bar', function (err) { + t.ok(err); + t.equal(err.statusCode, 400); + t.equal(err.message, 'screw you client'); + t.end(); + }); }); test('gh-283 maximum available versioned route matching', function (t) { - var p = '/' + uuid.v4(); - var versions = ['1.0.0', '1.1.0']; - var i; - - function mnt(v) { - SERVER.get({ - path: p, - version: v - }, function (req, res, next) { - res.json(200, {version: v}); - next(); - }); - } - - for (i = 0; i < versions.length; i++) - mnt(versions[i]); + var p = '/' + uuid.v4(); + var versions = ['1.0.0', '1.1.0']; + var i; - var opts = { - path: p, - headers: { - 'accept-version': '~1' - } - }; - - CLIENT.get(opts, function (err, _, res, obj) { - t.equal(obj.version, '1.1.0'); - t.end(); + function mnt(v) { + SERVER.get({ + path: p, + version: v + }, function (req, res, next) { + res.json(200, {version: v}); + next(); }); -}); + } + for (i = 0; i < versions.length; i++) + mnt(versions[i]); -test('gh-329 wrong values in res.methods', function (t) { - function route(req, res, next) { - res.send(200); - next(); + var opts = { + path: p, + headers: { + 'accept-version': '~1' } + }; - SERVER.get('/stuff', route); - SERVER.post('/stuff', route); - SERVER.get('/stuff/:id', route); - SERVER.put('/stuff/:id', route); - SERVER.del('/stuff/:id', route); + CLIENT.get(opts, function (err, _, res, obj) { + t.equal(obj.version, '1.1.0'); + t.end(); + }); +}); - SERVER.once('MethodNotAllowed', function (req, res, cb) { - t.ok(res.methods); - t.deepEqual(res.methods, ['GET', 'PUT', 'DELETE']); - res.send(405); - }); - CLIENT.post('/stuff/foo', {}, function (err, _, res) { - t.ok(err); - t.end(); - }); +test('gh-329 wrong values in res.methods', function (t) { + function route(req, res, next) { + res.send(200); + next(); + } + + SERVER.get('/stuff', route); + SERVER.post('/stuff', route); + SERVER.get('/stuff/:id', route); + SERVER.put('/stuff/:id', route); + SERVER.del('/stuff/:id', route); + + SERVER.once('MethodNotAllowed', function (req, res, cb) { + t.ok(res.methods); + t.deepEqual(res.methods, ['GET', 'PUT', 'DELETE']); + res.send(405); + }); + + CLIENT.post('/stuff/foo', {}, function (err, _, res) { + t.ok(err); + t.end(); + }); }); test('GH-323: //? broken', function (t) { - SERVER.pre(restify.pre.sanitizePath()); - SERVER.use(restify.queryParser()); - SERVER.get('/hello/:name', function (req, res, next) { - res.send(req.params); - }); - - SERVER.listen(8080, function () { - CLIENT.get('/hello/foo/?bar=baz', function (err, _, __, obj) { - t.ifError(err); - t.deepEqual(obj, {name: 'foo', bar: 'baz'}); - t.end(); - }); - }); + SERVER.pre(restify.pre.sanitizePath()); + SERVER.use(restify.queryParser()); + SERVER.get('/hello/:name', function (req, res, next) { + res.send(req.params); + }); + + SERVER.listen(8080, function () { + CLIENT.get('/hello/foo/?bar=baz', function (err, _, __, obj) { + t.ifError(err); + t.deepEqual(obj, {name: 'foo', bar: 'baz'}); + t.end(); + }); + }); }); test('/? broken', function (t) { - SERVER.pre(restify.pre.sanitizePath()); - SERVER.use(restify.queryParser()); - /* JSSTYLED */ - SERVER.get(/\/.*/, function (req, res, next) { - res.send(req.params); - }); - - SERVER.listen(8080, function () { - CLIENT.get('/?bar=baz', function (err, _, __, obj) { - t.ifError(err); - t.deepEqual(obj, {bar: 'baz'}); - t.end(); - }); - }); + SERVER.pre(restify.pre.sanitizePath()); + SERVER.use(restify.queryParser()); + /* JSSTYLED */ + SERVER.get(/\/.*/, function (req, res, next) { + res.send(req.params); + }); + + SERVER.listen(8080, function () { + CLIENT.get('/?bar=baz', function (err, _, __, obj) { + t.ifError(err); + t.deepEqual(obj, {bar: 'baz'}); + t.end(); + }); + }); }); test('content-type routing vendor', function (t) { - SERVER.post({ - name: 'foo', - path: '/', - contentType: 'application/vnd.joyent.com.foo+json' - }, function (req, res, next) { - res.send(201); - }); - - SERVER.post({ - name: 'bar', - path: '/', - contentType: 'application/vnd.joyent.com.bar+json' - }, function (req, res, next) { - res.send(202); - }); - - SERVER.listen(8080, function () { - var _done = 0; - function done() { - if (++_done === 2) - t.end(); - } - - var opts = { - path: '/', - headers: { - 'content-type': - 'application/vnd.joyent.com.foo+json' - } - }; - CLIENT.post(opts, {}, function (err, _, res) { - t.ifError(err); - t.equal(res.statusCode, 201); - done(); - }); - - var opts2 = { - path: '/', - headers: { - 'content-type': - 'application/vnd.joyent.com.bar+json' - } - }; - CLIENT.post(opts2, {}, function (err, _, res) { - t.ifError(err); - t.equal(res.statusCode, 202); - done(); - }); - }); -}); - - -test('content-type routing params only', function (t) { - SERVER.post({ - name: 'foo', - path: '/', - contentType: 'application/json; type=foo' - }, function (req, res, next) { - res.send(201); - }); - - SERVER.post({ - name: 'bar', - path: '/', - contentType: 'application/json; type=bar' - }, function (req, res, next) { - res.send(202); - }); - + SERVER.post({ + name: 'foo', + path: '/', + contentType: 'application/vnd.joyent.com.foo+json' + }, function (req, res, next) { + res.send(201); + }); + + SERVER.post({ + name: 'bar', + path: '/', + contentType: 'application/vnd.joyent.com.bar+json' + }, function (req, res, next) { + res.send(202); + }); + + SERVER.listen(8080, function () { var _done = 0; + function done() { - if (++_done === 2) - t.end(); + if (++_done === 2) + t.end(); } var opts = { - path: '/', - headers: { - 'content-type': - 'application/json; type=foo' - } + path: '/', + headers: { + 'content-type': 'application/vnd.joyent.com.foo+json' + } }; CLIENT.post(opts, {}, function (err, _, res) { - t.ifError(err); - t.equal(res.statusCode, 201); - done(); + t.ifError(err); + t.equal(res.statusCode, 201); + done(); }); var opts2 = { - path: '/', - headers: { - 'content-type': - 'application/json; type=bar' - } + path: '/', + headers: { + 'content-type': 'application/vnd.joyent.com.bar+json' + } }; CLIENT.post(opts2, {}, function (err, _, res) { - t.ifError(err); - t.equal(res.statusCode, 202); - done(); + t.ifError(err); + t.equal(res.statusCode, 202); + done(); }); + }); }); -test('gh-193 basic', function (t) { - SERVER.get({ - name: 'foo', - path: '/foo' - }, function (req, res, next) { - next('bar'); - }); +test('content-type routing params only', function (t) { + SERVER.post({ + name: 'foo', + path: '/', + contentType: 'application/json; type=foo' + }, function (req, res, next) { + res.send(201); + }); + + SERVER.post({ + name: 'bar', + path: '/', + contentType: 'application/json; type=bar' + }, function (req, res, next) { + res.send(202); + }); + + var _done = 0; + + function done() { + if (++_done === 2) + t.end(); + } + + var opts = { + path: '/', + headers: { + 'content-type': 'application/json; type=foo' + } + }; + CLIENT.post(opts, {}, function (err, _, res) { + t.ifError(err); + t.equal(res.statusCode, 201); + done(); + }); + + var opts2 = { + path: '/', + headers: { + 'content-type': 'application/json; type=bar' + } + }; + CLIENT.post(opts2, {}, function (err, _, res) { + t.ifError(err); + t.equal(res.statusCode, 202); + done(); + }); +}); - SERVER.get({ - name: 'bar', - path: '/bar' - }, function (req, res, next) { - res.send(200); - next(); - }); - CLIENT.get('/foo', function (err, _, res) { - t.ifError(err); - t.equal(res.statusCode, 200); - t.end(); - }); +test('gh-193 basic', function (t) { + SERVER.get({ + name: 'foo', + path: '/foo' + }, function (req, res, next) { + next('bar'); + }); + + SERVER.get({ + name: 'bar', + path: '/bar' + }, function (req, res, next) { + res.send(200); + next(); + }); + + CLIENT.get('/foo', function (err, _, res) { + t.ifError(err); + t.equal(res.statusCode, 200); + t.end(); + }); }); - test('gh-193 route ENOEXIST', function (t) { - SERVER.get({ - name: 'foo', - path: '/foo' - }, function (req, res, next) { - next('baz'); - }); - - SERVER.get({ - name: 'bar', - path: '/bar' - }, function (req, res, next) { - res.send(200); - next(); - }); - - CLIENT.get('/foo', function (err, _, res) { - t.ok(err); - t.equal(res.statusCode, 500); - t.end(); - }); + SERVER.get({ + name: 'foo', + path: '/foo' + }, function (req, res, next) { + next('baz'); + }); + + SERVER.get({ + name: 'bar', + path: '/bar' + }, function (req, res, next) { + res.send(200); + next(); + }); + + CLIENT.get('/foo', function (err, _, res) { + t.ok(err); + t.equal(res.statusCode, 500); + t.end(); + }); }); test('gh-193 route only run use once', function (t) { - var count = 0; - - SERVER.use(function (req, res, next) { - count++; - next(); - }); - - SERVER.get({ - name: 'foo', - path: '/foo' - }, function (req, res, next) { - next('bar'); - }); - - SERVER.get({ - name: 'bar', - path: '/bar' - }, function (req, res, next) { - res.send(200); - next(); - }); - - CLIENT.get('/foo', function (err, _, res) { - t.ifError(err); - t.equal(res.statusCode, 200); - t.equal(count, 1); - t.end(); - }); + var count = 0; + + SERVER.use(function (req, res, next) { + count++; + next(); + }); + + SERVER.get({ + name: 'foo', + path: '/foo' + }, function (req, res, next) { + next('bar'); + }); + + SERVER.get({ + name: 'bar', + path: '/bar' + }, function (req, res, next) { + res.send(200); + next(); + }); + + CLIENT.get('/foo', function (err, _, res) { + t.ifError(err); + t.equal(res.statusCode, 200); + t.equal(count, 1); + t.end(); + }); }); test('gh-193 route chained', function (t) { - var count = 0; - - SERVER.use(function addCounter(req, res, next) { - count++; - next(); - }); - - SERVER.get({ - name: 'foo', - path: '/foo' - }, function getFoo(req, res, next) { - next('bar'); - }); - - SERVER.get({ - name: 'bar', - path: '/bar' - }, function getBar(req, res, next) { - next('baz'); - }); - - SERVER.get({ - name: 'baz', - path: '/baz' - }, function getBaz(req, res, next) { - res.send(200); - next(); - }); - - CLIENT.get('/foo', function (err, _, res) { - t.ok(err); - t.equal(res.statusCode, 500); - t.equal(count, 1); - t.end(); - }); + var count = 0; + + SERVER.use(function addCounter(req, res, next) { + count++; + next(); + }); + + SERVER.get({ + name: 'foo', + path: '/foo' + }, function getFoo(req, res, next) { + next('bar'); + }); + + SERVER.get({ + name: 'bar', + path: '/bar' + }, function getBar(req, res, next) { + next('baz'); + }); + + SERVER.get({ + name: 'baz', + path: '/baz' + }, function getBaz(req, res, next) { + res.send(200); + next(); + }); + + CLIENT.get('/foo', function (err, _, res) { + t.ok(err); + t.equal(res.statusCode, 500); + t.equal(count, 1); + t.end(); + }); }); test('gh-193 route params basic', function (t) { - var count = 0; - - SERVER.use(function (req, res, next) { - count++; - next(); - }); - - SERVER.get({ - name: 'foo', - path: '/foo/:id' - }, function (req, res, next) { - t.equal(req.params.id, 'blah'); - next('bar'); - }); - - SERVER.get({ - name: 'bar', - path: '/bar/:baz' - }, function (req, res, next) { - t.notOk(req.params.baz); - res.send(200); - next(); - }); - - CLIENT.get('/foo/blah', function (err, _, res) { - t.ifError(err); - t.equal(res.statusCode, 200); - t.equal(count, 1); - t.end(); - }); + var count = 0; + + SERVER.use(function (req, res, next) { + count++; + next(); + }); + + SERVER.get({ + name: 'foo', + path: '/foo/:id' + }, function (req, res, next) { + t.equal(req.params.id, 'blah'); + next('bar'); + }); + + SERVER.get({ + name: 'bar', + path: '/bar/:baz' + }, function (req, res, next) { + t.notOk(req.params.baz); + res.send(200); + next(); + }); + + CLIENT.get('/foo/blah', function (err, _, res) { + t.ifError(err); + t.equal(res.statusCode, 200); + t.equal(count, 1); + t.end(); + }); }); test('gh-193 same url w/params', function (t) { - var count = 0; - - SERVER.use(function (req, res, next) { - count++; - next(); - }); - - SERVER.get({ - name: 'foo', - path: '/foo/:id' - }, function (req, res, next) { - t.equal(req.params.id, 'blah'); - next('foo2'); - }); - - SERVER.get({ - name: 'foo2', - path: '/foo/:baz' - }, function (req, res, next) { - t.equal(req.params.baz, 'blah'); - res.send(200); - next(); - }); - - CLIENT.get('/foo/blah', function (err, _, res) { - t.ifError(err); - t.equal(res.statusCode, 200); - t.equal(count, 1); - t.end(); - }); + var count = 0; + + SERVER.use(function (req, res, next) { + count++; + next(); + }); + + SERVER.get({ + name: 'foo', + path: '/foo/:id' + }, function (req, res, next) { + t.equal(req.params.id, 'blah'); + next('foo2'); + }); + + SERVER.get({ + name: 'foo2', + path: '/foo/:baz' + }, function (req, res, next) { + t.equal(req.params.baz, 'blah'); + res.send(200); + next(); + }); + + CLIENT.get('/foo/blah', function (err, _, res) { + t.ifError(err); + t.equal(res.statusCode, 200); + t.equal(count, 1); + t.end(); + }); }); test('gh-193 next("route") from a use plugin', function (t) { - var count = 0; - - SERVER.use(function plugin(req, res, next) { - count++; - next('bar'); - }); - - SERVER.get({ - name: 'foo', - path: '/foo' - }, function getFoo(req, res, next) { - res.send(500); - next(); - }); - - SERVER.get({ - name: 'bar', - path: '/bar' - }, function getBar(req, res, next) { - res.send(200); - next(); - }); - - CLIENT.get('/foo', function (err, _, res) { - t.ifError(err); - t.equal(res.statusCode, 200); - t.equal(count, 1); - t.end(); - }); + var count = 0; + + SERVER.use(function plugin(req, res, next) { + count++; + next('bar'); + }); + + SERVER.get({ + name: 'foo', + path: '/foo' + }, function getFoo(req, res, next) { + res.send(500); + next(); + }); + + SERVER.get({ + name: 'bar', + path: '/bar' + }, function getBar(req, res, next) { + res.send(200); + next(); + }); + + CLIENT.get('/foo', function (err, _, res) { + t.ifError(err); + t.equal(res.statusCode, 200); + t.equal(count, 1); + t.end(); + }); }); - test('res.charSet', function (t) { - SERVER.get('/foo', function getFoo(req, res, next) { - res.charSet('ISO-8859-1'); - res.set('Content-Type', 'text/plain'); - res.send(200, {foo: 'bar'}); - next(); - }); - - CLIENT.get('/foo', function (err, _, res) { - t.ifError(err); - t.equal(res.statusCode, 200); - t.equal(res.headers['content-type'], - 'text/plain; charset=ISO-8859-1'); - t.end(); - }); + SERVER.get('/foo', function getFoo(req, res, next) { + res.charSet('ISO-8859-1'); + res.set('Content-Type', 'text/plain'); + res.send(200, {foo: 'bar'}); + next(); + }); + + CLIENT.get('/foo', function (err, _, res) { + t.ifError(err); + t.equal(res.statusCode, 200); + t.equal(res.headers['content-type'], + 'text/plain; charset=ISO-8859-1'); + t.end(); + }); }); test('res.charSet override', function (t) { - SERVER.get('/foo', function getFoo(req, res, next) { - res.charSet('ISO-8859-1'); - res.set('Content-Type', 'text/plain;charset=utf-8'); - res.send(200, {foo: 'bar'}); - next(); - }); - - CLIENT.get('/foo', function (err, _, res) { - t.ifError(err); - t.equal(res.statusCode, 200); - t.equal(res.headers['content-type'], - 'text/plain; charset=ISO-8859-1'); - t.end(); - }); + SERVER.get('/foo', function getFoo(req, res, next) { + res.charSet('ISO-8859-1'); + res.set('Content-Type', 'text/plain;charset=utf-8'); + res.send(200, {foo: 'bar'}); + next(); + }); + + CLIENT.get('/foo', function (err, _, res) { + t.ifError(err); + t.equal(res.statusCode, 200); + t.equal(res.headers['content-type'], + 'text/plain; charset=ISO-8859-1'); + t.end(); + }); }); test('GH-384 res.json(200, {}) broken', function (t) { - SERVER.get('/foo', function (req, res, next) { - res.json(200, {foo: 'bar'}); - next(); - }); - - CLIENT.get('/foo', function (err, _, res, obj) { - t.ifError(err); - t.equal(res.statusCode, 200); - t.ok(obj); - t.equal((obj || {}).foo, 'bar'); - t.end(); - }); + SERVER.get('/foo', function (req, res, next) { + res.json(200, {foo: 'bar'}); + next(); + }); + + CLIENT.get('/foo', function (err, _, res, obj) { + t.ifError(err); + t.equal(res.statusCode, 200); + t.ok(obj); + t.equal((obj || {}).foo, 'bar'); + t.end(); + }); }); test('GH-401 regex routing broken', function (t) { - function handle(req, res, next) { - res.send(204); - next(); - } + function handle(req, res, next) { + res.send(204); + next(); + } - var done = 0; - function client_cb(err, _, res) { - t.ifError(err); - t.equal(res.statusCode, 204); - if (++done === 2) - t.end(); - } + var done = 0; - SERVER.get('/image', handle); - SERVER.get(/^(\/image\/)(.*)/, handle); + function client_cb(err, _, res) { + t.ifError(err); + t.equal(res.statusCode, 204); + if (++done === 2) + t.end(); + } - CLIENT.get('/image', client_cb); - CLIENT.get('/image/1.jpg', client_cb); + SERVER.get('/image', handle); + SERVER.get(/^(\/image\/)(.*)/, handle); + + CLIENT.get('/image', client_cb); + CLIENT.get('/image/1.jpg', client_cb); }); test('explicitly sending a 403 with custom error', function (t) { - function MyCustomError() {} - MyCustomError.prototype = Object.create(Error.prototype); + function MyCustomError() { + } - SERVER.get('/', function (req, res, next) { - res.send(403, new MyCustomError('bah!')); - }); + MyCustomError.prototype = Object.create(Error.prototype); - CLIENT.get('/', function (err, _, res) { - t.ok(err); - t.equal(res.statusCode, 403); - t.end(); - }); + SERVER.get('/', function (req, res, next) { + res.send(403, new MyCustomError('bah!')); + }); + + CLIENT.get('/', function (err, _, res) { + t.ok(err); + t.equal(res.statusCode, 403); + t.end(); + }); }); test('explicitly sending a 403 on error', function (t) { - SERVER.get('/', function (req, res, next) { - res.send(403, new Error('bah!')); - }); + SERVER.get('/', function (req, res, next) { + res.send(403, new Error('bah!')); + }); - CLIENT.get('/', function (err, _, res) { - t.ok(err); - t.equal(res.statusCode, 403); - t.end(); - }); + CLIENT.get('/', function (err, _, res) { + t.ok(err); + t.equal(res.statusCode, 403); + t.end(); + }); }); diff --git a/test/throttle.test.js b/test/throttle.test.js index dd1dc5130..1aee627e4 100644 --- a/test/throttle.test.js +++ b/test/throttle.test.js @@ -7,11 +7,10 @@ var uuid = require('node-uuid'); var restify = require('../lib'); if (require.cache[__dirname + '/lib/helper.js']) - delete require.cache[__dirname + '/lib/helper.js']; + delete require.cache[__dirname + '/lib/helper.js']; var helper = require('./lib/helper.js'); - ///--- Globals var after = helper.after; @@ -25,131 +24,128 @@ var USERNAME = uuid(); var PASSWORD = uuid(); - ///--- Tests - - - - //--- Tests test('setup', function (t) { - SERVER = restify.createServer({ - dtrace: helper.dtrace, - log: helper.getLog('server') - }); - - SERVER.use(function ghettoAuthenticate(req, res, next) { - if (req.params.name) - req.username = req.params.name; - - next(); - }); - - SERVER.use(restify.throttle({ - burst: 1, - rate: 0.5, - username: true, - overrides: { - 'admin': { - burst: 0, - rate: 0 - }, - 'special': { - burst: 3, - rate: 1 - } - } - })); - - SERVER.get('/test/:name', function (req, res, next) { - res.send(); - next(); + SERVER = restify.createServer({ + dtrace: helper.dtrace, + log: helper.getLog('server') + }); + + SERVER.use(function ghettoAuthenticate(req, res, next) { + if (req.params.name) + req.username = req.params.name; + + next(); + }); + + SERVER.use(restify.throttle({ + burst: 1, + rate: 0.5, + username: true, + overrides: { + 'admin': { + burst: 0, + rate: 0 + }, + 'special': { + burst: 3, + rate: 1 + } + } + })); + + SERVER.get('/test/:name', function (req, res, next) { + res.send(); + next(); + }); + + SERVER.listen(PORT, '127.0.0.1', function () { + PORT = SERVER.address().port; + CLIENT = restify.createJsonClient({ + url: 'http://127.0.0.1:' + PORT, + dtrace: helper.dtrace, + retry: false, + agent: false }); - SERVER.listen(PORT, '127.0.0.1', function () { - PORT = SERVER.address().port; - CLIENT = restify.createJsonClient({ - url: 'http://127.0.0.1:' + PORT, - dtrace: helper.dtrace, - retry: false, - agent: false - }); - - t.end(); - }); + t.end(); + }); }); test('ok', function (t) { - CLIENT.get('/test/throttleMe', function (err, _, res) { - t.ifError(err); - t.equal(res.statusCode, 200); - t.end(); - }); + CLIENT.get('/test/throttleMe', function (err, _, res) { + t.ifError(err); + t.equal(res.statusCode, 200); + t.end(); + }); }); test('throttled', function (t) { - CLIENT.get('/test/throttleMe', function (err, _, res) { - t.ok(err); - t.equal(err.statusCode, 429); - t.ok(err.message); - t.equal(res.statusCode, 429); - setTimeout(function () { t.end(); }, 2100); - }); + CLIENT.get('/test/throttleMe', function (err, _, res) { + t.ok(err); + t.equal(err.statusCode, 429); + t.ok(err.message); + t.equal(res.statusCode, 429); + setTimeout(function () { + t.end(); + }, 2100); + }); }); test('ok after tokens', function (t) { - CLIENT.get('/test/throttleMe', function (err, _, res) { - t.ifError(err); - t.equal(res.statusCode, 200); - t.end(); - }); + CLIENT.get('/test/throttleMe', function (err, _, res) { + t.ifError(err); + t.equal(res.statusCode, 200); + t.end(); + }); }); test('override limited', function (t) { - CLIENT.get('/test/special', function (err, _, res) { - t.ifError(err); - t.equal(res.statusCode, 200); - t.end(); - }); + CLIENT.get('/test/special', function (err, _, res) { + t.ifError(err); + t.equal(res.statusCode, 200); + t.end(); + }); }); test('override limited (not throttled)', function (t) { - CLIENT.get('/test/special', function (err, _, res) { - t.ifError(err); - t.equal(res.statusCode, 200); - t.end(); - }); + CLIENT.get('/test/special', function (err, _, res) { + t.ifError(err); + t.equal(res.statusCode, 200); + t.end(); + }); }); test('override unlimited', function (t) { - CLIENT.get('/test/admin', function (err, _, res) { - t.ifError(err); - t.equal(res.statusCode, 200); - t.end(); - }); + CLIENT.get('/test/admin', function (err, _, res) { + t.ifError(err); + t.equal(res.statusCode, 200); + t.end(); + }); }); test('override unlimited (not throttled)', function (t) { - CLIENT.get('/test/admin', function (err, _, res) { - t.ifError(err); - t.equal(res.statusCode, 200); - t.end(); - }); + CLIENT.get('/test/admin', function (err, _, res) { + t.ifError(err); + t.equal(res.statusCode, 200); + t.end(); + }); }); test('shutdown', function (t) { - SERVER.close(function () { - t.end(); - }); + SERVER.close(function () { + t.end(); + }); }); diff --git a/test/upgrade.test.js b/test/upgrade.test.js index c755cef0c..dc2fcd9be 100644 --- a/test/upgrade.test.js +++ b/test/upgrade.test.js @@ -8,15 +8,14 @@ var Watershed = require('watershed').Watershed; var HttpError = require('../lib/errors').HttpError; var RestError = require('../lib/errors').RestError; var InvalidUpgradeStateError = - require('../lib/upgrade').InvalidUpgradeStateError; + require('../lib/upgrade').InvalidUpgradeStateError; var restify = require('../lib'); if (require.cache[__dirname + '/lib/helper.js']) - delete require.cache[__dirname + '/lib/helper.js']; + delete require.cache[__dirname + '/lib/helper.js']; var helper = require('./lib/helper.js'); - ///--- Globals var after = helper.after; @@ -34,382 +33,381 @@ var TIMEOUT = 15000; ///--- Test Helper function -finish_latch(_test, _names) -{ - var complete = false; - var t = _test; - var names = _names; - var iv = setTimeout(function () { - if (complete) - return; - - complete = true; - t.ok(false, 'timeout after ' + TIMEOUT + 'ms'); - t.ok(false, 'remaining latches: ' + - Object.keys(names).join(', ')); - t.done(); - }, TIMEOUT); - return (function (name, err) { - if (complete) - return; - - if (names[name] === undefined) { - complete = true; - t.ok(false, 'latch name "' + name + - '" not expected'); - t.ok(false, 'remaining latches: ' + - Object.keys(names).join(', ')); - t.done(); - return; - } - - if (--names[name] === 0) - delete names[name]; - - /* - * Check that all latch names are done, and if so, - * end the test: - */ - if (Object.keys(names).length === 0) { - complete = true; - clearTimeout(iv); - iv = null; - t.done(); - } - }); + finish_latch(_test, _names) { + var complete = false; + var t = _test; + var names = _names; + var iv = setTimeout(function () { + if (complete) + return; + + complete = true; + t.ok(false, 'timeout after ' + TIMEOUT + 'ms'); + t.ok(false, 'remaining latches: ' + + Object.keys(names).join(', ')); + t.done(); + }, TIMEOUT); + return (function (name, err) { + if (complete) + return; + + if (names[name] === undefined) { + complete = true; + t.ok(false, 'latch name "' + name + + '" not expected'); + t.ok(false, 'remaining latches: ' + + Object.keys(names).join(', ')); + t.done(); + return; + } + + if (--names[name] === 0) + delete names[name]; + + /* + * Check that all latch names are done, and if so, + * end the test: + */ + if (Object.keys(names).length === 0) { + complete = true; + clearTimeout(iv); + iv = null; + t.done(); + } + }); } ///--- Tests before(function (cb) { - try { - SERVER = restify.createServer({ - dtrace: helper.dtrace, - log: helper.getLog('server'), - version: ['2.0.0', '0.5.4', '1.4.3'], - handleUpgrades: true - }); - SERVER.listen(PORT, '127.0.0.1', function () { - PORT = SERVER.address().port; - CLIENT = restify.createHttpClient({ - url: 'http://127.0.0.1:' + PORT, - dtrace: helper.dtrace, - retry: false - }); - - cb(); - }); - } catch (e) { - console.error(e.stack); - process.exit(1); - } + try { + SERVER = restify.createServer({ + dtrace: helper.dtrace, + log: helper.getLog('server'), + version: ['2.0.0', '0.5.4', '1.4.3'], + handleUpgrades: true + }); + SERVER.listen(PORT, '127.0.0.1', function () { + PORT = SERVER.address().port; + CLIENT = restify.createHttpClient({ + url: 'http://127.0.0.1:' + PORT, + dtrace: helper.dtrace, + retry: false + }); + + cb(); + }); + } catch (e) { + console.error(e.stack); + process.exit(1); + } }); after(function (cb) { - try { - CLIENT.close(); - SERVER.close(function () { - CLIENT = null; - SERVER = null; - cb(); - }); - while (SHEDLIST.length > 0) { - SHEDLIST.pop().destroy(); - } - } catch (e) { - console.error(e.stack); - process.exit(1); + try { + CLIENT.close(); + SERVER.close(function () { + CLIENT = null; + SERVER = null; + cb(); + }); + while (SHEDLIST.length > 0) { + SHEDLIST.pop().destroy(); } + } catch (e) { + console.error(e.stack); + process.exit(1); + } }); test('GET without upgrade headers', function (t) { - var done = finish_latch(t, { - 'client response': 1, - 'server response': 1 + var done = finish_latch(t, { + 'client response': 1, + 'server response': 1 + }); + + SERVER.get('/attach', function (req, res, next) { + t.ok(!res.claimUpgrade, 'res.claimUpgrade not present'); + res.send(400); + next(); + done('server response'); + }); + + var options = { + headers: { + 'uprgade': 'ebfrockets' + }, + path: '/attach' + }; + CLIENT.get(options, function (err, req) { + t.ifError(err); + req.on('error', function (err2) { + t.ifError(err2); + done('client error'); }); - - SERVER.get('/attach', function (req, res, next) { - t.ok(!res.claimUpgrade, 'res.claimUpgrade not present'); - res.send(400); - next(); - done('server response'); + req.on('result', function (err2, res) { + if (err2 && err2.name !== 'BadRequestError') + t.ifError(err2); + t.equal(res.statusCode, 400); + res.on('end', function () { + done('client response'); + }); + res.resume(); }); - - var options = { - headers: { - 'uprgade': 'ebfrockets' - }, - path: '/attach' - }; - CLIENT.get(options, function (err, req) { - t.ifError(err); - req.on('error', function (err2) { - t.ifError(err2); - done('client error'); - }); - req.on('result', function (err2, res) { - if (err2 && err2.name !== 'BadRequestError') - t.ifError(err2); - t.equal(res.statusCode, 400); - res.on('end', function () { - done('client response'); - }); - res.resume(); - }); - req.on('upgradeResult', function (err2, res) { - done('server upgraded unexpectedly'); - }); + req.on('upgradeResult', function (err2, res) { + done('server upgraded unexpectedly'); }); + }); }); test('Dueling upgrade and response handling 1', function (t) { - var done = finish_latch(t, { - 'expected requestUpgrade error': 1, - 'client response': 1 - }); + var done = finish_latch(t, { + 'expected requestUpgrade error': 1, + 'client response': 1 + }); - SERVER.get('/attach', function (req, res, next) { - try { - res.send(400); - } catch (ex) { - t.ifError(ex); - done('unxpected res.send error'); - return; - } - - try { - var upg = res.claimUpgrade(); - upg.socket.destroy(); - } catch (ex) { - done('expected requestUpgrade error'); - } - next(false); - }); + SERVER.get('/attach', function (req, res, next) { + try { + res.send(400); + } catch (ex) { + t.ifError(ex); + done('unxpected res.send error'); + return; + } - var wskey = WATERSHED.generateKey(); - var options = { - headers: { - 'connection': 'upgrade', - 'upgrade': 'websocket', - 'sec-websocket-key': wskey - }, - path: '/attach' - }; - CLIENT.get(options, function (err, req) { - t.ifError(err); - req.on('error', function (err2) { - t.ifError(err2); - done('client error'); - }); - req.on('result', function (err2, res) { - if (err2 && err2.name !== 'BadRequestError') - t.ifError(err2); - t.equal(res.statusCode, 400); - res.on('end', function () { - done('client response'); - }); - res.resume(); - }); - req.on('upgradeResult', function (err2, res) { - done('server upgraded unexpectedly'); - }); + try { + var upg = res.claimUpgrade(); + upg.socket.destroy(); + } catch (ex) { + done('expected requestUpgrade error'); + } + next(false); + }); + + var wskey = WATERSHED.generateKey(); + var options = { + headers: { + 'connection': 'upgrade', + 'upgrade': 'websocket', + 'sec-websocket-key': wskey + }, + path: '/attach' + }; + CLIENT.get(options, function (err, req) { + t.ifError(err); + req.on('error', function (err2) { + t.ifError(err2); + done('client error'); }); + req.on('result', function (err2, res) { + if (err2 && err2.name !== 'BadRequestError') + t.ifError(err2); + t.equal(res.statusCode, 400); + res.on('end', function () { + done('client response'); + }); + res.resume(); + }); + req.on('upgradeResult', function (err2, res) { + done('server upgraded unexpectedly'); + }); + }); }); test('Dueling upgrade and response handling 2', function (t) { - var done = finish_latch(t, { - 'expected res.send error': 1, - 'expected server to reset': 1 - }); + var done = finish_latch(t, { + 'expected res.send error': 1, + 'expected server to reset': 1 + }); - SERVER.get('/attach', function (req, res, next) { - try { - var upg = res.claimUpgrade(); - upg.socket.destroy(); - } catch (ex) { - t.ifError(ex); - done('unexpected requestUpgrade error'); - } - - try { - res.send(400); - } catch (ex) { - if (ex.name !== 'InvalidUpgradeStateError') - t.ifError(ex); - done('expected res.send error'); - return; - } - - next(false); - }); + SERVER.get('/attach', function (req, res, next) { + try { + var upg = res.claimUpgrade(); + upg.socket.destroy(); + } catch (ex) { + t.ifError(ex); + done('unexpected requestUpgrade error'); + } - var wskey = WATERSHED.generateKey(); - var options = { - headers: { - 'connection': 'upgrade', - 'upgrade': 'websocket', - 'sec-websocket-key': wskey - }, - path: '/attach' - }; - CLIENT.get(options, function (err, req) { - t.ifError(err); - done('expected server to reset'); - return; - }); + try { + res.send(400); + } catch (ex) { + if (ex.name !== 'InvalidUpgradeStateError') + t.ifError(ex); + done('expected res.send error'); + return; + } + + next(false); + }); + + var wskey = WATERSHED.generateKey(); + var options = { + headers: { + 'connection': 'upgrade', + 'upgrade': 'websocket', + 'sec-websocket-key': wskey + }, + path: '/attach' + }; + CLIENT.get(options, function (err, req) { + t.ifError(err); + done('expected server to reset'); + return; + }); }); test('GET with upgrade headers', function (t) { - var done = finish_latch(t, { - 'client shed end': 1, - 'server shed end': 1 + var done = finish_latch(t, { + 'client shed end': 1, + 'server shed end': 1 + }); + + SERVER.get('/attach', function (req, res, next) { + t.ok(res.claimUpgrade, 'res.claimUpgrade present'); + t.doesNotThrow(function () { + var upgrade = res.claimUpgrade(); + var shed = WATERSHED.accept(req, upgrade.socket, + upgrade.head); + SHEDLIST.push(shed); + shed.end('ok we\'re done here'); + shed.on('error', function (err) { + t.ifError(err); + done('server shed error'); + }); + shed.on('end', function () { + done('server shed end'); + }); + next(false); }); - - SERVER.get('/attach', function (req, res, next) { - t.ok(res.claimUpgrade, 'res.claimUpgrade present'); - t.doesNotThrow(function () { - var upgrade = res.claimUpgrade(); - var shed = WATERSHED.accept(req, upgrade.socket, - upgrade.head); - SHEDLIST.push(shed); - shed.end('ok we\'re done here'); - shed.on('error', function (err) { - t.ifError(err); - done('server shed error'); - }); - shed.on('end', function () { - done('server shed end'); - }); - next(false); - }); + }); + + var wskey = WATERSHED.generateKey(); + var options = { + headers: { + 'connection': 'upgrade', + 'upgrade': 'websocket', + 'sec-websocket-key': wskey + }, + path: '/attach' + }; + CLIENT.get(options, function (err, req) { + t.ifError(err); + req.on('result', function (err2, res) { + t.ifError(err2); + t.ok(false, 'server did not upgrade'); + done(true); }); - - var wskey = WATERSHED.generateKey(); - var options = { - headers: { - 'connection': 'upgrade', - 'upgrade': 'websocket', - 'sec-websocket-key': wskey - }, - path: '/attach' - }; - CLIENT.get(options, function (err, req) { - t.ifError(err); - req.on('result', function (err2, res) { - t.ifError(err2); - t.ok(false, 'server did not upgrade'); - done(true); + req.on('upgradeResult', function (err2, res, socket, head) { + t.ifError(err2); + t.ok(true, 'server upgraded'); + t.equal(res.statusCode, 101); + t.equal(typeof (socket), 'object'); + t.ok(Buffer.isBuffer(head), 'head is Buffer'); + t.doesNotThrow(function () { + var shed = WATERSHED.connect(res, socket, head, + wskey); + SHEDLIST.push(shed); + shed.end('ok, done'); + shed.on('error', function (err3) { + t.ifError(err3); + done('client shed error'); }); - req.on('upgradeResult', function (err2, res, socket, head) { - t.ifError(err2); - t.ok(true, 'server upgraded'); - t.equal(res.statusCode, 101); - t.equal(typeof (socket), 'object'); - t.ok(Buffer.isBuffer(head), 'head is Buffer'); - t.doesNotThrow(function () { - var shed = WATERSHED.connect(res, socket, head, - wskey); - SHEDLIST.push(shed); - shed.end('ok, done'); - shed.on('error', function (err3) { - t.ifError(err3); - done('client shed error'); - }); - shed.on('end', function () { - done('client shed end'); - }); - }); + shed.on('end', function () { + done('client shed end'); }); + }); }); + }); }); test('GET with some websocket traffic', function (t) { - var done = finish_latch(t, { - 'client shed end': 1, - 'server shed end': 1, - 'server receive message': 5, - 'client receive message': 3 + var done = finish_latch(t, { + 'client shed end': 1, + 'server shed end': 1, + 'server receive message': 5, + 'client receive message': 3 + }); + + SERVER.get('/attach', function (req, res, next) { + t.ok(res.claimUpgrade, 'res.claimUpgrade present'); + t.doesNotThrow(function () { + var upgrade = res.claimUpgrade(); + var shed = WATERSHED.accept(req, upgrade.socket, + upgrade.head); + SHEDLIST.push(shed); + shed.on('error', function (err) { + t.ifError(err); + done('server shed error'); + }); + shed.on('text', function (msg) { + if (msg === 'to server') + done('server receive message'); + }); + shed.on('end', function () { + done('server shed end'); + }); + shed.send('to client'); + shed.send('to client'); + shed.send('to client'); + next(false); }); - - SERVER.get('/attach', function (req, res, next) { - t.ok(res.claimUpgrade, 'res.claimUpgrade present'); - t.doesNotThrow(function () { - var upgrade = res.claimUpgrade(); - var shed = WATERSHED.accept(req, upgrade.socket, - upgrade.head); - SHEDLIST.push(shed); - shed.on('error', function (err) { - t.ifError(err); - done('server shed error'); - }); - shed.on('text', function (msg) { - if (msg === 'to server') - done('server receive message'); - }); - shed.on('end', function () { - done('server shed end'); - }); - shed.send('to client'); - shed.send('to client'); - shed.send('to client'); - next(false); - }); + }); + + var wskey = WATERSHED.generateKey(); + var options = { + headers: { + 'connection': 'upgrade', + 'upgrade': 'websocket', + 'sec-websocket-key': wskey + }, + path: '/attach' + }; + CLIENT.get(options, function (err, req) { + t.ifError(err); + req.on('result', function (err2, res) { + t.ifError(err2); + t.ok(false, 'server did not upgrade'); + done(true); }); - - var wskey = WATERSHED.generateKey(); - var options = { - headers: { - 'connection': 'upgrade', - 'upgrade': 'websocket', - 'sec-websocket-key': wskey - }, - path: '/attach' - }; - CLIENT.get(options, function (err, req) { - t.ifError(err); - req.on('result', function (err2, res) { - t.ifError(err2); - t.ok(false, 'server did not upgrade'); - done(true); + req.on('upgradeResult', function (err2, res, socket, head) { + t.ifError(err2); + t.ok(true, 'server upgraded'); + t.equal(res.statusCode, 101); + t.equal(typeof (socket), 'object'); + t.ok(Buffer.isBuffer(head), 'head is Buffer'); + t.doesNotThrow(function () { + var shed = WATERSHED.connect(res, socket, head, + wskey); + SHEDLIST.push(shed); + shed.on('error', function (err3) { + t.ifError(err3); + done('client shed error'); + }); + shed.on('end', function () { + done('client shed end'); }); - req.on('upgradeResult', function (err2, res, socket, head) { - t.ifError(err2); - t.ok(true, 'server upgraded'); - t.equal(res.statusCode, 101); - t.equal(typeof (socket), 'object'); - t.ok(Buffer.isBuffer(head), 'head is Buffer'); - t.doesNotThrow(function () { - var shed = WATERSHED.connect(res, socket, head, - wskey); - SHEDLIST.push(shed); - shed.on('error', function (err3) { - t.ifError(err3); - done('client shed error'); - }); - shed.on('end', function () { - done('client shed end'); - }); - shed.on('text', function (msg) { - if (msg === 'to client') - done('client receive message'); - }); - var count = 5; - var iv = setInterval(function () { - if (--count < 0) { - clearInterval(iv); - shed.end(); - } else { - shed.send('to server'); - } - }, 100); - }); + shed.on('text', function (msg) { + if (msg === 'to client') + done('client receive message'); }); + var count = 5; + var iv = setInterval(function () { + if (--count < 0) { + clearInterval(iv); + shed.end(); + } else { + shed.send('to server'); + } + }, 100); + }); }); + }); }); diff --git a/tools/jsstyle.conf b/tools/jsstyle.conf index 351736ac2..a54201a5e 100644 --- a/tools/jsstyle.conf +++ b/tools/jsstyle.conf @@ -1,4 +1,4 @@ -indent=8 +indent=4 doxygen unparenthesized-return=1 blank-after-start-comment=0 \ No newline at end of file