From 79f8afad2cae0c36fefb6a98081f5e923a4d0f42 Mon Sep 17 00:00:00 2001 From: Hage Yaapa Date: Mon, 24 Oct 2016 15:53:33 +0530 Subject: [PATCH 1/3] Add Router events The following events were added: 1. `handlestart` - emitted when the router starts to handle the router stack. 2. `layer` - emitted when a layer is macthed by the router. It returns the matched layer. 3. `handleend` - emitted when the router has completed handling the router stack. I am sure very sure about the name of the events, feedback are most welcome. --- README.md | 75 +++++++++++++++++++++++++ index.js | 18 ++++++ test/router.js | 146 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 239 insertions(+) diff --git a/README.md b/README.md index a2e5e016..c77fbd0d 100644 --- a/README.md +++ b/README.md @@ -295,6 +295,81 @@ curl http://127.0.0.1:8080/such_path > such_path ``` +## Events + +The router emits three events - `handlestart`, `layer`, and `handleend` - as it processes requests. + +### 1. `handlestart` + +This event is emitted when the router starts to process a request. + +Example: + +```js +router.on('handlestart', function (req) { + req.id = Math.random().toString(36).slice(2) + console.log('HANDLESTART:', req.id) +}) +``` + +### 2. `layer` + +This event is emitted when a route layer is matched. + +Layers are matched till one of them sends a response back to the client. + +Example: + +```js +router.on('layer', function (req, layer) { + console.log(layer) +}) +``` + +### 3. `handleend` + +This event is emitted when the router has finished processing a request and sent a response. + +Example: + +```js +router.on('handleend', function (req) { + console.log('HANDLEEND:', req.id) +}) +``` + +Here is a complete example of using router events in an Express 5 app. + +```js +var express = require('express') +var app = express() + +app.use(function (req, res, next) { + next() +}) + +app.get('/', function (req, res) { + res.send('HELLO') +}) + +app.router.on('handlestart', function (req) { + req.id = Math.random().toString(36).slice(2) + console.log('HANDLESTART:', req.id) +}) + +app.router.on('layer', function (req, layer) { + if (!('layerCounter' in req)) req.layerCounter = 0 + console.log('LAYER: %s -> %d', req.id, ++req.layerCounter) + // console.log(layer) // uncomment this to log the layer +}) + +app.router.on('handleend', function (req, router) { + console.log('HANDLEEND:', req.id) +}) + +app.listen(3000) +``` + ## License [MIT](LICENSE) diff --git a/index.js b/index.js index b488e585..3e4e43d8 100644 --- a/index.js +++ b/index.js @@ -20,6 +20,7 @@ var mixin = require('utils-merge') var parseUrl = require('parseurl') var Route = require('./lib/route') var setPrototypeOf = require('setprototypeof') +var EventEmitter = require('events').EventEmitter /** * Module variables. @@ -64,6 +65,9 @@ function Router(options) { router.handle(req, res, next) } + // make Router an EventEmitter + mixin(this, EventEmitter.prototype, false) + // inherit from the correct prototype setPrototypeOf(router, this) @@ -164,6 +168,7 @@ Router.prototype.handle = function handle(req, res, callback) { var self = this var slashAdded = false var paramcalled = {} + var events = self._events // middleware and routes var stack = this.stack @@ -186,6 +191,16 @@ Router.prototype.handle = function handle(req, res, callback) { req.baseUrl = parentUrl req.originalUrl = req.originalUrl || req.url + // trigger the "beginning of route handling" event + if (events && 'handlestart' in events) self.emit('handlestart', req) + + // trigger the "end of route handling" event + if (events && 'handleend' in events) { + res.once('finish', function () { + self.emit('handleend', req) + }) + } + next() function next(err) { @@ -269,6 +284,9 @@ Router.prototype.handle = function handle(req, res, callback) { return done(layerError) } + // trigger the "layer found" event + if (events && 'layer' in events) self.emit('layer', req, layer) + // store route for dispatch on change if (route) { req.route = route diff --git a/test/router.js b/test/router.js index 046bd322..e20912e5 100644 --- a/test/router.js +++ b/test/router.js @@ -3,6 +3,7 @@ var after = require('after') var methods = require('methods') var Router = require('..') var utils = require('./support/utils') +var Layer = require('../lib/layer.js') var assert = utils.assert var createHitHandle = utils.createHitHandle @@ -983,8 +984,153 @@ describe('Router', function () { .expect(200, 'saw GET /bar', done) }) }) + + describe('events', function () { + + describe('"handlestart"', function () { + it('should pass the request object', function (done) { + var router = new Router() + var server = createServer(router) + + router.on('handlestart', function (req) { + assert.equal('true', req.headers['x-handlestart']) + done() + }) + + request(server).get('/').set({'x-handlestart': 'true'}).end() + }) + + it('should be emitted at the beginning of handling the router stack', function (done) { + var router = new Router() + var server = createServer(router) + + var handlers = [passThrough, passThrough, passThrough] + router.use(handlers) + + var counter = 0 + router.on('layer', function (layer, req) { + counter++ + }) + router.on('handlestart', function (req) { + assert.equal(0, counter) + done() + }) + + request(server).get('/').end() + }) + }) + + describe('"layer"', function () { + it('should pass the request object and the matched layer', function (done) { + var router = new Router() + var server = createServer(router) + + router.use(passThrough) + + router.on('layer', function (req, layer) { + assert.equal('true', req.headers['x-layer']) + assert(Layer.prototype.isPrototypeOf(layer)) + done() + }) + + request(server).get('/').set({'x-layer': 'true'}).end() + }) + + it('should be emitted for each layer of the router stack', function (done) { + var router = new Router() + var server = createServer(router) + + var handlers = [passThrough, passThrough, passThrough] + router.use(handlers) + + var cb = after(handlers.length, done) + + router.on('layer', function (layer, req) { + cb() + }) + + request(server).get('/').end() + }) + + it('should not be emitted beyond the layer which sends a response', function (done) { + var router = new Router() + var server = createServer(router) + + var handlers = [helloWorld, passThrough, passThrough, passThrough] + router.use(handlers) + + var cb = after(1, done) + + router.on('layer', function (layer, req) { + cb() + }) + + request(server).get('/').end() + }) + + }) + + describe('"handleend"', function () { + it('should pass the request object', function (done) { + var router = new Router() + var server = createServer(router) + + router.use(passThrough) + + router.on('handleend', function (req) { + assert.equal('true', req.headers['x-handleend']) + done() + }) + + request(server).get('/').set({'x-handleend': 'true'}).end() + }) + + it('should be emitted at the end of handling the router stack', function (done) { + var router = new Router() + var server = createServer(router) + + var handlers = [passThrough, passThrough, passThrough] + router.use(handlers) + + var counter = 0 + router.on('layer', function (layer, req) { + counter++ + }) + router.on('handleend', function (req) { + assert.equal(handlers.length, counter) + done() + }) + + request(server).get('/').end() + }) + + it('should be emitted when a response is sent by any of the middleware/handlers', function (done) { + var router = new Router() + var server = createServer(router) + + var handlers = [helloWorld, passThrough, passThrough, passThrough] + router.use(handlers) + + var counter = 0 + router.on('layer', function (layer, req) { + counter++ + }) + router.on('handleend', function (req) { + assert.equal(1, counter) + done() + }) + + request(server).get('/').end() + }) + }) + + }) }) +function passThrough(req, res, next) { + next() +} + function helloWorld(req, res) { res.statusCode = 200 res.setHeader('Content-Type', 'text/plain') From c7babd60aa9676a5456bb429674cfb2cb00633ba Mon Sep 17 00:00:00 2001 From: Wes Todd Date: Sat, 25 Mar 2017 13:52:22 -0500 Subject: [PATCH 2/3] removed handlestart and handleend, added bubbling layerstart and layerend events --- README.md | 60 +++++++++------------ index.js | 39 +++++++------- test/router.js | 142 ++++++++++++++----------------------------------- 3 files changed, 84 insertions(+), 157 deletions(-) diff --git a/README.md b/README.md index c77fbd0d..88f16716 100644 --- a/README.md +++ b/README.md @@ -297,44 +297,29 @@ curl http://127.0.0.1:8080/such_path ## Events -The router emits three events - `handlestart`, `layer`, and `handleend` - as it processes requests. +The router emits two events - `layerstart`, and `layerend` - as it processes requests. -### 1. `handlestart` +### 1. `layerstart` -This event is emitted when the router starts to process a request. +This event is emitted when the router matches a layer, and starts the middleware stack Example: ```js -router.on('handlestart', function (req) { - req.id = Math.random().toString(36).slice(2) - console.log('HANDLESTART:', req.id) -}) -``` - -### 2. `layer` - -This event is emitted when a route layer is matched. - -Layers are matched till one of them sends a response back to the client. - -Example: - -```js -router.on('layer', function (req, layer) { - console.log(layer) +router.on('layerstart', function (req) { + req.layerStartTime = Date.now() }) ``` -### 3. `handleend` +### 2. `layerend` -This event is emitted when the router has finished processing a request and sent a response. +This event is emitted when a route layer finishes calling middleware functions Example: ```js -router.on('handleend', function (req) { - console.log('HANDLEEND:', req.id) +router.on('layerend', function (req, layer) { + console.log('The layer ' + layer.path + ' took ' + (Date.now() - req.layerStartTime) + 'ms') }) ``` @@ -342,9 +327,20 @@ Here is a complete example of using router events in an Express 5 app. ```js var express = require('express') +var onFinished = require('on-finished') var app = express() app.use(function (req, res, next) { + req.id = Math.random().toString(36).slice(2) + req.layerCounter = 0 + req.totalTime = 0 + onFinished(res, function logLayerStats (err) { + console.log('Request ' + req.id + ':') + console.log(' Request Errored: ' + !!err) + console.log(' Layers run: ' + req.layerCounter) + console.log(' Avg Time: ' + req.totalTime / req.layerCounter) + }) + next() }) @@ -352,19 +348,13 @@ app.get('/', function (req, res) { res.send('HELLO') }) -app.router.on('handlestart', function (req) { - req.id = Math.random().toString(36).slice(2) - console.log('HANDLESTART:', req.id) -}) - -app.router.on('layer', function (req, layer) { - if (!('layerCounter' in req)) req.layerCounter = 0 - console.log('LAYER: %s -> %d', req.id, ++req.layerCounter) - // console.log(layer) // uncomment this to log the layer +app.router.on('layerstart', function (req, layer) { + req.layerStartTime = Date.now() }) -app.router.on('handleend', function (req, router) { - console.log('HANDLEEND:', req.id) +app.router.on('layerend', function (req, layer) { + req.layerCounter++; + req.totalTime += Date.now() - req.layerStartTime; }) app.listen(3000) diff --git a/index.js b/index.js index 3e4e43d8..73842fa9 100644 --- a/index.js +++ b/index.js @@ -13,6 +13,7 @@ */ var debug = require('debug')('router') +var EventEmitter = require('events').EventEmitter var flatten = require('array-flatten') var Layer = require('./lib/layer') var methods = require('methods') @@ -20,7 +21,6 @@ var mixin = require('utils-merge') var parseUrl = require('parseurl') var Route = require('./lib/route') var setPrototypeOf = require('setprototypeof') -var EventEmitter = require('events').EventEmitter /** * Module variables. @@ -168,7 +168,6 @@ Router.prototype.handle = function handle(req, res, callback) { var self = this var slashAdded = false var paramcalled = {} - var events = self._events // middleware and routes var stack = this.stack @@ -191,16 +190,6 @@ Router.prototype.handle = function handle(req, res, callback) { req.baseUrl = parentUrl req.originalUrl = req.originalUrl || req.url - // trigger the "beginning of route handling" event - if (events && 'handlestart' in events) self.emit('handlestart', req) - - // trigger the "end of route handling" event - if (events && 'handleend' in events) { - res.once('finish', function () { - self.emit('handleend', req) - }) - } - next() function next(err) { @@ -285,7 +274,11 @@ Router.prototype.handle = function handle(req, res, callback) { } // trigger the "layer found" event - if (events && 'layer' in events) self.emit('layer', req, layer) + self.emit('layerstart', req, res, layer) + var _next = function (err) { + self.emit('layerend', req, res, layer) + next(err) + } // store route for dispatch on change if (route) { @@ -301,22 +294,22 @@ Router.prototype.handle = function handle(req, res, callback) { // this should be done for the layer self.process_params(layer, paramcalled, req, res, function (err) { if (err) { - return next(layerError || err) + return _next(layerError || err) } if (route) { return layer.handle_request(req, res, next) } - trim_prefix(layer, layerError, layerPath, path) + trim_prefix(layer, layerError, layerPath, path, _next) }) } - function trim_prefix(layer, layerError, layerPath, path) { + function trim_prefix(layer, layerError, layerPath, path, _next) { var c = path[layerPath.length] if (c && c !== '/') { - next(layerError) + _next(layerError) return } @@ -342,9 +335,9 @@ Router.prototype.handle = function handle(req, res, callback) { debug('%s %s : %s', layer.name, layerPath, req.originalUrl) if (layerError) { - layer.handle_error(layerError, req, res, next) + layer.handle_error(layerError, req, res, _next) } else { - layer.handle_request(req, res, next) + layer.handle_request(req, res, _next) } } } @@ -497,6 +490,14 @@ Router.prototype.use = function use(handler) { // add the middleware debug('use %s %s', path, fn.name || '') + // If fn looks like another router instance, + // bubble the layerstart and layerend events + // up the tree + if (typeof fn.use === 'function' && typeof fn.emit === 'function') { + this.on('layerstart', fn.emit.bind(fn, 'layerstart')) + this.on('layerend', fn.emit.bind(fn, 'layerend')) + } + var layer = new Layer(path, { sensitive: this.caseSensitive, strict: false, diff --git a/test/router.js b/test/router.js index e20912e5..cc23fbbf 100644 --- a/test/router.js +++ b/test/router.js @@ -987,48 +987,16 @@ describe('Router', function () { describe('events', function () { - describe('"handlestart"', function () { - it('should pass the request object', function (done) { - var router = new Router() - var server = createServer(router) - - router.on('handlestart', function (req) { - assert.equal('true', req.headers['x-handlestart']) - done() - }) - - request(server).get('/').set({'x-handlestart': 'true'}).end() - }) - - it('should be emitted at the beginning of handling the router stack', function (done) { - var router = new Router() - var server = createServer(router) - - var handlers = [passThrough, passThrough, passThrough] - router.use(handlers) - - var counter = 0 - router.on('layer', function (layer, req) { - counter++ - }) - router.on('handlestart', function (req) { - assert.equal(0, counter) - done() - }) - - request(server).get('/').end() - }) - }) - describe('"layer"', function () { - it('should pass the request object and the matched layer', function (done) { + it('should pass the request and response objects and the matched layer', function (done) { var router = new Router() var server = createServer(router) - router.use(passThrough) + router.use(helloWorld) - router.on('layer', function (req, layer) { + router.on('layerstart', function (req, res, layer) { assert.equal('true', req.headers['x-layer']) + assert.equal(200, res.statusCode) assert(Layer.prototype.isPrototypeOf(layer)) done() }) @@ -1042,88 +1010,56 @@ describe('Router', function () { var handlers = [passThrough, passThrough, passThrough] router.use(handlers) + var start = 0 + var end = 0 - var cb = after(handlers.length, done) - - router.on('layer', function (layer, req) { - cb() + router.on('layerstart', function () { + start++ }) - - request(server).get('/').end() - }) - - it('should not be emitted beyond the layer which sends a response', function (done) { - var router = new Router() - var server = createServer(router) - - var handlers = [helloWorld, passThrough, passThrough, passThrough] - router.use(handlers) - - var cb = after(1, done) - - router.on('layer', function (layer, req) { - cb() + router.on('layerend', function () { + end++ + if (end === handlers.length) { + assert.equal(start, end) + done() + } }) request(server).get('/').end() }) - }) - - describe('"handleend"', function () { - it('should pass the request object', function (done) { - var router = new Router() - var server = createServer(router) - - router.use(passThrough) - - router.on('handleend', function (req) { - assert.equal('true', req.headers['x-handleend']) - done() - }) - - request(server).get('/').set({'x-handleend': 'true'}).end() - }) - - it('should be emitted at the end of handling the router stack', function (done) { - var router = new Router() - var server = createServer(router) - - var handlers = [passThrough, passThrough, passThrough] - router.use(handlers) - - var counter = 0 - router.on('layer', function (layer, req) { - counter++ + it('should bubble events on mounted routers', function () { + var router1 = new Router() + var router2 = new Router() + var server = createServer(router1) + router2.use(passThrough, passThrough) + router1.use(router2) + var start1 = 0 + var start2 = 0 + var end1 = 0 + var end2 = 0 + + router2.on('layerstart', function () { + start2++ }) - router.on('handleend', function (req) { - assert.equal(handlers.length, counter) - done() + router1.on('layerend', function () { + end2++ }) - - request(server).get('/').end() - }) - - it('should be emitted when a response is sent by any of the middleware/handlers', function (done) { - var router = new Router() - var server = createServer(router) - - var handlers = [helloWorld, passThrough, passThrough, passThrough] - router.use(handlers) - - var counter = 0 - router.on('layer', function (layer, req) { - counter++ + router1.on('layerstart', function () { + start1++ }) - router.on('handleend', function (req) { - assert.equal(1, counter) - done() + router1.on('layerend', function () { + end1++ + if (end1 === 3) { + assert.equal(start1, 3) + assert.equal(start2, 2) + assert.equal(end2, 2) + done() + } }) request(server).get('/').end() }) }) - }) }) From fc3d6f45847e689902668913c7d26e3bacdcc36d Mon Sep 17 00:00:00 2001 From: Wes Todd Date: Sat, 25 Mar 2017 14:20:12 -0500 Subject: [PATCH 3/3] readme fix --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ba6dc0fd..18888c0b 100644 --- a/README.md +++ b/README.md @@ -355,7 +355,7 @@ This event is emitted when a route layer finishes calling middleware functions Example: ```js -router.on('layerend', function (req, layer) { +router.on('layerend', function (req, res, layer) { console.log('The layer ' + layer.path + ' took ' + (Date.now() - req.layerStartTime) + 'ms') }) ``` @@ -385,11 +385,11 @@ app.get('/', function (req, res) { res.send('HELLO') }) -app.router.on('layerstart', function (req, layer) { +app.router.on('layerstart', function (req, res, layer) { req.layerStartTime = Date.now() }) -app.router.on('layerend', function (req, layer) { +app.router.on('layerend', function (req, res, layer) { req.layerCounter++; req.totalTime += Date.now() - req.layerStartTime; })