-
-
Notifications
You must be signed in to change notification settings - Fork 118
Add Router events #49
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should not be part of the handleend section. |
||
|
|
||
| ```js | ||
| var express = require('express') | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can the example be a real example instead of just a bunch of console log lines? I.e. demonstrate the use case people will use this for. People love to copy paste, and this is not something they would do that on. |
||
| 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) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should probably be alphabetical like the rest. |
||
|
|
||
| /** | ||
| * 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 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's not peak into the internals of Node.js if it's not required, and I see no reason this is required. If there is, please enlighten me :) |
||
|
|
||
| // 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 () { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the purpose of adding the finish event listener? Especially because there are no tests around all the edge cases this will cause (for example, are we saying that if the response is already finished before the handleend, our events is not supposed to fire?) |
||
| 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 | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 () { | ||
|
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. These tests have a very inconsistent style from all other tests if you can clean them up at some point :) |
||
| 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') | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you define "route layer" in the docs?