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

Prevent next from being called multiple times #26

Closed
wants to merge 19 commits into from
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
27 changes: 25 additions & 2 deletions lib/layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ Layer.prototype.handle_error = function handle_error(error, req, res, next) {
}

try {
fn(error, req, res, next)
fn(error, req, res, once(next))
} catch (err) {
next(err)
}
Expand All @@ -90,7 +90,7 @@ Layer.prototype.handle_request = function handle(req, res, next) {
}

try {
fn(req, res, next)
fn(req, res, once(next))
} catch (err) {
next(err)
}
Expand Down Expand Up @@ -173,3 +173,26 @@ function decode_param(val){
throw err
}
}

/**
* Makes next function only callable without an error parameter once
*
* @param {function} next
* @return {function}
* @private
*/

function once(fn) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This function needs jsdoc comment.

var called = false

return function (err) {
if (!err) {
if (called) {
throw new Error('next() cannot be called twice')
}
called = true
}

fn.call(this, err)
}
}
100 changes: 100 additions & 0 deletions test/middleware.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@

var after = require('after')
var Router = require('..')
var utils = require('./support/utils')

var assert = utils.assert
var createHitHandle = utils.createHitHandle
var createErrorHitHandle = utils.createErrorHitHandle
var shouldHitHandle = utils.shouldHitHandle
var shouldNotHitHandle = utils.shouldNotHitHandle
var manyAsyncCalls = utils.manyAsyncCalls
var createServer = utils.createServer
var request = utils.request

describe('middleware', function () {
it('cannot call the next function twice', function (done) {
var router = Router()
var server = createServer(router)
done = after(2, done)

router.use(function (req, res, next) {
next()
next()
})

router.use(function (req, res, next) { res.end() })
router.use(createHitHandle(1, { checkHeadersSent: true }))
router.use(createErrorHitHandle(2, { checkHeadersSent: true }))

router.use(function (error, req, res, next) {
assert.equal(error.message, 'next() cannot be called twice')
done()
})

request(server)
.get('/')
.expect(shouldNotHitHandle(1))
.expect(shouldNotHitHandle(2))
.expect(200, done)
})

it('cannot call the next function twice in an error handler', function (done) {
var router = Router()
var server = createServer(router)
done = after(2, done)

router.use(function (req, res, next) {
next(new Error('Happy error'))
})

router.use(function (error, req, res, next) {
next()
next()
})

router.use(function (req, res, next) { res.end() })
router.use(createHitHandle(1, { checkHeadersSent: true }))
router.use(createErrorHitHandle(2, { checkHeadersSent: true }))

router.use(function (error, req, res, next) {
assert.equal(error.message, 'next() cannot be called twice')
done()
})

request(server)
.get('/')
.expect(shouldNotHitHandle(1))
.expect(shouldNotHitHandle(2))
.expect(200, done)
})

it('can call next multiple times with an error', function (done) {
var router = Router()
var server = createServer(router)
done = after(3, done)

router.use(function (req, res, next) {
next(new Error('1'))
next(new Error('2'))
res.end()
})

router.use(createHitHandle(1, { checkHeadersSent: true }))

router.use(function (error, req, res, next) {
assert.equal(error.message, '1')
done()
})

router.use(function (error, req, res, next) {
assert.equal(error.message, '2')
done()
})

request(server)
.get('/')
.expect(shouldNotHitHandle(1))
.expect(200, done)
})
})
17 changes: 14 additions & 3 deletions test/support/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,31 @@ var request = require('supertest')

exports.assert = assert
exports.createHitHandle = createHitHandle
exports.createErrorHitHandle = createErrorHitHandle
exports.createServer = createServer
exports.rawrequest = rawrequest
exports.request = request
exports.shouldHitHandle = shouldHitHandle
exports.shouldNotHitHandle = shouldNotHitHandle

function createHitHandle(num) {
function createHitHandle(num, options) {
options = options || {}
var name = 'x-fn-' + String(num)
return function hit(req, res, next) {
res.setHeader(name, 'hit')
if (!options.checkHeadersSent || (options.checkHeadersSent && res._headers)) {
res.setHeader(name, 'hit')
}
next()
}
}

function createErrorHitHandle(num, options) {
var hit = createHitHandle(num, options)
return function hitError(error, req, res, next) {
hit(req, res, function () { next(error) })
}
}

function createServer(router) {
return http.createServer(function onRequest(req, res) {
router(req, res, finalhandler(req, res))
Expand Down Expand Up @@ -88,7 +99,7 @@ function rawrequest(server) {
function shouldHitHandle(num) {
var header = 'x-fn-' + String(num)
return function (res) {
assert.equal(res.headers[header], 'hit')
assert.equal(res.headers[header], 'hit', 'header ' + header + ' was included in the response')
}
}

Expand Down