Skip to content
Closed
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
75 changes: 75 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Copy link
Contributor

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?


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.
Copy link
Contributor

Choose a reason for hiding this comment

The 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')
Copy link
Contributor

Choose a reason for hiding this comment

The 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)
Expand Down
18 changes: 18 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be alphabetical like the rest.


/**
* Module variables.
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 @@ -164,6 +168,7 @@ Router.prototype.handle = function handle(req, res, callback) {
var self = this
var slashAdded = false
var paramcalled = {}
var events = self._events
Copy link
Contributor

Choose a reason for hiding this comment

The 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
Expand All @@ -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 () {
Copy link
Contributor

Choose a reason for hiding this comment

The 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) {
Expand Down Expand Up @@ -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
Expand Down
146 changes: 146 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 @@ -983,8 +984,153 @@ describe('Router', function () {
.expect(200, 'saw GET /bar', done)
})
})

describe('events', function () {

Copy link
Contributor

Choose a reason for hiding this comment

The 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')
Expand Down