diff --git a/index.js b/index.js index 3608f26..e3fd78a 100644 --- a/index.js +++ b/index.js @@ -164,6 +164,7 @@ Router.prototype.handle = function handle(req, res, callback) { var self = this var slashAdded = false var paramcalled = {} + var matchedLayer = false // middleware and routes var stack = this.stack @@ -286,6 +287,14 @@ Router.prototype.handle = function handle(req, res, callback) { : layer.params var layerPath = layer.path + if (!matchedLayer) { + matchedLayer = true + req.layerStack = req.layerStack || [] + } else { + req.layerStack.pop() + } + req.layerStack.push(layer) + // this should be done for the layer self.process_params(layer, paramcalled, req, res, function (err) { if (err) { diff --git a/lib/layer.js b/lib/layer.js index 60a737f..cc64f47 100644 --- a/lib/layer.js +++ b/lib/layer.js @@ -15,6 +15,18 @@ var pathRegexp = require('path-to-regexp') var debug = require('debug')('router:layer') +/** + * Diagnostic channels + * @private + */ +var onHandleRequest +var onHandleError +try { + var dc = require('diagnostics_channel') + onHandleRequest = dc.channel('router.layer.handle_request') + onHandleError = dc.channel('router.layer.handle_error') +} catch (err) { } + /** * Module variables. * @private @@ -41,6 +53,7 @@ function Layer(path, options, fn) { this.params = undefined this.path = undefined this.regexp = pathRegexp(path, this.keys = [], opts) + this.routingPath = path // set fast path flags this.regexp.fast_star = path === '*' @@ -65,6 +78,15 @@ Layer.prototype.handle_error = function handle_error(error, req, res, next) { return next(error) } + if (onHandleError && onHandleError.shouldPublish()) { + onHandleError.publish({ + error: error, + request: req, + response: res, + layer: this + }) + } + try { fn(error, req, res, next) } catch (err) { @@ -89,6 +111,14 @@ Layer.prototype.handle_request = function handle(req, res, next) { return next() } + if (onHandleRequest && onHandleRequest.shouldPublish()) { + onHandleRequest.publish({ + request: req, + response: res, + layer: this + }) + } + try { fn(req, res, next) } catch (err) { diff --git a/lib/route.js b/lib/route.js index 98363fe..78cc697 100644 --- a/lib/route.js +++ b/lib/route.js @@ -93,6 +93,7 @@ Route.prototype._methods = function _methods() { */ Route.prototype.dispatch = function dispatch(req, res, done) { + var matchedLayer = false var idx = 0 var stack = this.stack if (stack.length === 0) { @@ -138,6 +139,14 @@ Route.prototype.dispatch = function dispatch(req, res, done) { return done(err) } + if (!matchedLayer) { + matchedLayer = true + req.layerStack = req.layerStack || [] + } else { + req.layerStack.pop() + } + req.layerStack.push(layer) + if (err) { layer.handle_error(err, req, res, next) } else { diff --git a/test/diagnostics-channel.js b/test/diagnostics-channel.js new file mode 100644 index 0000000..e4f38f7 --- /dev/null +++ b/test/diagnostics-channel.js @@ -0,0 +1,106 @@ + +var Router = require('../') + , assert = require('assert'); + +var dc = require('diagnostics_channel'); +var onHandleRequest = dc.channel('router.layer.handle_request'); +var onHandleError = dc.channel('router.layer.handle_error'); + +function mapProp(prop) { + return function mapped(obj) { + return obj[prop]; + }; +} + +function mapAndJoin(prop) { + return function (list) { + return list.map(mapProp(prop)).join(''); + } +} + +function noop() { } + +describe('diagnostics_channel', function () { + var joinLayerStack = mapAndJoin('routingPath'); + var handleRequest; + var handleError; + + onHandleRequest.subscribe(function (message) { + handleRequest = message; + }); + + onHandleError.subscribe(function (message) { + handleError = message; + }); + + it('use has no layers with a path', function (done) { + var router = new Router(); + + router.use(function (req, res) { + res.end(); + }); + + function end() { + assert.strictEqual(joinLayerStack(handleRequest.request.layerStack), '/'); + done(); + } + + router.handle({ url: '/', method: 'GET' }, { end }, noop); + }); + + it('regular routes have a layer with a path', function (done) { + var router = new Router(); + + router.get('/hello/:name', function (req, res) { + res.end(); + }); + + function end() { + assert.strictEqual(joinLayerStack(handleRequest.request.layerStack), '/hello/:name/'); + done(); + } + + router.handle({ url: '/hello/world', method: 'GET' }, { end }, noop); + }); + + it('nested routes have multiple layers with paths', function (done) { + var outer = new Router(); + var inner = new Router(); + + inner.get('/:name', function (req, res) { + res.end(); + }); + + outer.use('/hello', (req, res, next) => { + next(); + }, inner); + + function end() { + assert.strictEqual(joinLayerStack(handleRequest.request.layerStack), '/hello/:name/'); + done(); + } + + outer.handle({ url: '/hello/world', method: 'GET' }, { end }, noop); + }); + + it('errors send through a different channel', function (done) { + var router = new Router(); + var error = new Error('fail'); + + router.get('/hello/:name', function (req, res) { + throw error; + }); + + router.use(function (err, req, res, next) { + res.end(); + }); + + function end() { + assert.strictEqual(joinLayerStack(handleRequest.request.layerStack), '/hello/:name/'); + assert.strictEqual(handleError.error, error); + done(); + } + + router.handle({ url: '/hello/world', method: 'GET' }, { end }, noop); + }); +});