pax_global_header00006660000000000000000000000064124065103620014511gustar00rootroot0000000000000052 comment=8cc4b0afde5ab1fdb1f42bcdf80fd1d9dd4e1528 finalhandler-0.3.0/000077500000000000000000000000001240651036200141405ustar00rootroot00000000000000finalhandler-0.3.0/.gitignore000066400000000000000000000000461240651036200161300ustar00rootroot00000000000000coverage/ node_modules/ npm-debug.log finalhandler-0.3.0/.travis.yml000066400000000000000000000003711240651036200162520ustar00rootroot00000000000000language: node_js node_js: - "0.8" - "0.10" - "0.11" matrix: allow_failures: - node_js: "0.11" fast_finish: true script: "npm run-script test-travis" after_script: "npm install coveralls@2.10.0 && cat ./coverage/lcov.info | coveralls" finalhandler-0.3.0/HISTORY.md000066400000000000000000000013251240651036200156240ustar00rootroot000000000000000.3.0 / 2014-09-17 ================== * Terminate in progress response only on error * Use `on-finished` to determine request status 0.2.0 / 2014-09-03 ================== * Set `X-Content-Type-Options: nosniff` header * deps: debug@~2.0.0 0.1.0 / 2014-07-16 ================== * Respond after request fully read - prevents hung responses and socket hang ups * deps: debug@1.0.4 0.0.3 / 2014-07-11 ================== * deps: debug@1.0.3 - Add support for multiple wildcards in namespaces 0.0.2 / 2014-06-19 ================== * Handle invalid status codes 0.0.1 / 2014-06-05 ================== * deps: debug@1.0.2 0.0.0 / 2014-06-05 ================== * Extracted from connect/express finalhandler-0.3.0/LICENSE000066400000000000000000000021321240651036200151430ustar00rootroot00000000000000(The MIT License) Copyright (c) 2014 Douglas Christopher Wilson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. finalhandler-0.3.0/README.md000066400000000000000000000064401240651036200154230ustar00rootroot00000000000000# finalhandler [![NPM Version][npm-image]][npm-url] [![NPM Downloads][downloads-image]][downloads-url] [![Node.js Version][node-image]][node-url] [![Build Status][travis-image]][travis-url] [![Test Coverage][coveralls-image]][coveralls-url] Node.js function to invoke as the final step to respond to HTTP request. ## Installation ```sh $ npm install finalhandler ``` ## API ```js var finalhandler = require('finalhandler') ``` ### finalhandler(req, res, [options]) Returns function to be invoked as the final step for the given `req` and `res`. This function is to be invoked as `fn(err)`. If `err` is falsy, the handler will write out a 404 response to the `res`. If it is truthy, an error response will be written out to the `res`, and `res.statusCode` is set from `err.status`. The final handler will also unpipe anything from `req` when it is invoked. #### options.env By default, the environment is determined by `NODE_ENV` variable, but it can be overridden by this option. #### options.onerror Provide a function to be called with the `err` when it exists. Can be used for writing errors to a central location without excessive function generation. Called as `onerror(err, req, res)`. ## Examples ### always 404 ```js var finalhandler = require('finalhandler') var http = require('http') var server = http.createServer(function (req, res) { var done = finalhandler(req, res) done() }) server.listen(3000) ``` ### perform simple action ```js var finalhandler = require('finalhandler') var fs = require('fs') var http = require('http') var server = http.createServer(function (req, res) { var done = finalhandler(req, res) fs.readFile('index.html', function (err, buf) { if (err) return done(err) res.setHeader('Content-Type', 'text/html') res.end(buf) }) }) server.listen(3000) ``` ### use with middleware-style functions ```js var finalhandler = require('finalhandler') var http = require('http') var serveStatic = require('serve-static') var serve = serveStatic('public') var server = http.createServer(function (req, res) { var done = finalhandler(req, res) serve(req, res, done) }) server.listen(3000) ``` ### keep log of all errors ```js var finalhandler = require('finalhandler') var fs = require('fs') var http = require('http') var server = http.createServer(function (req, res) { var done = finalhandler(req, res, {onerror: logerror}) fs.readFile('index.html', function (err, buf) { if (err) return done(err) res.setHeader('Content-Type', 'text/html') res.end(buf) }) }) server.listen(3000) function logerror(err) { console.error(err.stack || err.toString()) } ``` ## License [MIT](LICENSE) [npm-image]: https://img.shields.io/npm/v/finalhandler.svg?style=flat [npm-url]: https://npmjs.org/package/finalhandler [node-image]: https://img.shields.io/node/v/finalhandler.svg?style=flat [node-url]: http://nodejs.org/download/ [travis-image]: https://img.shields.io/travis/pillarjs/finalhandler.svg?style=flat [travis-url]: https://travis-ci.org/pillarjs/finalhandler [coveralls-image]: https://img.shields.io/coveralls/pillarjs/finalhandler.svg?style=flat [coveralls-url]: https://coveralls.io/r/pillarjs/finalhandler?branch=master [downloads-image]: https://img.shields.io/npm/dm/finalhandler.svg?style=flat [downloads-url]: https://npmjs.org/package/finalhandler finalhandler-0.3.0/index.js000066400000000000000000000065371240651036200156200ustar00rootroot00000000000000/*! * finalhandler * Copyright(c) 2014 Douglas Christopher Wilson * MIT Licensed */ /** * Module dependencies. */ var debug = require('debug')('finalhandler') var escapeHtml = require('escape-html') var http = require('http') var onFinished = require('on-finished') /** * Variables. */ /* istanbul ignore next */ var defer = typeof setImmediate === 'function' ? setImmediate : function(fn){ process.nextTick(fn.bind.apply(fn, arguments)) } var isFinished = onFinished.isFinished /** * Module exports. */ module.exports = finalhandler /** * Final handler: * * @param {Request} req * @param {Response} res * @param {Object} [options] * @return {Function} * @api public */ function finalhandler(req, res, options) { options = options || {} // get environment var env = options.env || process.env.NODE_ENV || 'development' // get error callback var onerror = options.onerror return function (err) { var msg // ignore 404 on in-flight response if (!err && res._header) { debug('cannot 404 after headers sent') return } // unhandled error if (err) { // default status code to 500 if (!res.statusCode || res.statusCode < 400) { res.statusCode = 500 } // respect err.status if (err.status) { res.statusCode = err.status } // production gets a basic error message var msg = env === 'production' ? http.STATUS_CODES[res.statusCode] : err.stack || err.toString() msg = escapeHtml(msg) .replace(/\n/g, '
') .replace(/ /g, '  ') + '\n' } else { res.statusCode = 404 msg = 'Cannot ' + escapeHtml(req.method) + ' ' + escapeHtml(req.originalUrl || req.url) + '\n' } debug('default %s', res.statusCode) // schedule onerror callback if (err && onerror) { defer(onerror, err, req, res) } // cannot actually respond if (res._header) { return req.socket.destroy() } send(req, res, res.statusCode, msg) } } /** * Send response. * * @param {IncomingMessage} req * @param {OutgoingMessage} res * @param {number} status * @param {string} body * @api private */ function send(req, res, status, body) { function write() { res.statusCode = status // security header for content sniffing res.setHeader('X-Content-Type-Options', 'nosniff') // standard headers res.setHeader('Content-Type', 'text/html; charset=utf-8') res.setHeader('Content-Length', Buffer.byteLength(body, 'utf8')) if (req.method === 'HEAD') { res.end() return } res.end(body, 'utf8') } if (isFinished(req)) { write() return } // unpipe everything from the request unpipe(req) // flush the request onFinished(req, write) req.resume() } /** * Unpipe everything from a stream. * * @param {Object} stream * @api private */ /* istanbul ignore next: implementation differs between versions */ function unpipe(stream) { if (typeof stream.unpipe === 'function') { // new-style stream.unpipe() return } // Node.js 0.8 hack var listener var listeners = stream.listeners('close') for (var i = 0; i < listeners.length; i++) { listener = listeners[i] if (listener.name !== 'cleanup' && listener.name !== 'onclose') { continue } // invoke the listener listener.call(stream) } } finalhandler-0.3.0/package.json000066400000000000000000000016161240651036200164320ustar00rootroot00000000000000{ "name": "finalhandler", "description": "Node.js final http responder", "version": "0.3.0", "author": "Douglas Christopher Wilson ", "license": "MIT", "repository": "pillarjs/finalhandler", "dependencies": { "debug": "~2.0.0", "escape-html": "1.0.1", "on-finished": "~2.1.0" }, "devDependencies": { "istanbul": "0.3.2", "mocha": "~1.21.4", "readable-stream": "~1.0.27", "should": "~4.0.1", "supertest": "~0.13.0" }, "files": [ "LICENSE", "HISTORY.md", "index.js" ], "engines": { "node": ">= 0.8" }, "scripts": { "test": "mocha --reporter spec --bail --check-leaks test/", "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/", "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/" } } finalhandler-0.3.0/test/000077500000000000000000000000001240651036200151175ustar00rootroot00000000000000finalhandler-0.3.0/test/test.js000066400000000000000000000160411240651036200164360ustar00rootroot00000000000000 var finalhandler = require('..') var http = require('http') var request = require('supertest') var should = require('should') var stream = require('readable-stream') var util = require('util') describe('finalhandler(req, res)', function () { describe('status code', function () { it('should 404 on no error', function (done) { var server = createServer() request(server) .get('/') .expect(404, done) }) it('should 500 on error', function (done) { var server = createServer(new Error()) request(server) .get('/') .expect(500, done) }) it('should use err.status', function (done) { var err = new Error() err.status = 400 var server = createServer(err) request(server) .get('/') .expect(400, done) }) }) describe('404 response', function () { it('include method and path', function (done) { var server = createServer() request(server) .get('/foo') .expect(404, 'Cannot GET /foo\n', done) }) it('should handle HEAD', function (done) { var server = createServer() request(server) .head('/foo') .expect(404, '', done) }) it('should include security header', function (done) { var server = createServer() request(server) .get('/foo') .expect('X-Content-Type-Options', 'nosniff') .expect(404, done) }) it('should not hang/error if there is a request body', function (done) { var buf = new Buffer(1024 * 16) var server = createServer() var test = request(server).post('/foo') buf.fill('.') test.write(buf) test.write(buf) test.write(buf) test.expect(404, done) }) }) describe('error response', function () { it('should include error stack', function (done) { var server = createServer(new Error('boom!')) request(server) .get('/foo') .expect(500, /^Error: boom!
   at/, done) }) it('should handle HEAD', function (done) { var server = createServer() request(server) .head('/foo') .expect(404, '', done) }) it('should include security header', function (done) { var server = createServer(new Error('boom!')) request(server) .get('/foo') .expect('X-Content-Type-Options', 'nosniff') .expect(500, done) }) it('should handle non-error-objects', function (done) { var server = createServer('lame string') request(server) .get('/foo') .expect(500, 'lame string\n', done) }) it('should send staus code name when production', function (done) { var err = new Error('boom!') err.status = 501 var server = createServer(err, {env: 'production'}) request(server) .get('/foo') .expect(501, 'Not Implemented\n', done) }) describe('when there is a request body', function () { it('should not hang/error when unread', function (done) { var buf = new Buffer(1024 * 16) var server = createServer(new Error('boom!')) var test = request(server).post('/foo') buf.fill('.') test.write(buf) test.write(buf) test.write(buf) test.expect(500, done) }) it('should not hang/error when actively piped', function (done) { var buf = new Buffer(1024 * 16) var server = createServer(function (req, res, next) { req.pipe(stream) process.nextTick(function () { next(new Error('boom!')) }) }) var stream = createSlowWriteStream() var test = request(server).post('/foo') buf.fill('.') test.write(buf) test.write(buf) test.write(buf) test.expect(500, done) }) it('should not hang/error when read', function (done) { var buf = new Buffer(1024 * 16) var server = createServer(function (req, res, next) { // read off the request req.once('end', function () { next(new Error('boom!')) }) req.resume() }) var test = request(server).post('/foo') buf.fill('.') test.write(buf) test.write(buf) test.write(buf) test.expect(500, done) }) }) describe('when res.statusCode set', function () { it('should keep when > 400', function (done) { var server = http.createServer(function (req, res) { var done = finalhandler(req, res) res.statusCode = 503 done(new Error('oops')) }) request(server) .get('/foo') .expect(503, done) }) it('should override with err.status', function (done) { var server = http.createServer(function (req, res) { var done = finalhandler(req, res) var err = new Error('oops') res.statusCode = 503 err.status = 414 done(err) }) request(server) .get('/foo') .expect(414, done) }) }) describe('when res.statusCode undefined', function () { it('should set to 500', function (done) { var server = http.createServer(function (req, res) { var done = finalhandler(req, res) res.statusCode = undefined done(new Error('oops')) }) request(server) .get('/foo') .expect(500, done) }) }) }) describe('request started', function () { it('should not respond', function (done) { var server = http.createServer(function (req, res) { var done = finalhandler(req, res) res.statusCode = 301 res.write('0') process.nextTick(function () { done() res.end('1') }) }) request(server) .get('/foo') .expect(301, '01', done) }) it('should terminate on error', function (done) { var server = http.createServer(function (req, res) { var done = finalhandler(req, res) res.statusCode = 301 res.write('0') process.nextTick(function () { done(new Error('boom!')) res.end('1') }) }) request(server) .get('/foo') .expect(301, '0', done) }) }) describe('onerror', function () { it('should be invoked when error', function (done) { var err = new Error('boom!') var error var log = function (e) { error = e } var server = createServer(err, {onerror: log}) request(server) .get('/') .end(function () { should(error).equal(err) done() }) }) }) }) function createServer(err, opts) { return http.createServer(function (req, res) { var done = finalhandler(req, res, opts) if (typeof err === 'function') { err(req, res, done) return } done(err) }) } function createSlowWriteStream() { return new SlowWriteStream() } function SlowWriteStream() { stream.Writable.call(this) } util.inherits(SlowWriteStream, stream.Writable) SlowWriteStream.prototype._write = function _write(chunk, encoding, callback) { setTimeout(callback, 1000) }