diff --git a/lib/router/layer.js b/lib/router/layer.js index 4dc8e86d4f7..e2afc649bcc 100644 --- a/lib/router/layer.js +++ b/lib/router/layer.js @@ -16,6 +16,18 @@ var pathRegexp = require('path-to-regexp'); var debug = require('debug')('express:router:layer'); +/** + * Diagnostic channels + * @private + */ +var onHandleRequest; +var onHandleError; +try { + var dc = require('diagnostics_channel'); + onHandleRequest = dc.channel('express.layer.handle_request'); + onHandleError = dc.channel('express.layer.handle_error'); +} catch (err) {} + /** * Module variables. * @private @@ -43,6 +55,7 @@ function Layer(path, options, fn) { this.params = undefined; this.path = undefined; this.regexp = pathRegexp(path, this.keys = [], opts); + this.routingKey = path; // set fast path flags this.regexp.fast_star = path === '*' @@ -67,9 +80,23 @@ Layer.prototype.handle_error = function handle_error(error, req, res, next) { return next(error); } + req.layerStack = req.layerStack || []; + req.layerStack.push(this); + + if (onHandleError && onHandleError.shouldPublish()) { + onHandleError.publish({ + error: error, + request: req, + response: res, + layer: this + }); + } + try { fn(error, req, res, next); + req.layerStack.pop(); } catch (err) { + req.layerStack.pop(); next(err); } }; @@ -91,9 +118,22 @@ Layer.prototype.handle_request = function handle(req, res, next) { return next(); } + req.layerStack = req.layerStack || []; + req.layerStack.push(this); + + if (onHandleRequest && onHandleRequest.shouldPublish()) { + onHandleRequest.publish({ + request: req, + response: res, + layer: this + }); + } + try { fn(req, res, next); + req.layerStack.pop(); } catch (err) { + req.layerStack.pop(); next(err); } }; diff --git a/test/diagnostics_channel.js b/test/diagnostics_channel.js new file mode 100644 index 00000000000..4f473007587 --- /dev/null +++ b/test/diagnostics_channel.js @@ -0,0 +1,104 @@ + +var express = require('../') + , assert = require('assert'); + +var dc = require('diagnostics_channel'); +var onHandleRequest = dc.channel('express.layer.handle_request'); +var onHandleError = dc.channel('express.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('routingKey'); + 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 express.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 express.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 express.Router(); + var inner = new express.Router(); + + inner.get('/:name', function (req, res) { + res.end(); + }); + + outer.use('/hello', 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 express.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); + }); +});