Skip to content
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

Router events (WIP Attempt 2) #56

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,71 @@ curl http://127.0.0.1:8080/such_path
> such_path
```

## Events

The router emits two events - `layerstart`, and `layerend` - as it processes requests.

### 1. `layerstart`

This event is emitted when the router matches a layer, and starts the middleware stack

Example:

```js
router.on('layerstart', function (req) {
req.layerStartTime = Date.now()
})
```

### 2. `layerend`

This event is emitted when a route layer finishes calling middleware functions

Example:

```js
router.on('layerend', function (req, res, layer) {
console.log('The layer ' + layer.path + ' took ' + (Date.now() - req.layerStartTime) + 'ms')
})
```

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()
})

app.get('/', function (req, res) {
res.send('HELLO')
})

app.router.on('layerstart', function (req, res, layer) {
req.layerStartTime = Date.now()
})

app.router.on('layerend', function (req, res, layer) {
req.layerCounter++;
req.totalTime += Date.now() - req.layerStartTime;
})

app.listen(3000)
```

## License

[MIT](LICENSE)
Expand Down
29 changes: 24 additions & 5 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -275,6 +279,13 @@ Router.prototype.handle = function handle(req, res, callback) {
return done(layerError)
}

// trigger the "layer found" event
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) {
req.route = route
Expand All @@ -289,18 +300,18 @@ 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) {
if (layerPath.length !== 0) {
// Validate path breaks on a path separator
var c = path[layerPath.length]
Expand Down Expand Up @@ -330,9 +341,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)
}
}
}
Expand Down Expand Up @@ -480,6 +491,14 @@ Router.prototype.use = function use(handler) {
// add the middleware
debug('use %o %s', path, fn.name || '<anonymous>')

// 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,
Expand Down
82 changes: 82 additions & 0 deletions test/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -1129,8 +1130,89 @@ describe('Router', function () {
.expect(200, 'saw GET /bar', done)
})
})

describe('events', function () {

describe('"layer"', function () {
it('should pass the request and response objects and the matched layer', function (done) {
var router = new Router()
var server = createServer(router)

router.use(helloWorld)

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()
})

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 start = 0
var end = 0

router.on('layerstart', function () {
start++
})
router.on('layerend', function () {
end++
if (end === handlers.length) {
assert.equal(start, end)
done()
}
})

request(server).get('/').end()
})

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++
})
router1.on('layerend', function () {
end2++
})
router1.on('layerstart', function () {
start1++
})
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()
})
})
})
})

function passThrough(req, res, next) {
next()
}

function helloWorld(req, res) {
res.statusCode = 200
res.setHeader('Content-Type', 'text/plain')
Expand Down