deps: finalhandler@0.5.1
[express.git] / lib / response.js
blob6128f450a94903c222804ad18757d9b273610d26
1 /*!
2  * express
3  * Copyright(c) 2009-2013 TJ Holowaychuk
4  * Copyright(c) 2014-2015 Douglas Christopher Wilson
5  * MIT Licensed
6  */
8 'use strict';
10 /**
11  * Module dependencies.
12  * @private
13  */
15 var contentDisposition = require('content-disposition');
16 var deprecate = require('depd')('express');
17 var encodeUrl = require('encodeurl');
18 var escapeHtml = require('escape-html');
19 var http = require('http');
20 var isAbsolute = require('./utils').isAbsolute;
21 var onFinished = require('on-finished');
22 var path = require('path');
23 var merge = require('utils-merge');
24 var sign = require('cookie-signature').sign;
25 var normalizeType = require('./utils').normalizeType;
26 var normalizeTypes = require('./utils').normalizeTypes;
27 var setCharset = require('./utils').setCharset;
28 var statusCodes = http.STATUS_CODES;
29 var cookie = require('cookie');
30 var send = require('send');
31 var extname = path.extname;
32 var mime = send.mime;
33 var resolve = path.resolve;
34 var vary = require('vary');
36 /**
37  * Response prototype.
38  */
40 var res = module.exports = {
41   __proto__: http.ServerResponse.prototype
44 /**
45  * Module variables.
46  * @private
47  */
49 var charsetRegExp = /;\s*charset\s*=/;
51 /**
52  * Set status `code`.
53  *
54  * @param {Number} code
55  * @return {ServerResponse}
56  * @public
57  */
59 res.status = function status(code) {
60   this.statusCode = code;
61   return this;
64 /**
65  * Set Link header field with the given `links`.
66  *
67  * Examples:
68  *
69  *    res.links({
70  *      next: 'http://api.example.com/users?page=2',
71  *      last: 'http://api.example.com/users?page=5'
72  *    });
73  *
74  * @param {Object} links
75  * @return {ServerResponse}
76  * @public
77  */
79 res.links = function(links){
80   var link = this.get('Link') || '';
81   if (link) link += ', ';
82   return this.set('Link', link + Object.keys(links).map(function(rel){
83     return '<' + links[rel] + '>; rel="' + rel + '"';
84   }).join(', '));
87 /**
88  * Send a response.
89  *
90  * Examples:
91  *
92  *     res.send(new Buffer('wahoo'));
93  *     res.send({ some: 'json' });
94  *     res.send('<p>some html</p>');
95  *
96  * @param {string|number|boolean|object|Buffer} body
97  * @public
98  */
100 res.send = function send(body) {
101   var chunk = body;
102   var encoding;
103   var len;
104   var req = this.req;
105   var type;
107   // settings
108   var app = this.app;
110   // allow status / body
111   if (arguments.length === 2) {
112     // res.send(body, status) backwards compat
113     if (typeof arguments[0] !== 'number' && typeof arguments[1] === 'number') {
114       deprecate('res.send(body, status): Use res.status(status).send(body) instead');
115       this.statusCode = arguments[1];
116     } else {
117       deprecate('res.send(status, body): Use res.status(status).send(body) instead');
118       this.statusCode = arguments[0];
119       chunk = arguments[1];
120     }
121   }
123   // disambiguate res.send(status) and res.send(status, num)
124   if (typeof chunk === 'number' && arguments.length === 1) {
125     // res.send(status) will set status message as text string
126     if (!this.get('Content-Type')) {
127       this.type('txt');
128     }
130     deprecate('res.send(status): Use res.sendStatus(status) instead');
131     this.statusCode = chunk;
132     chunk = statusCodes[chunk];
133   }
135   switch (typeof chunk) {
136     // string defaulting to html
137     case 'string':
138       if (!this.get('Content-Type')) {
139         this.type('html');
140       }
141       break;
142     case 'boolean':
143     case 'number':
144     case 'object':
145       if (chunk === null) {
146         chunk = '';
147       } else if (Buffer.isBuffer(chunk)) {
148         if (!this.get('Content-Type')) {
149           this.type('bin');
150         }
151       } else {
152         return this.json(chunk);
153       }
154       break;
155   }
157   // write strings in utf-8
158   if (typeof chunk === 'string') {
159     encoding = 'utf8';
160     type = this.get('Content-Type');
162     // reflect this in content-type
163     if (typeof type === 'string') {
164       this.set('Content-Type', setCharset(type, 'utf-8'));
165     }
166   }
168   // populate Content-Length
169   if (chunk !== undefined) {
170     if (!Buffer.isBuffer(chunk)) {
171       // convert chunk to Buffer; saves later double conversions
172       chunk = new Buffer(chunk, encoding);
173       encoding = undefined;
174     }
176     len = chunk.length;
177     this.set('Content-Length', len);
178   }
180   // populate ETag
181   var etag;
182   var generateETag = len !== undefined && app.get('etag fn');
183   if (typeof generateETag === 'function' && !this.get('ETag')) {
184     if ((etag = generateETag(chunk, encoding))) {
185       this.set('ETag', etag);
186     }
187   }
189   // freshness
190   if (req.fresh) this.statusCode = 304;
192   // strip irrelevant headers
193   if (204 === this.statusCode || 304 === this.statusCode) {
194     this.removeHeader('Content-Type');
195     this.removeHeader('Content-Length');
196     this.removeHeader('Transfer-Encoding');
197     chunk = '';
198   }
200   if (req.method === 'HEAD') {
201     // skip body for HEAD
202     this.end();
203   } else {
204     // respond
205     this.end(chunk, encoding);
206   }
208   return this;
212  * Send JSON response.
214  * Examples:
216  *     res.json(null);
217  *     res.json({ user: 'tj' });
219  * @param {string|number|boolean|object} obj
220  * @public
221  */
223 res.json = function json(obj) {
224   var val = obj;
226   // allow status / body
227   if (arguments.length === 2) {
228     // res.json(body, status) backwards compat
229     if (typeof arguments[1] === 'number') {
230       deprecate('res.json(obj, status): Use res.status(status).json(obj) instead');
231       this.statusCode = arguments[1];
232     } else {
233       deprecate('res.json(status, obj): Use res.status(status).json(obj) instead');
234       this.statusCode = arguments[0];
235       val = arguments[1];
236     }
237   }
239   // settings
240   var app = this.app;
241   var replacer = app.get('json replacer');
242   var spaces = app.get('json spaces');
243   var body = stringify(val, replacer, spaces);
245   // content-type
246   if (!this.get('Content-Type')) {
247     this.set('Content-Type', 'application/json');
248   }
250   return this.send(body);
254  * Send JSON response with JSONP callback support.
256  * Examples:
258  *     res.jsonp(null);
259  *     res.jsonp({ user: 'tj' });
261  * @param {string|number|boolean|object} obj
262  * @public
263  */
265 res.jsonp = function jsonp(obj) {
266   var val = obj;
268   // allow status / body
269   if (arguments.length === 2) {
270     // res.json(body, status) backwards compat
271     if (typeof arguments[1] === 'number') {
272       deprecate('res.jsonp(obj, status): Use res.status(status).json(obj) instead');
273       this.statusCode = arguments[1];
274     } else {
275       deprecate('res.jsonp(status, obj): Use res.status(status).jsonp(obj) instead');
276       this.statusCode = arguments[0];
277       val = arguments[1];
278     }
279   }
281   // settings
282   var app = this.app;
283   var replacer = app.get('json replacer');
284   var spaces = app.get('json spaces');
285   var body = stringify(val, replacer, spaces);
286   var callback = this.req.query[app.get('jsonp callback name')];
288   // content-type
289   if (!this.get('Content-Type')) {
290     this.set('X-Content-Type-Options', 'nosniff');
291     this.set('Content-Type', 'application/json');
292   }
294   // fixup callback
295   if (Array.isArray(callback)) {
296     callback = callback[0];
297   }
299   // jsonp
300   if (typeof callback === 'string' && callback.length !== 0) {
301     this.charset = 'utf-8';
302     this.set('X-Content-Type-Options', 'nosniff');
303     this.set('Content-Type', 'text/javascript');
305     // restrict callback charset
306     callback = callback.replace(/[^\[\]\w$.]/g, '');
308     // replace chars not allowed in JavaScript that are in JSON
309     body = body
310       .replace(/\u2028/g, '\\u2028')
311       .replace(/\u2029/g, '\\u2029');
313     // the /**/ is a specific security mitigation for "Rosetta Flash JSONP abuse"
314     // the typeof check is just to reduce client error noise
315     body = '/**/ typeof ' + callback + ' === \'function\' && ' + callback + '(' + body + ');';
316   }
318   return this.send(body);
322  * Send given HTTP status code.
324  * Sets the response status to `statusCode` and the body of the
325  * response to the standard description from node's http.STATUS_CODES
326  * or the statusCode number if no description.
328  * Examples:
330  *     res.sendStatus(200);
332  * @param {number} statusCode
333  * @public
334  */
336 res.sendStatus = function sendStatus(statusCode) {
337   var body = statusCodes[statusCode] || String(statusCode);
339   this.statusCode = statusCode;
340   this.type('txt');
342   return this.send(body);
346  * Transfer the file at the given `path`.
348  * Automatically sets the _Content-Type_ response header field.
349  * The callback `callback(err)` is invoked when the transfer is complete
350  * or when an error occurs. Be sure to check `res.sentHeader`
351  * if you wish to attempt responding, as the header and some data
352  * may have already been transferred.
354  * Options:
356  *   - `maxAge`   defaulting to 0 (can be string converted by `ms`)
357  *   - `root`     root directory for relative filenames
358  *   - `headers`  object of headers to serve with file
359  *   - `dotfiles` serve dotfiles, defaulting to false; can be `"allow"` to send them
361  * Other options are passed along to `send`.
363  * Examples:
365  *  The following example illustrates how `res.sendFile()` may
366  *  be used as an alternative for the `static()` middleware for
367  *  dynamic situations. The code backing `res.sendFile()` is actually
368  *  the same code, so HTTP cache support etc is identical.
370  *     app.get('/user/:uid/photos/:file', function(req, res){
371  *       var uid = req.params.uid
372  *         , file = req.params.file;
374  *       req.user.mayViewFilesFrom(uid, function(yes){
375  *         if (yes) {
376  *           res.sendFile('/uploads/' + uid + '/' + file);
377  *         } else {
378  *           res.send(403, 'Sorry! you cant see that.');
379  *         }
380  *       });
381  *     });
383  * @public
384  */
386 res.sendFile = function sendFile(path, options, callback) {
387   var done = callback;
388   var req = this.req;
389   var res = this;
390   var next = req.next;
391   var opts = options || {};
393   if (!path) {
394     throw new TypeError('path argument is required to res.sendFile');
395   }
397   // support function as second arg
398   if (typeof options === 'function') {
399     done = options;
400     opts = {};
401   }
403   if (!opts.root && !isAbsolute(path)) {
404     throw new TypeError('path must be absolute or specify root to res.sendFile');
405   }
407   // create file stream
408   var pathname = encodeURI(path);
409   var file = send(req, pathname, opts);
411   // transfer
412   sendfile(res, file, opts, function (err) {
413     if (done) return done(err);
414     if (err && err.code === 'EISDIR') return next();
416     // next() all but write errors
417     if (err && err.code !== 'ECONNABORTED' && err.syscall !== 'write') {
418       next(err);
419     }
420   });
424  * Transfer the file at the given `path`.
426  * Automatically sets the _Content-Type_ response header field.
427  * The callback `callback(err)` is invoked when the transfer is complete
428  * or when an error occurs. Be sure to check `res.sentHeader`
429  * if you wish to attempt responding, as the header and some data
430  * may have already been transferred.
432  * Options:
434  *   - `maxAge`   defaulting to 0 (can be string converted by `ms`)
435  *   - `root`     root directory for relative filenames
436  *   - `headers`  object of headers to serve with file
437  *   - `dotfiles` serve dotfiles, defaulting to false; can be `"allow"` to send them
439  * Other options are passed along to `send`.
441  * Examples:
443  *  The following example illustrates how `res.sendfile()` may
444  *  be used as an alternative for the `static()` middleware for
445  *  dynamic situations. The code backing `res.sendfile()` is actually
446  *  the same code, so HTTP cache support etc is identical.
448  *     app.get('/user/:uid/photos/:file', function(req, res){
449  *       var uid = req.params.uid
450  *         , file = req.params.file;
452  *       req.user.mayViewFilesFrom(uid, function(yes){
453  *         if (yes) {
454  *           res.sendfile('/uploads/' + uid + '/' + file);
455  *         } else {
456  *           res.send(403, 'Sorry! you cant see that.');
457  *         }
458  *       });
459  *     });
461  * @public
462  */
464 res.sendfile = function (path, options, callback) {
465   var done = callback;
466   var req = this.req;
467   var res = this;
468   var next = req.next;
469   var opts = options || {};
471   // support function as second arg
472   if (typeof options === 'function') {
473     done = options;
474     opts = {};
475   }
477   // create file stream
478   var file = send(req, path, opts);
480   // transfer
481   sendfile(res, file, opts, function (err) {
482     if (done) return done(err);
483     if (err && err.code === 'EISDIR') return next();
485     // next() all but write errors
486     if (err && err.code !== 'ECONNABORT' && err.syscall !== 'write') {
487       next(err);
488     }
489   });
492 res.sendfile = deprecate.function(res.sendfile,
493   'res.sendfile: Use res.sendFile instead');
496  * Transfer the file at the given `path` as an attachment.
498  * Optionally providing an alternate attachment `filename`,
499  * and optional callback `callback(err)`. The callback is invoked
500  * when the data transfer is complete, or when an error has
501  * ocurred. Be sure to check `res.headersSent` if you plan to respond.
503  * This method uses `res.sendfile()`.
505  * @public
506  */
508 res.download = function download(path, filename, callback) {
509   var done = callback;
510   var name = filename;
512   // support function as second arg
513   if (typeof filename === 'function') {
514     done = filename;
515     name = null;
516   }
518   // set Content-Disposition when file is sent
519   var headers = {
520     'Content-Disposition': contentDisposition(name || path)
521   };
523   // Resolve the full path for sendFile
524   var fullPath = resolve(path);
526   return this.sendFile(fullPath, { headers: headers }, done);
530  * Set _Content-Type_ response header with `type` through `mime.lookup()`
531  * when it does not contain "/", or set the Content-Type to `type` otherwise.
533  * Examples:
535  *     res.type('.html');
536  *     res.type('html');
537  *     res.type('json');
538  *     res.type('application/json');
539  *     res.type('png');
541  * @param {String} type
542  * @return {ServerResponse} for chaining
543  * @public
544  */
546 res.contentType =
547 res.type = function contentType(type) {
548   var ct = type.indexOf('/') === -1
549     ? mime.lookup(type)
550     : type;
552   return this.set('Content-Type', ct);
556  * Respond to the Acceptable formats using an `obj`
557  * of mime-type callbacks.
559  * This method uses `req.accepted`, an array of
560  * acceptable types ordered by their quality values.
561  * When "Accept" is not present the _first_ callback
562  * is invoked, otherwise the first match is used. When
563  * no match is performed the server responds with
564  * 406 "Not Acceptable".
566  * Content-Type is set for you, however if you choose
567  * you may alter this within the callback using `res.type()`
568  * or `res.set('Content-Type', ...)`.
570  *    res.format({
571  *      'text/plain': function(){
572  *        res.send('hey');
573  *      },
575  *      'text/html': function(){
576  *        res.send('<p>hey</p>');
577  *      },
579  *      'appliation/json': function(){
580  *        res.send({ message: 'hey' });
581  *      }
582  *    });
584  * In addition to canonicalized MIME types you may
585  * also use extnames mapped to these types:
587  *    res.format({
588  *      text: function(){
589  *        res.send('hey');
590  *      },
592  *      html: function(){
593  *        res.send('<p>hey</p>');
594  *      },
596  *      json: function(){
597  *        res.send({ message: 'hey' });
598  *      }
599  *    });
601  * By default Express passes an `Error`
602  * with a `.status` of 406 to `next(err)`
603  * if a match is not made. If you provide
604  * a `.default` callback it will be invoked
605  * instead.
607  * @param {Object} obj
608  * @return {ServerResponse} for chaining
609  * @public
610  */
612 res.format = function(obj){
613   var req = this.req;
614   var next = req.next;
616   var fn = obj.default;
617   if (fn) delete obj.default;
618   var keys = Object.keys(obj);
620   var key = keys.length > 0
621     ? req.accepts(keys)
622     : false;
624   this.vary("Accept");
626   if (key) {
627     this.set('Content-Type', normalizeType(key).value);
628     obj[key](req, this, next);
629   } else if (fn) {
630     fn();
631   } else {
632     var err = new Error('Not Acceptable');
633     err.status = err.statusCode = 406;
634     err.types = normalizeTypes(keys).map(function(o){ return o.value });
635     next(err);
636   }
638   return this;
642  * Set _Content-Disposition_ header to _attachment_ with optional `filename`.
644  * @param {String} filename
645  * @return {ServerResponse}
646  * @public
647  */
649 res.attachment = function attachment(filename) {
650   if (filename) {
651     this.type(extname(filename));
652   }
654   this.set('Content-Disposition', contentDisposition(filename));
656   return this;
660  * Append additional header `field` with value `val`.
662  * Example:
664  *    res.append('Link', ['<http://localhost/>', '<http://localhost:3000/>']);
665  *    res.append('Set-Cookie', 'foo=bar; Path=/; HttpOnly');
666  *    res.append('Warning', '199 Miscellaneous warning');
668  * @param {String} field
669  * @param {String|Array} val
670  * @return {ServerResponse} for chaining
671  * @public
672  */
674 res.append = function append(field, val) {
675   var prev = this.get(field);
676   var value = val;
678   if (prev) {
679     // concat the new and prev vals
680     value = Array.isArray(prev) ? prev.concat(val)
681       : Array.isArray(val) ? [prev].concat(val)
682       : [prev, val];
683   }
685   return this.set(field, value);
689  * Set header `field` to `val`, or pass
690  * an object of header fields.
692  * Examples:
694  *    res.set('Foo', ['bar', 'baz']);
695  *    res.set('Accept', 'application/json');
696  *    res.set({ Accept: 'text/plain', 'X-API-Key': 'tobi' });
698  * Aliased as `res.header()`.
700  * @param {String|Object} field
701  * @param {String|Array} val
702  * @return {ServerResponse} for chaining
703  * @public
704  */
706 res.set =
707 res.header = function header(field, val) {
708   if (arguments.length === 2) {
709     var value = Array.isArray(val)
710       ? val.map(String)
711       : String(val);
713     // add charset to content-type
714     if (field.toLowerCase() === 'content-type' && !charsetRegExp.test(value)) {
715       var charset = mime.charsets.lookup(value.split(';')[0]);
716       if (charset) value += '; charset=' + charset.toLowerCase();
717     }
719     this.setHeader(field, value);
720   } else {
721     for (var key in field) {
722       this.set(key, field[key]);
723     }
724   }
725   return this;
729  * Get value for header `field`.
731  * @param {String} field
732  * @return {String}
733  * @public
734  */
736 res.get = function(field){
737   return this.getHeader(field);
741  * Clear cookie `name`.
743  * @param {String} name
744  * @param {Object} [options]
745  * @return {ServerResponse} for chaining
746  * @public
747  */
749 res.clearCookie = function clearCookie(name, options) {
750   var opts = merge({ expires: new Date(1), path: '/' }, options);
752   return this.cookie(name, '', opts);
756  * Set cookie `name` to `value`, with the given `options`.
758  * Options:
760  *    - `maxAge`   max-age in milliseconds, converted to `expires`
761  *    - `signed`   sign the cookie
762  *    - `path`     defaults to "/"
764  * Examples:
766  *    // "Remember Me" for 15 minutes
767  *    res.cookie('rememberme', '1', { expires: new Date(Date.now() + 900000), httpOnly: true });
769  *    // save as above
770  *    res.cookie('rememberme', '1', { maxAge: 900000, httpOnly: true })
772  * @param {String} name
773  * @param {String|Object} value
774  * @param {Options} options
775  * @return {ServerResponse} for chaining
776  * @public
777  */
779 res.cookie = function (name, value, options) {
780   var opts = merge({}, options);
781   var secret = this.req.secret;
782   var signed = opts.signed;
784   if (signed && !secret) {
785     throw new Error('cookieParser("secret") required for signed cookies');
786   }
788   var val = typeof value === 'object'
789     ? 'j:' + JSON.stringify(value)
790     : String(value);
792   if (signed) {
793     val = 's:' + sign(val, secret);
794   }
796   if ('maxAge' in opts) {
797     opts.expires = new Date(Date.now() + opts.maxAge);
798     opts.maxAge /= 1000;
799   }
801   if (opts.path == null) {
802     opts.path = '/';
803   }
805   this.append('Set-Cookie', cookie.serialize(name, String(val), opts));
807   return this;
811  * Set the location header to `url`.
813  * The given `url` can also be "back", which redirects
814  * to the _Referrer_ or _Referer_ headers or "/".
816  * Examples:
818  *    res.location('/foo/bar').;
819  *    res.location('http://example.com');
820  *    res.location('../login');
822  * @param {String} url
823  * @return {ServerResponse} for chaining
824  * @public
825  */
827 res.location = function location(url) {
828   var loc = url;
830   // "back" is an alias for the referrer
831   if (url === 'back') {
832     loc = this.req.get('Referrer') || '/';
833   }
835   // set location
836   return this.set('Location', encodeUrl(loc));
840  * Redirect to the given `url` with optional response `status`
841  * defaulting to 302.
843  * The resulting `url` is determined by `res.location()`, so
844  * it will play nicely with mounted apps, relative paths,
845  * `"back"` etc.
847  * Examples:
849  *    res.redirect('/foo/bar');
850  *    res.redirect('http://example.com');
851  *    res.redirect(301, 'http://example.com');
852  *    res.redirect('../login'); // /blog/post/1 -> /blog/login
854  * @public
855  */
857 res.redirect = function redirect(url) {
858   var address = url;
859   var body;
860   var status = 302;
862   // allow status / url
863   if (arguments.length === 2) {
864     if (typeof arguments[0] === 'number') {
865       status = arguments[0];
866       address = arguments[1];
867     } else {
868       deprecate('res.redirect(url, status): Use res.redirect(status, url) instead');
869       status = arguments[1];
870     }
871   }
873   // Set location header
874   address = this.location(address).get('Location');
876   // Support text/{plain,html} by default
877   this.format({
878     text: function(){
879       body = statusCodes[status] + '. Redirecting to ' + address;
880     },
882     html: function(){
883       var u = escapeHtml(address);
884       body = '<p>' + statusCodes[status] + '. Redirecting to <a href="' + u + '">' + u + '</a></p>';
885     },
887     default: function(){
888       body = '';
889     }
890   });
892   // Respond
893   this.statusCode = status;
894   this.set('Content-Length', Buffer.byteLength(body));
896   if (this.req.method === 'HEAD') {
897     this.end();
898   } else {
899     this.end(body);
900   }
904  * Add `field` to Vary. If already present in the Vary set, then
905  * this call is simply ignored.
907  * @param {Array|String} field
908  * @return {ServerResponse} for chaining
909  * @public
910  */
912 res.vary = function(field){
913   // checks for back-compat
914   if (!field || (Array.isArray(field) && !field.length)) {
915     deprecate('res.vary(): Provide a field name');
916     return this;
917   }
919   vary(this, field);
921   return this;
925  * Render `view` with the given `options` and optional callback `fn`.
926  * When a callback function is given a response will _not_ be made
927  * automatically, otherwise a response of _200_ and _text/html_ is given.
929  * Options:
931  *  - `cache`     boolean hinting to the engine it should cache
932  *  - `filename`  filename of the view being rendered
934  * @public
935  */
937 res.render = function render(view, options, callback) {
938   var app = this.req.app;
939   var done = callback;
940   var opts = options || {};
941   var req = this.req;
942   var self = this;
944   // support callback function as second arg
945   if (typeof options === 'function') {
946     done = options;
947     opts = {};
948   }
950   // merge res.locals
951   opts._locals = self.locals;
953   // default callback to respond
954   done = done || function (err, str) {
955     if (err) return req.next(err);
956     self.send(str);
957   };
959   // render
960   app.render(view, opts, done);
963 // pipe the send file stream
964 function sendfile(res, file, options, callback) {
965   var done = false;
966   var streaming;
968   // request aborted
969   function onaborted() {
970     if (done) return;
971     done = true;
973     var err = new Error('Request aborted');
974     err.code = 'ECONNABORTED';
975     callback(err);
976   }
978   // directory
979   function ondirectory() {
980     if (done) return;
981     done = true;
983     var err = new Error('EISDIR, read');
984     err.code = 'EISDIR';
985     callback(err);
986   }
988   // errors
989   function onerror(err) {
990     if (done) return;
991     done = true;
992     callback(err);
993   }
995   // ended
996   function onend() {
997     if (done) return;
998     done = true;
999     callback();
1000   }
1002   // file
1003   function onfile() {
1004     streaming = false;
1005   }
1007   // finished
1008   function onfinish(err) {
1009     if (err && err.code === 'ECONNRESET') return onaborted();
1010     if (err) return onerror(err);
1011     if (done) return;
1013     setImmediate(function () {
1014       if (streaming !== false && !done) {
1015         onaborted();
1016         return;
1017       }
1019       if (done) return;
1020       done = true;
1021       callback();
1022     });
1023   }
1025   // streaming
1026   function onstream() {
1027     streaming = true;
1028   }
1030   file.on('directory', ondirectory);
1031   file.on('end', onend);
1032   file.on('error', onerror);
1033   file.on('file', onfile);
1034   file.on('stream', onstream);
1035   onFinished(res, onfinish);
1037   if (options.headers) {
1038     // set headers on successful transfer
1039     file.on('headers', function headers(res) {
1040       var obj = options.headers;
1041       var keys = Object.keys(obj);
1043       for (var i = 0; i < keys.length; i++) {
1044         var k = keys[i];
1045         res.setHeader(k, obj[k]);
1046       }
1047     });
1048   }
1050   // pipe
1051   file.pipe(res);
1055  * Stringify JSON, like JSON.stringify, but v8 optimized.
1056  * @private
1057  */
1059 function stringify(value, replacer, spaces) {
1060   // v8 checks arguments.length for optimizing simple call
1061   // https://bugs.chromium.org/p/v8/issues/detail?id=4730
1062   return replacer || spaces
1063     ? JSON.stringify(value, replacer, spaces)
1064     : JSON.stringify(value);