second
[josuexyz/.git] / node_modules / express / lib / response.js
1 /*!
2  * express
3  * Copyright(c) 2009-2013 TJ Holowaychuk
4  * Copyright(c) 2014-2015 Douglas Christopher Wilson
5  * MIT Licensed
6  */
7
8 'use strict';
9
10 /**
11  * Module dependencies.
12  * @private
13  */
14
15 var Buffer = require('safe-buffer').Buffer
16 var contentDisposition = require('content-disposition');
17 var deprecate = require('depd')('express');
18 var encodeUrl = require('encodeurl');
19 var escapeHtml = require('escape-html');
20 var http = require('http');
21 var isAbsolute = require('./utils').isAbsolute;
22 var onFinished = require('on-finished');
23 var path = require('path');
24 var statuses = require('statuses')
25 var merge = require('utils-merge');
26 var sign = require('cookie-signature').sign;
27 var normalizeType = require('./utils').normalizeType;
28 var normalizeTypes = require('./utils').normalizeTypes;
29 var setCharset = require('./utils').setCharset;
30 var cookie = require('cookie');
31 var send = require('send');
32 var extname = path.extname;
33 var mime = send.mime;
34 var resolve = path.resolve;
35 var vary = require('vary');
36
37 /**
38  * Response prototype.
39  * @public
40  */
41
42 var res = Object.create(http.ServerResponse.prototype)
43
44 /**
45  * Module exports.
46  * @public
47  */
48
49 module.exports = res
50
51 /**
52  * Module variables.
53  * @private
54  */
55
56 var charsetRegExp = /;\s*charset\s*=/;
57
58 /**
59  * Set status `code`.
60  *
61  * @param {Number} code
62  * @return {ServerResponse}
63  * @public
64  */
65
66 res.status = function status(code) {
67   this.statusCode = code;
68   return this;
69 };
70
71 /**
72  * Set Link header field with the given `links`.
73  *
74  * Examples:
75  *
76  *    res.links({
77  *      next: 'http://api.example.com/users?page=2',
78  *      last: 'http://api.example.com/users?page=5'
79  *    });
80  *
81  * @param {Object} links
82  * @return {ServerResponse}
83  * @public
84  */
85
86 res.links = function(links){
87   var link = this.get('Link') || '';
88   if (link) link += ', ';
89   return this.set('Link', link + Object.keys(links).map(function(rel){
90     return '<' + links[rel] + '>; rel="' + rel + '"';
91   }).join(', '));
92 };
93
94 /**
95  * Send a response.
96  *
97  * Examples:
98  *
99  *     res.send(Buffer.from('wahoo'));
100  *     res.send({ some: 'json' });
101  *     res.send('<p>some html</p>');
102  *
103  * @param {string|number|boolean|object|Buffer} body
104  * @public
105  */
106
107 res.send = function send(body) {
108   var chunk = body;
109   var encoding;
110   var req = this.req;
111   var type;
112
113   // settings
114   var app = this.app;
115
116   // allow status / body
117   if (arguments.length === 2) {
118     // res.send(body, status) backwards compat
119     if (typeof arguments[0] !== 'number' && typeof arguments[1] === 'number') {
120       deprecate('res.send(body, status): Use res.status(status).send(body) instead');
121       this.statusCode = arguments[1];
122     } else {
123       deprecate('res.send(status, body): Use res.status(status).send(body) instead');
124       this.statusCode = arguments[0];
125       chunk = arguments[1];
126     }
127   }
128
129   // disambiguate res.send(status) and res.send(status, num)
130   if (typeof chunk === 'number' && arguments.length === 1) {
131     // res.send(status) will set status message as text string
132     if (!this.get('Content-Type')) {
133       this.type('txt');
134     }
135
136     deprecate('res.send(status): Use res.sendStatus(status) instead');
137     this.statusCode = chunk;
138     chunk = statuses[chunk]
139   }
140
141   switch (typeof chunk) {
142     // string defaulting to html
143     case 'string':
144       if (!this.get('Content-Type')) {
145         this.type('html');
146       }
147       break;
148     case 'boolean':
149     case 'number':
150     case 'object':
151       if (chunk === null) {
152         chunk = '';
153       } else if (Buffer.isBuffer(chunk)) {
154         if (!this.get('Content-Type')) {
155           this.type('bin');
156         }
157       } else {
158         return this.json(chunk);
159       }
160       break;
161   }
162
163   // write strings in utf-8
164   if (typeof chunk === 'string') {
165     encoding = 'utf8';
166     type = this.get('Content-Type');
167
168     // reflect this in content-type
169     if (typeof type === 'string') {
170       this.set('Content-Type', setCharset(type, 'utf-8'));
171     }
172   }
173
174   // determine if ETag should be generated
175   var etagFn = app.get('etag fn')
176   var generateETag = !this.get('ETag') && typeof etagFn === 'function'
177
178   // populate Content-Length
179   var len
180   if (chunk !== undefined) {
181     if (Buffer.isBuffer(chunk)) {
182       // get length of Buffer
183       len = chunk.length
184     } else if (!generateETag && chunk.length < 1000) {
185       // just calculate length when no ETag + small chunk
186       len = Buffer.byteLength(chunk, encoding)
187     } else {
188       // convert chunk to Buffer and calculate
189       chunk = Buffer.from(chunk, encoding)
190       encoding = undefined;
191       len = chunk.length
192     }
193
194     this.set('Content-Length', len);
195   }
196
197   // populate ETag
198   var etag;
199   if (generateETag && len !== undefined) {
200     if ((etag = etagFn(chunk, encoding))) {
201       this.set('ETag', etag);
202     }
203   }
204
205   // freshness
206   if (req.fresh) this.statusCode = 304;
207
208   // strip irrelevant headers
209   if (204 === this.statusCode || 304 === this.statusCode) {
210     this.removeHeader('Content-Type');
211     this.removeHeader('Content-Length');
212     this.removeHeader('Transfer-Encoding');
213     chunk = '';
214   }
215
216   if (req.method === 'HEAD') {
217     // skip body for HEAD
218     this.end();
219   } else {
220     // respond
221     this.end(chunk, encoding);
222   }
223
224   return this;
225 };
226
227 /**
228  * Send JSON response.
229  *
230  * Examples:
231  *
232  *     res.json(null);
233  *     res.json({ user: 'tj' });
234  *
235  * @param {string|number|boolean|object} obj
236  * @public
237  */
238
239 res.json = function json(obj) {
240   var val = obj;
241
242   // allow status / body
243   if (arguments.length === 2) {
244     // res.json(body, status) backwards compat
245     if (typeof arguments[1] === 'number') {
246       deprecate('res.json(obj, status): Use res.status(status).json(obj) instead');
247       this.statusCode = arguments[1];
248     } else {
249       deprecate('res.json(status, obj): Use res.status(status).json(obj) instead');
250       this.statusCode = arguments[0];
251       val = arguments[1];
252     }
253   }
254
255   // settings
256   var app = this.app;
257   var escape = app.get('json escape')
258   var replacer = app.get('json replacer');
259   var spaces = app.get('json spaces');
260   var body = stringify(val, replacer, spaces, escape)
261
262   // content-type
263   if (!this.get('Content-Type')) {
264     this.set('Content-Type', 'application/json');
265   }
266
267   return this.send(body);
268 };
269
270 /**
271  * Send JSON response with JSONP callback support.
272  *
273  * Examples:
274  *
275  *     res.jsonp(null);
276  *     res.jsonp({ user: 'tj' });
277  *
278  * @param {string|number|boolean|object} obj
279  * @public
280  */
281
282 res.jsonp = function jsonp(obj) {
283   var val = obj;
284
285   // allow status / body
286   if (arguments.length === 2) {
287     // res.json(body, status) backwards compat
288     if (typeof arguments[1] === 'number') {
289       deprecate('res.jsonp(obj, status): Use res.status(status).json(obj) instead');
290       this.statusCode = arguments[1];
291     } else {
292       deprecate('res.jsonp(status, obj): Use res.status(status).jsonp(obj) instead');
293       this.statusCode = arguments[0];
294       val = arguments[1];
295     }
296   }
297
298   // settings
299   var app = this.app;
300   var escape = app.get('json escape')
301   var replacer = app.get('json replacer');
302   var spaces = app.get('json spaces');
303   var body = stringify(val, replacer, spaces, escape)
304   var callback = this.req.query[app.get('jsonp callback name')];
305
306   // content-type
307   if (!this.get('Content-Type')) {
308     this.set('X-Content-Type-Options', 'nosniff');
309     this.set('Content-Type', 'application/json');
310   }
311
312   // fixup callback
313   if (Array.isArray(callback)) {
314     callback = callback[0];
315   }
316
317   // jsonp
318   if (typeof callback === 'string' && callback.length !== 0) {
319     this.set('X-Content-Type-Options', 'nosniff');
320     this.set('Content-Type', 'text/javascript');
321
322     // restrict callback charset
323     callback = callback.replace(/[^\[\]\w$.]/g, '');
324
325     // replace chars not allowed in JavaScript that are in JSON
326     body = body
327       .replace(/\u2028/g, '\\u2028')
328       .replace(/\u2029/g, '\\u2029');
329
330     // the /**/ is a specific security mitigation for "Rosetta Flash JSONP abuse"
331     // the typeof check is just to reduce client error noise
332     body = '/**/ typeof ' + callback + ' === \'function\' && ' + callback + '(' + body + ');';
333   }
334
335   return this.send(body);
336 };
337
338 /**
339  * Send given HTTP status code.
340  *
341  * Sets the response status to `statusCode` and the body of the
342  * response to the standard description from node's http.STATUS_CODES
343  * or the statusCode number if no description.
344  *
345  * Examples:
346  *
347  *     res.sendStatus(200);
348  *
349  * @param {number} statusCode
350  * @public
351  */
352
353 res.sendStatus = function sendStatus(statusCode) {
354   var body = statuses[statusCode] || String(statusCode)
355
356   this.statusCode = statusCode;
357   this.type('txt');
358
359   return this.send(body);
360 };
361
362 /**
363  * Transfer the file at the given `path`.
364  *
365  * Automatically sets the _Content-Type_ response header field.
366  * The callback `callback(err)` is invoked when the transfer is complete
367  * or when an error occurs. Be sure to check `res.sentHeader`
368  * if you wish to attempt responding, as the header and some data
369  * may have already been transferred.
370  *
371  * Options:
372  *
373  *   - `maxAge`   defaulting to 0 (can be string converted by `ms`)
374  *   - `root`     root directory for relative filenames
375  *   - `headers`  object of headers to serve with file
376  *   - `dotfiles` serve dotfiles, defaulting to false; can be `"allow"` to send them
377  *
378  * Other options are passed along to `send`.
379  *
380  * Examples:
381  *
382  *  The following example illustrates how `res.sendFile()` may
383  *  be used as an alternative for the `static()` middleware for
384  *  dynamic situations. The code backing `res.sendFile()` is actually
385  *  the same code, so HTTP cache support etc is identical.
386  *
387  *     app.get('/user/:uid/photos/:file', function(req, res){
388  *       var uid = req.params.uid
389  *         , file = req.params.file;
390  *
391  *       req.user.mayViewFilesFrom(uid, function(yes){
392  *         if (yes) {
393  *           res.sendFile('/uploads/' + uid + '/' + file);
394  *         } else {
395  *           res.send(403, 'Sorry! you cant see that.');
396  *         }
397  *       });
398  *     });
399  *
400  * @public
401  */
402
403 res.sendFile = function sendFile(path, options, callback) {
404   var done = callback;
405   var req = this.req;
406   var res = this;
407   var next = req.next;
408   var opts = options || {};
409
410   if (!path) {
411     throw new TypeError('path argument is required to res.sendFile');
412   }
413
414   // support function as second arg
415   if (typeof options === 'function') {
416     done = options;
417     opts = {};
418   }
419
420   if (!opts.root && !isAbsolute(path)) {
421     throw new TypeError('path must be absolute or specify root to res.sendFile');
422   }
423
424   // create file stream
425   var pathname = encodeURI(path);
426   var file = send(req, pathname, opts);
427
428   // transfer
429   sendfile(res, file, opts, function (err) {
430     if (done) return done(err);
431     if (err && err.code === 'EISDIR') return next();
432
433     // next() all but write errors
434     if (err && err.code !== 'ECONNABORTED' && err.syscall !== 'write') {
435       next(err);
436     }
437   });
438 };
439
440 /**
441  * Transfer the file at the given `path`.
442  *
443  * Automatically sets the _Content-Type_ response header field.
444  * The callback `callback(err)` is invoked when the transfer is complete
445  * or when an error occurs. Be sure to check `res.sentHeader`
446  * if you wish to attempt responding, as the header and some data
447  * may have already been transferred.
448  *
449  * Options:
450  *
451  *   - `maxAge`   defaulting to 0 (can be string converted by `ms`)
452  *   - `root`     root directory for relative filenames
453  *   - `headers`  object of headers to serve with file
454  *   - `dotfiles` serve dotfiles, defaulting to false; can be `"allow"` to send them
455  *
456  * Other options are passed along to `send`.
457  *
458  * Examples:
459  *
460  *  The following example illustrates how `res.sendfile()` may
461  *  be used as an alternative for the `static()` middleware for
462  *  dynamic situations. The code backing `res.sendfile()` is actually
463  *  the same code, so HTTP cache support etc is identical.
464  *
465  *     app.get('/user/:uid/photos/:file', function(req, res){
466  *       var uid = req.params.uid
467  *         , file = req.params.file;
468  *
469  *       req.user.mayViewFilesFrom(uid, function(yes){
470  *         if (yes) {
471  *           res.sendfile('/uploads/' + uid + '/' + file);
472  *         } else {
473  *           res.send(403, 'Sorry! you cant see that.');
474  *         }
475  *       });
476  *     });
477  *
478  * @public
479  */
480
481 res.sendfile = function (path, options, callback) {
482   var done = callback;
483   var req = this.req;
484   var res = this;
485   var next = req.next;
486   var opts = options || {};
487
488   // support function as second arg
489   if (typeof options === 'function') {
490     done = options;
491     opts = {};
492   }
493
494   // create file stream
495   var file = send(req, path, opts);
496
497   // transfer
498   sendfile(res, file, opts, function (err) {
499     if (done) return done(err);
500     if (err && err.code === 'EISDIR') return next();
501
502     // next() all but write errors
503     if (err && err.code !== 'ECONNABORTED' && err.syscall !== 'write') {
504       next(err);
505     }
506   });
507 };
508
509 res.sendfile = deprecate.function(res.sendfile,
510   'res.sendfile: Use res.sendFile instead');
511
512 /**
513  * Transfer the file at the given `path` as an attachment.
514  *
515  * Optionally providing an alternate attachment `filename`,
516  * and optional callback `callback(err)`. The callback is invoked
517  * when the data transfer is complete, or when an error has
518  * ocurred. Be sure to check `res.headersSent` if you plan to respond.
519  *
520  * Optionally providing an `options` object to use with `res.sendFile()`.
521  * This function will set the `Content-Disposition` header, overriding
522  * any `Content-Disposition` header passed as header options in order
523  * to set the attachment and filename.
524  *
525  * This method uses `res.sendFile()`.
526  *
527  * @public
528  */
529
530 res.download = function download (path, filename, options, callback) {
531   var done = callback;
532   var name = filename;
533   var opts = options || null
534
535   // support function as second or third arg
536   if (typeof filename === 'function') {
537     done = filename;
538     name = null;
539     opts = null
540   } else if (typeof options === 'function') {
541     done = options
542     opts = null
543   }
544
545   // set Content-Disposition when file is sent
546   var headers = {
547     'Content-Disposition': contentDisposition(name || path)
548   };
549
550   // merge user-provided headers
551   if (opts && opts.headers) {
552     var keys = Object.keys(opts.headers)
553     for (var i = 0; i < keys.length; i++) {
554       var key = keys[i]
555       if (key.toLowerCase() !== 'content-disposition') {
556         headers[key] = opts.headers[key]
557       }
558     }
559   }
560
561   // merge user-provided options
562   opts = Object.create(opts)
563   opts.headers = headers
564
565   // Resolve the full path for sendFile
566   var fullPath = resolve(path);
567
568   // send file
569   return this.sendFile(fullPath, opts, done)
570 };
571
572 /**
573  * Set _Content-Type_ response header with `type` through `mime.lookup()`
574  * when it does not contain "/", or set the Content-Type to `type` otherwise.
575  *
576  * Examples:
577  *
578  *     res.type('.html');
579  *     res.type('html');
580  *     res.type('json');
581  *     res.type('application/json');
582  *     res.type('png');
583  *
584  * @param {String} type
585  * @return {ServerResponse} for chaining
586  * @public
587  */
588
589 res.contentType =
590 res.type = function contentType(type) {
591   var ct = type.indexOf('/') === -1
592     ? mime.lookup(type)
593     : type;
594
595   return this.set('Content-Type', ct);
596 };
597
598 /**
599  * Respond to the Acceptable formats using an `obj`
600  * of mime-type callbacks.
601  *
602  * This method uses `req.accepted`, an array of
603  * acceptable types ordered by their quality values.
604  * When "Accept" is not present the _first_ callback
605  * is invoked, otherwise the first match is used. When
606  * no match is performed the server responds with
607  * 406 "Not Acceptable".
608  *
609  * Content-Type is set for you, however if you choose
610  * you may alter this within the callback using `res.type()`
611  * or `res.set('Content-Type', ...)`.
612  *
613  *    res.format({
614  *      'text/plain': function(){
615  *        res.send('hey');
616  *      },
617  *
618  *      'text/html': function(){
619  *        res.send('<p>hey</p>');
620  *      },
621  *
622  *      'appliation/json': function(){
623  *        res.send({ message: 'hey' });
624  *      }
625  *    });
626  *
627  * In addition to canonicalized MIME types you may
628  * also use extnames mapped to these types:
629  *
630  *    res.format({
631  *      text: function(){
632  *        res.send('hey');
633  *      },
634  *
635  *      html: function(){
636  *        res.send('<p>hey</p>');
637  *      },
638  *
639  *      json: function(){
640  *        res.send({ message: 'hey' });
641  *      }
642  *    });
643  *
644  * By default Express passes an `Error`
645  * with a `.status` of 406 to `next(err)`
646  * if a match is not made. If you provide
647  * a `.default` callback it will be invoked
648  * instead.
649  *
650  * @param {Object} obj
651  * @return {ServerResponse} for chaining
652  * @public
653  */
654
655 res.format = function(obj){
656   var req = this.req;
657   var next = req.next;
658
659   var fn = obj.default;
660   if (fn) delete obj.default;
661   var keys = Object.keys(obj);
662
663   var key = keys.length > 0
664     ? req.accepts(keys)
665     : false;
666
667   this.vary("Accept");
668
669   if (key) {
670     this.set('Content-Type', normalizeType(key).value);
671     obj[key](req, this, next);
672   } else if (fn) {
673     fn();
674   } else {
675     var err = new Error('Not Acceptable');
676     err.status = err.statusCode = 406;
677     err.types = normalizeTypes(keys).map(function(o){ return o.value });
678     next(err);
679   }
680
681   return this;
682 };
683
684 /**
685  * Set _Content-Disposition_ header to _attachment_ with optional `filename`.
686  *
687  * @param {String} filename
688  * @return {ServerResponse}
689  * @public
690  */
691
692 res.attachment = function attachment(filename) {
693   if (filename) {
694     this.type(extname(filename));
695   }
696
697   this.set('Content-Disposition', contentDisposition(filename));
698
699   return this;
700 };
701
702 /**
703  * Append additional header `field` with value `val`.
704  *
705  * Example:
706  *
707  *    res.append('Link', ['<http://localhost/>', '<http://localhost:3000/>']);
708  *    res.append('Set-Cookie', 'foo=bar; Path=/; HttpOnly');
709  *    res.append('Warning', '199 Miscellaneous warning');
710  *
711  * @param {String} field
712  * @param {String|Array} val
713  * @return {ServerResponse} for chaining
714  * @public
715  */
716
717 res.append = function append(field, val) {
718   var prev = this.get(field);
719   var value = val;
720
721   if (prev) {
722     // concat the new and prev vals
723     value = Array.isArray(prev) ? prev.concat(val)
724       : Array.isArray(val) ? [prev].concat(val)
725       : [prev, val];
726   }
727
728   return this.set(field, value);
729 };
730
731 /**
732  * Set header `field` to `val`, or pass
733  * an object of header fields.
734  *
735  * Examples:
736  *
737  *    res.set('Foo', ['bar', 'baz']);
738  *    res.set('Accept', 'application/json');
739  *    res.set({ Accept: 'text/plain', 'X-API-Key': 'tobi' });
740  *
741  * Aliased as `res.header()`.
742  *
743  * @param {String|Object} field
744  * @param {String|Array} val
745  * @return {ServerResponse} for chaining
746  * @public
747  */
748
749 res.set =
750 res.header = function header(field, val) {
751   if (arguments.length === 2) {
752     var value = Array.isArray(val)
753       ? val.map(String)
754       : String(val);
755
756     // add charset to content-type
757     if (field.toLowerCase() === 'content-type') {
758       if (Array.isArray(value)) {
759         throw new TypeError('Content-Type cannot be set to an Array');
760       }
761       if (!charsetRegExp.test(value)) {
762         var charset = mime.charsets.lookup(value.split(';')[0]);
763         if (charset) value += '; charset=' + charset.toLowerCase();
764       }
765     }
766
767     this.setHeader(field, value);
768   } else {
769     for (var key in field) {
770       this.set(key, field[key]);
771     }
772   }
773   return this;
774 };
775
776 /**
777  * Get value for header `field`.
778  *
779  * @param {String} field
780  * @return {String}
781  * @public
782  */
783
784 res.get = function(field){
785   return this.getHeader(field);
786 };
787
788 /**
789  * Clear cookie `name`.
790  *
791  * @param {String} name
792  * @param {Object} [options]
793  * @return {ServerResponse} for chaining
794  * @public
795  */
796
797 res.clearCookie = function clearCookie(name, options) {
798   var opts = merge({ expires: new Date(1), path: '/' }, options);
799
800   return this.cookie(name, '', opts);
801 };
802
803 /**
804  * Set cookie `name` to `value`, with the given `options`.
805  *
806  * Options:
807  *
808  *    - `maxAge`   max-age in milliseconds, converted to `expires`
809  *    - `signed`   sign the cookie
810  *    - `path`     defaults to "/"
811  *
812  * Examples:
813  *
814  *    // "Remember Me" for 15 minutes
815  *    res.cookie('rememberme', '1', { expires: new Date(Date.now() + 900000), httpOnly: true });
816  *
817  *    // save as above
818  *    res.cookie('rememberme', '1', { maxAge: 900000, httpOnly: true })
819  *
820  * @param {String} name
821  * @param {String|Object} value
822  * @param {Object} [options]
823  * @return {ServerResponse} for chaining
824  * @public
825  */
826
827 res.cookie = function (name, value, options) {
828   var opts = merge({}, options);
829   var secret = this.req.secret;
830   var signed = opts.signed;
831
832   if (signed && !secret) {
833     throw new Error('cookieParser("secret") required for signed cookies');
834   }
835
836   var val = typeof value === 'object'
837     ? 'j:' + JSON.stringify(value)
838     : String(value);
839
840   if (signed) {
841     val = 's:' + sign(val, secret);
842   }
843
844   if ('maxAge' in opts) {
845     opts.expires = new Date(Date.now() + opts.maxAge);
846     opts.maxAge /= 1000;
847   }
848
849   if (opts.path == null) {
850     opts.path = '/';
851   }
852
853   this.append('Set-Cookie', cookie.serialize(name, String(val), opts));
854
855   return this;
856 };
857
858 /**
859  * Set the location header to `url`.
860  *
861  * The given `url` can also be "back", which redirects
862  * to the _Referrer_ or _Referer_ headers or "/".
863  *
864  * Examples:
865  *
866  *    res.location('/foo/bar').;
867  *    res.location('http://example.com');
868  *    res.location('../login');
869  *
870  * @param {String} url
871  * @return {ServerResponse} for chaining
872  * @public
873  */
874
875 res.location = function location(url) {
876   var loc = url;
877
878   // "back" is an alias for the referrer
879   if (url === 'back') {
880     loc = this.req.get('Referrer') || '/';
881   }
882
883   // set location
884   return this.set('Location', encodeUrl(loc));
885 };
886
887 /**
888  * Redirect to the given `url` with optional response `status`
889  * defaulting to 302.
890  *
891  * The resulting `url` is determined by `res.location()`, so
892  * it will play nicely with mounted apps, relative paths,
893  * `"back"` etc.
894  *
895  * Examples:
896  *
897  *    res.redirect('/foo/bar');
898  *    res.redirect('http://example.com');
899  *    res.redirect(301, 'http://example.com');
900  *    res.redirect('../login'); // /blog/post/1 -> /blog/login
901  *
902  * @public
903  */
904
905 res.redirect = function redirect(url) {
906   var address = url;
907   var body;
908   var status = 302;
909
910   // allow status / url
911   if (arguments.length === 2) {
912     if (typeof arguments[0] === 'number') {
913       status = arguments[0];
914       address = arguments[1];
915     } else {
916       deprecate('res.redirect(url, status): Use res.redirect(status, url) instead');
917       status = arguments[1];
918     }
919   }
920
921   // Set location header
922   address = this.location(address).get('Location');
923
924   // Support text/{plain,html} by default
925   this.format({
926     text: function(){
927       body = statuses[status] + '. Redirecting to ' + address
928     },
929
930     html: function(){
931       var u = escapeHtml(address);
932       body = '<p>' + statuses[status] + '. Redirecting to <a href="' + u + '">' + u + '</a></p>'
933     },
934
935     default: function(){
936       body = '';
937     }
938   });
939
940   // Respond
941   this.statusCode = status;
942   this.set('Content-Length', Buffer.byteLength(body));
943
944   if (this.req.method === 'HEAD') {
945     this.end();
946   } else {
947     this.end(body);
948   }
949 };
950
951 /**
952  * Add `field` to Vary. If already present in the Vary set, then
953  * this call is simply ignored.
954  *
955  * @param {Array|String} field
956  * @return {ServerResponse} for chaining
957  * @public
958  */
959
960 res.vary = function(field){
961   // checks for back-compat
962   if (!field || (Array.isArray(field) && !field.length)) {
963     deprecate('res.vary(): Provide a field name');
964     return this;
965   }
966
967   vary(this, field);
968
969   return this;
970 };
971
972 /**
973  * Render `view` with the given `options` and optional callback `fn`.
974  * When a callback function is given a response will _not_ be made
975  * automatically, otherwise a response of _200_ and _text/html_ is given.
976  *
977  * Options:
978  *
979  *  - `cache`     boolean hinting to the engine it should cache
980  *  - `filename`  filename of the view being rendered
981  *
982  * @public
983  */
984
985 res.render = function render(view, options, callback) {
986   var app = this.req.app;
987   var done = callback;
988   var opts = options || {};
989   var req = this.req;
990   var self = this;
991
992   // support callback function as second arg
993   if (typeof options === 'function') {
994     done = options;
995     opts = {};
996   }
997
998   // merge res.locals
999   opts._locals = self.locals;
1000
1001   // default callback to respond
1002   done = done || function (err, str) {
1003     if (err) return req.next(err);
1004     self.send(str);
1005   };
1006
1007   // render
1008   app.render(view, opts, done);
1009 };
1010
1011 // pipe the send file stream
1012 function sendfile(res, file, options, callback) {
1013   var done = false;
1014   var streaming;
1015
1016   // request aborted
1017   function onaborted() {
1018     if (done) return;
1019     done = true;
1020
1021     var err = new Error('Request aborted');
1022     err.code = 'ECONNABORTED';
1023     callback(err);
1024   }
1025
1026   // directory
1027   function ondirectory() {
1028     if (done) return;
1029     done = true;
1030
1031     var err = new Error('EISDIR, read');
1032     err.code = 'EISDIR';
1033     callback(err);
1034   }
1035
1036   // errors
1037   function onerror(err) {
1038     if (done) return;
1039     done = true;
1040     callback(err);
1041   }
1042
1043   // ended
1044   function onend() {
1045     if (done) return;
1046     done = true;
1047     callback();
1048   }
1049
1050   // file
1051   function onfile() {
1052     streaming = false;
1053   }
1054
1055   // finished
1056   function onfinish(err) {
1057     if (err && err.code === 'ECONNRESET') return onaborted();
1058     if (err) return onerror(err);
1059     if (done) return;
1060
1061     setImmediate(function () {
1062       if (streaming !== false && !done) {
1063         onaborted();
1064         return;
1065       }
1066
1067       if (done) return;
1068       done = true;
1069       callback();
1070     });
1071   }
1072
1073   // streaming
1074   function onstream() {
1075     streaming = true;
1076   }
1077
1078   file.on('directory', ondirectory);
1079   file.on('end', onend);
1080   file.on('error', onerror);
1081   file.on('file', onfile);
1082   file.on('stream', onstream);
1083   onFinished(res, onfinish);
1084
1085   if (options.headers) {
1086     // set headers on successful transfer
1087     file.on('headers', function headers(res) {
1088       var obj = options.headers;
1089       var keys = Object.keys(obj);
1090
1091       for (var i = 0; i < keys.length; i++) {
1092         var k = keys[i];
1093         res.setHeader(k, obj[k]);
1094       }
1095     });
1096   }
1097
1098   // pipe
1099   file.pipe(res);
1100 }
1101
1102 /**
1103  * Stringify JSON, like JSON.stringify, but v8 optimized, with the
1104  * ability to escape characters that can trigger HTML sniffing.
1105  *
1106  * @param {*} value
1107  * @param {function} replaces
1108  * @param {number} spaces
1109  * @param {boolean} escape
1110  * @returns {string}
1111  * @private
1112  */
1113
1114 function stringify (value, replacer, spaces, escape) {
1115   // v8 checks arguments.length for optimizing simple call
1116   // https://bugs.chromium.org/p/v8/issues/detail?id=4730
1117   var json = replacer || spaces
1118     ? JSON.stringify(value, replacer, spaces)
1119     : JSON.stringify(value);
1120
1121   if (escape) {
1122     json = json.replace(/[<>&]/g, function (c) {
1123       switch (c.charCodeAt(0)) {
1124         case 0x3c:
1125           return '\\u003c'
1126         case 0x3e:
1127           return '\\u003e'
1128         case 0x26:
1129           return '\\u0026'
1130         default:
1131           return c
1132       }
1133     })
1134   }
1135
1136   return json
1137 }