twitst4tz

twitter statistics web application
Log | Files | Refs | README | LICENSE

response.js (27124B)


      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   if (typeof path !== 'string') {
    415     throw new TypeError('path must be a string to res.sendFile')
    416   }
    417 
    418   // support function as second arg
    419   if (typeof options === 'function') {
    420     done = options;
    421     opts = {};
    422   }
    423 
    424   if (!opts.root && !isAbsolute(path)) {
    425     throw new TypeError('path must be absolute or specify root to res.sendFile');
    426   }
    427 
    428   // create file stream
    429   var pathname = encodeURI(path);
    430   var file = send(req, pathname, opts);
    431 
    432   // transfer
    433   sendfile(res, file, opts, function (err) {
    434     if (done) return done(err);
    435     if (err && err.code === 'EISDIR') return next();
    436 
    437     // next() all but write errors
    438     if (err && err.code !== 'ECONNABORTED' && err.syscall !== 'write') {
    439       next(err);
    440     }
    441   });
    442 };
    443 
    444 /**
    445  * Transfer the file at the given `path`.
    446  *
    447  * Automatically sets the _Content-Type_ response header field.
    448  * The callback `callback(err)` is invoked when the transfer is complete
    449  * or when an error occurs. Be sure to check `res.sentHeader`
    450  * if you wish to attempt responding, as the header and some data
    451  * may have already been transferred.
    452  *
    453  * Options:
    454  *
    455  *   - `maxAge`   defaulting to 0 (can be string converted by `ms`)
    456  *   - `root`     root directory for relative filenames
    457  *   - `headers`  object of headers to serve with file
    458  *   - `dotfiles` serve dotfiles, defaulting to false; can be `"allow"` to send them
    459  *
    460  * Other options are passed along to `send`.
    461  *
    462  * Examples:
    463  *
    464  *  The following example illustrates how `res.sendfile()` may
    465  *  be used as an alternative for the `static()` middleware for
    466  *  dynamic situations. The code backing `res.sendfile()` is actually
    467  *  the same code, so HTTP cache support etc is identical.
    468  *
    469  *     app.get('/user/:uid/photos/:file', function(req, res){
    470  *       var uid = req.params.uid
    471  *         , file = req.params.file;
    472  *
    473  *       req.user.mayViewFilesFrom(uid, function(yes){
    474  *         if (yes) {
    475  *           res.sendfile('/uploads/' + uid + '/' + file);
    476  *         } else {
    477  *           res.send(403, 'Sorry! you cant see that.');
    478  *         }
    479  *       });
    480  *     });
    481  *
    482  * @public
    483  */
    484 
    485 res.sendfile = function (path, options, callback) {
    486   var done = callback;
    487   var req = this.req;
    488   var res = this;
    489   var next = req.next;
    490   var opts = options || {};
    491 
    492   // support function as second arg
    493   if (typeof options === 'function') {
    494     done = options;
    495     opts = {};
    496   }
    497 
    498   // create file stream
    499   var file = send(req, path, opts);
    500 
    501   // transfer
    502   sendfile(res, file, opts, function (err) {
    503     if (done) return done(err);
    504     if (err && err.code === 'EISDIR') return next();
    505 
    506     // next() all but write errors
    507     if (err && err.code !== 'ECONNABORTED' && err.syscall !== 'write') {
    508       next(err);
    509     }
    510   });
    511 };
    512 
    513 res.sendfile = deprecate.function(res.sendfile,
    514   'res.sendfile: Use res.sendFile instead');
    515 
    516 /**
    517  * Transfer the file at the given `path` as an attachment.
    518  *
    519  * Optionally providing an alternate attachment `filename`,
    520  * and optional callback `callback(err)`. The callback is invoked
    521  * when the data transfer is complete, or when an error has
    522  * ocurred. Be sure to check `res.headersSent` if you plan to respond.
    523  *
    524  * Optionally providing an `options` object to use with `res.sendFile()`.
    525  * This function will set the `Content-Disposition` header, overriding
    526  * any `Content-Disposition` header passed as header options in order
    527  * to set the attachment and filename.
    528  *
    529  * This method uses `res.sendFile()`.
    530  *
    531  * @public
    532  */
    533 
    534 res.download = function download (path, filename, options, callback) {
    535   var done = callback;
    536   var name = filename;
    537   var opts = options || null
    538 
    539   // support function as second or third arg
    540   if (typeof filename === 'function') {
    541     done = filename;
    542     name = null;
    543     opts = null
    544   } else if (typeof options === 'function') {
    545     done = options
    546     opts = null
    547   }
    548 
    549   // set Content-Disposition when file is sent
    550   var headers = {
    551     'Content-Disposition': contentDisposition(name || path)
    552   };
    553 
    554   // merge user-provided headers
    555   if (opts && opts.headers) {
    556     var keys = Object.keys(opts.headers)
    557     for (var i = 0; i < keys.length; i++) {
    558       var key = keys[i]
    559       if (key.toLowerCase() !== 'content-disposition') {
    560         headers[key] = opts.headers[key]
    561       }
    562     }
    563   }
    564 
    565   // merge user-provided options
    566   opts = Object.create(opts)
    567   opts.headers = headers
    568 
    569   // Resolve the full path for sendFile
    570   var fullPath = resolve(path);
    571 
    572   // send file
    573   return this.sendFile(fullPath, opts, done)
    574 };
    575 
    576 /**
    577  * Set _Content-Type_ response header with `type` through `mime.lookup()`
    578  * when it does not contain "/", or set the Content-Type to `type` otherwise.
    579  *
    580  * Examples:
    581  *
    582  *     res.type('.html');
    583  *     res.type('html');
    584  *     res.type('json');
    585  *     res.type('application/json');
    586  *     res.type('png');
    587  *
    588  * @param {String} type
    589  * @return {ServerResponse} for chaining
    590  * @public
    591  */
    592 
    593 res.contentType =
    594 res.type = function contentType(type) {
    595   var ct = type.indexOf('/') === -1
    596     ? mime.lookup(type)
    597     : type;
    598 
    599   return this.set('Content-Type', ct);
    600 };
    601 
    602 /**
    603  * Respond to the Acceptable formats using an `obj`
    604  * of mime-type callbacks.
    605  *
    606  * This method uses `req.accepted`, an array of
    607  * acceptable types ordered by their quality values.
    608  * When "Accept" is not present the _first_ callback
    609  * is invoked, otherwise the first match is used. When
    610  * no match is performed the server responds with
    611  * 406 "Not Acceptable".
    612  *
    613  * Content-Type is set for you, however if you choose
    614  * you may alter this within the callback using `res.type()`
    615  * or `res.set('Content-Type', ...)`.
    616  *
    617  *    res.format({
    618  *      'text/plain': function(){
    619  *        res.send('hey');
    620  *      },
    621  *
    622  *      'text/html': function(){
    623  *        res.send('<p>hey</p>');
    624  *      },
    625  *
    626  *      'appliation/json': function(){
    627  *        res.send({ message: 'hey' });
    628  *      }
    629  *    });
    630  *
    631  * In addition to canonicalized MIME types you may
    632  * also use extnames mapped to these types:
    633  *
    634  *    res.format({
    635  *      text: function(){
    636  *        res.send('hey');
    637  *      },
    638  *
    639  *      html: function(){
    640  *        res.send('<p>hey</p>');
    641  *      },
    642  *
    643  *      json: function(){
    644  *        res.send({ message: 'hey' });
    645  *      }
    646  *    });
    647  *
    648  * By default Express passes an `Error`
    649  * with a `.status` of 406 to `next(err)`
    650  * if a match is not made. If you provide
    651  * a `.default` callback it will be invoked
    652  * instead.
    653  *
    654  * @param {Object} obj
    655  * @return {ServerResponse} for chaining
    656  * @public
    657  */
    658 
    659 res.format = function(obj){
    660   var req = this.req;
    661   var next = req.next;
    662 
    663   var fn = obj.default;
    664   if (fn) delete obj.default;
    665   var keys = Object.keys(obj);
    666 
    667   var key = keys.length > 0
    668     ? req.accepts(keys)
    669     : false;
    670 
    671   this.vary("Accept");
    672 
    673   if (key) {
    674     this.set('Content-Type', normalizeType(key).value);
    675     obj[key](req, this, next);
    676   } else if (fn) {
    677     fn();
    678   } else {
    679     var err = new Error('Not Acceptable');
    680     err.status = err.statusCode = 406;
    681     err.types = normalizeTypes(keys).map(function(o){ return o.value });
    682     next(err);
    683   }
    684 
    685   return this;
    686 };
    687 
    688 /**
    689  * Set _Content-Disposition_ header to _attachment_ with optional `filename`.
    690  *
    691  * @param {String} filename
    692  * @return {ServerResponse}
    693  * @public
    694  */
    695 
    696 res.attachment = function attachment(filename) {
    697   if (filename) {
    698     this.type(extname(filename));
    699   }
    700 
    701   this.set('Content-Disposition', contentDisposition(filename));
    702 
    703   return this;
    704 };
    705 
    706 /**
    707  * Append additional header `field` with value `val`.
    708  *
    709  * Example:
    710  *
    711  *    res.append('Link', ['<http://localhost/>', '<http://localhost:3000/>']);
    712  *    res.append('Set-Cookie', 'foo=bar; Path=/; HttpOnly');
    713  *    res.append('Warning', '199 Miscellaneous warning');
    714  *
    715  * @param {String} field
    716  * @param {String|Array} val
    717  * @return {ServerResponse} for chaining
    718  * @public
    719  */
    720 
    721 res.append = function append(field, val) {
    722   var prev = this.get(field);
    723   var value = val;
    724 
    725   if (prev) {
    726     // concat the new and prev vals
    727     value = Array.isArray(prev) ? prev.concat(val)
    728       : Array.isArray(val) ? [prev].concat(val)
    729       : [prev, val];
    730   }
    731 
    732   return this.set(field, value);
    733 };
    734 
    735 /**
    736  * Set header `field` to `val`, or pass
    737  * an object of header fields.
    738  *
    739  * Examples:
    740  *
    741  *    res.set('Foo', ['bar', 'baz']);
    742  *    res.set('Accept', 'application/json');
    743  *    res.set({ Accept: 'text/plain', 'X-API-Key': 'tobi' });
    744  *
    745  * Aliased as `res.header()`.
    746  *
    747  * @param {String|Object} field
    748  * @param {String|Array} val
    749  * @return {ServerResponse} for chaining
    750  * @public
    751  */
    752 
    753 res.set =
    754 res.header = function header(field, val) {
    755   if (arguments.length === 2) {
    756     var value = Array.isArray(val)
    757       ? val.map(String)
    758       : String(val);
    759 
    760     // add charset to content-type
    761     if (field.toLowerCase() === 'content-type') {
    762       if (Array.isArray(value)) {
    763         throw new TypeError('Content-Type cannot be set to an Array');
    764       }
    765       if (!charsetRegExp.test(value)) {
    766         var charset = mime.charsets.lookup(value.split(';')[0]);
    767         if (charset) value += '; charset=' + charset.toLowerCase();
    768       }
    769     }
    770 
    771     this.setHeader(field, value);
    772   } else {
    773     for (var key in field) {
    774       this.set(key, field[key]);
    775     }
    776   }
    777   return this;
    778 };
    779 
    780 /**
    781  * Get value for header `field`.
    782  *
    783  * @param {String} field
    784  * @return {String}
    785  * @public
    786  */
    787 
    788 res.get = function(field){
    789   return this.getHeader(field);
    790 };
    791 
    792 /**
    793  * Clear cookie `name`.
    794  *
    795  * @param {String} name
    796  * @param {Object} [options]
    797  * @return {ServerResponse} for chaining
    798  * @public
    799  */
    800 
    801 res.clearCookie = function clearCookie(name, options) {
    802   var opts = merge({ expires: new Date(1), path: '/' }, options);
    803 
    804   return this.cookie(name, '', opts);
    805 };
    806 
    807 /**
    808  * Set cookie `name` to `value`, with the given `options`.
    809  *
    810  * Options:
    811  *
    812  *    - `maxAge`   max-age in milliseconds, converted to `expires`
    813  *    - `signed`   sign the cookie
    814  *    - `path`     defaults to "/"
    815  *
    816  * Examples:
    817  *
    818  *    // "Remember Me" for 15 minutes
    819  *    res.cookie('rememberme', '1', { expires: new Date(Date.now() + 900000), httpOnly: true });
    820  *
    821  *    // same as above
    822  *    res.cookie('rememberme', '1', { maxAge: 900000, httpOnly: true })
    823  *
    824  * @param {String} name
    825  * @param {String|Object} value
    826  * @param {Object} [options]
    827  * @return {ServerResponse} for chaining
    828  * @public
    829  */
    830 
    831 res.cookie = function (name, value, options) {
    832   var opts = merge({}, options);
    833   var secret = this.req.secret;
    834   var signed = opts.signed;
    835 
    836   if (signed && !secret) {
    837     throw new Error('cookieParser("secret") required for signed cookies');
    838   }
    839 
    840   var val = typeof value === 'object'
    841     ? 'j:' + JSON.stringify(value)
    842     : String(value);
    843 
    844   if (signed) {
    845     val = 's:' + sign(val, secret);
    846   }
    847 
    848   if ('maxAge' in opts) {
    849     opts.expires = new Date(Date.now() + opts.maxAge);
    850     opts.maxAge /= 1000;
    851   }
    852 
    853   if (opts.path == null) {
    854     opts.path = '/';
    855   }
    856 
    857   this.append('Set-Cookie', cookie.serialize(name, String(val), opts));
    858 
    859   return this;
    860 };
    861 
    862 /**
    863  * Set the location header to `url`.
    864  *
    865  * The given `url` can also be "back", which redirects
    866  * to the _Referrer_ or _Referer_ headers or "/".
    867  *
    868  * Examples:
    869  *
    870  *    res.location('/foo/bar').;
    871  *    res.location('http://example.com');
    872  *    res.location('../login');
    873  *
    874  * @param {String} url
    875  * @return {ServerResponse} for chaining
    876  * @public
    877  */
    878 
    879 res.location = function location(url) {
    880   var loc = url;
    881 
    882   // "back" is an alias for the referrer
    883   if (url === 'back') {
    884     loc = this.req.get('Referrer') || '/';
    885   }
    886 
    887   // set location
    888   return this.set('Location', encodeUrl(loc));
    889 };
    890 
    891 /**
    892  * Redirect to the given `url` with optional response `status`
    893  * defaulting to 302.
    894  *
    895  * The resulting `url` is determined by `res.location()`, so
    896  * it will play nicely with mounted apps, relative paths,
    897  * `"back"` etc.
    898  *
    899  * Examples:
    900  *
    901  *    res.redirect('/foo/bar');
    902  *    res.redirect('http://example.com');
    903  *    res.redirect(301, 'http://example.com');
    904  *    res.redirect('../login'); // /blog/post/1 -> /blog/login
    905  *
    906  * @public
    907  */
    908 
    909 res.redirect = function redirect(url) {
    910   var address = url;
    911   var body;
    912   var status = 302;
    913 
    914   // allow status / url
    915   if (arguments.length === 2) {
    916     if (typeof arguments[0] === 'number') {
    917       status = arguments[0];
    918       address = arguments[1];
    919     } else {
    920       deprecate('res.redirect(url, status): Use res.redirect(status, url) instead');
    921       status = arguments[1];
    922     }
    923   }
    924 
    925   // Set location header
    926   address = this.location(address).get('Location');
    927 
    928   // Support text/{plain,html} by default
    929   this.format({
    930     text: function(){
    931       body = statuses[status] + '. Redirecting to ' + address
    932     },
    933 
    934     html: function(){
    935       var u = escapeHtml(address);
    936       body = '<p>' + statuses[status] + '. Redirecting to <a href="' + u + '">' + u + '</a></p>'
    937     },
    938 
    939     default: function(){
    940       body = '';
    941     }
    942   });
    943 
    944   // Respond
    945   this.statusCode = status;
    946   this.set('Content-Length', Buffer.byteLength(body));
    947 
    948   if (this.req.method === 'HEAD') {
    949     this.end();
    950   } else {
    951     this.end(body);
    952   }
    953 };
    954 
    955 /**
    956  * Add `field` to Vary. If already present in the Vary set, then
    957  * this call is simply ignored.
    958  *
    959  * @param {Array|String} field
    960  * @return {ServerResponse} for chaining
    961  * @public
    962  */
    963 
    964 res.vary = function(field){
    965   // checks for back-compat
    966   if (!field || (Array.isArray(field) && !field.length)) {
    967     deprecate('res.vary(): Provide a field name');
    968     return this;
    969   }
    970 
    971   vary(this, field);
    972 
    973   return this;
    974 };
    975 
    976 /**
    977  * Render `view` with the given `options` and optional callback `fn`.
    978  * When a callback function is given a response will _not_ be made
    979  * automatically, otherwise a response of _200_ and _text/html_ is given.
    980  *
    981  * Options:
    982  *
    983  *  - `cache`     boolean hinting to the engine it should cache
    984  *  - `filename`  filename of the view being rendered
    985  *
    986  * @public
    987  */
    988 
    989 res.render = function render(view, options, callback) {
    990   var app = this.req.app;
    991   var done = callback;
    992   var opts = options || {};
    993   var req = this.req;
    994   var self = this;
    995 
    996   // support callback function as second arg
    997   if (typeof options === 'function') {
    998     done = options;
    999     opts = {};
   1000   }
   1001 
   1002   // merge res.locals
   1003   opts._locals = self.locals;
   1004 
   1005   // default callback to respond
   1006   done = done || function (err, str) {
   1007     if (err) return req.next(err);
   1008     self.send(str);
   1009   };
   1010 
   1011   // render
   1012   app.render(view, opts, done);
   1013 };
   1014 
   1015 // pipe the send file stream
   1016 function sendfile(res, file, options, callback) {
   1017   var done = false;
   1018   var streaming;
   1019 
   1020   // request aborted
   1021   function onaborted() {
   1022     if (done) return;
   1023     done = true;
   1024 
   1025     var err = new Error('Request aborted');
   1026     err.code = 'ECONNABORTED';
   1027     callback(err);
   1028   }
   1029 
   1030   // directory
   1031   function ondirectory() {
   1032     if (done) return;
   1033     done = true;
   1034 
   1035     var err = new Error('EISDIR, read');
   1036     err.code = 'EISDIR';
   1037     callback(err);
   1038   }
   1039 
   1040   // errors
   1041   function onerror(err) {
   1042     if (done) return;
   1043     done = true;
   1044     callback(err);
   1045   }
   1046 
   1047   // ended
   1048   function onend() {
   1049     if (done) return;
   1050     done = true;
   1051     callback();
   1052   }
   1053 
   1054   // file
   1055   function onfile() {
   1056     streaming = false;
   1057   }
   1058 
   1059   // finished
   1060   function onfinish(err) {
   1061     if (err && err.code === 'ECONNRESET') return onaborted();
   1062     if (err) return onerror(err);
   1063     if (done) return;
   1064 
   1065     setImmediate(function () {
   1066       if (streaming !== false && !done) {
   1067         onaborted();
   1068         return;
   1069       }
   1070 
   1071       if (done) return;
   1072       done = true;
   1073       callback();
   1074     });
   1075   }
   1076 
   1077   // streaming
   1078   function onstream() {
   1079     streaming = true;
   1080   }
   1081 
   1082   file.on('directory', ondirectory);
   1083   file.on('end', onend);
   1084   file.on('error', onerror);
   1085   file.on('file', onfile);
   1086   file.on('stream', onstream);
   1087   onFinished(res, onfinish);
   1088 
   1089   if (options.headers) {
   1090     // set headers on successful transfer
   1091     file.on('headers', function headers(res) {
   1092       var obj = options.headers;
   1093       var keys = Object.keys(obj);
   1094 
   1095       for (var i = 0; i < keys.length; i++) {
   1096         var k = keys[i];
   1097         res.setHeader(k, obj[k]);
   1098       }
   1099     });
   1100   }
   1101 
   1102   // pipe
   1103   file.pipe(res);
   1104 }
   1105 
   1106 /**
   1107  * Stringify JSON, like JSON.stringify, but v8 optimized, with the
   1108  * ability to escape characters that can trigger HTML sniffing.
   1109  *
   1110  * @param {*} value
   1111  * @param {function} replaces
   1112  * @param {number} spaces
   1113  * @param {boolean} escape
   1114  * @returns {string}
   1115  * @private
   1116  */
   1117 
   1118 function stringify (value, replacer, spaces, escape) {
   1119   // v8 checks arguments.length for optimizing simple call
   1120   // https://bugs.chromium.org/p/v8/issues/detail?id=4730
   1121   var json = replacer || spaces
   1122     ? JSON.stringify(value, replacer, spaces)
   1123     : JSON.stringify(value);
   1124 
   1125   if (escape) {
   1126     json = json.replace(/[<>&]/g, function (c) {
   1127       switch (c.charCodeAt(0)) {
   1128         case 0x3c:
   1129           return '\\u003c'
   1130         case 0x3e:
   1131           return '\\u003e'
   1132         case 0x26:
   1133           return '\\u0026'
   1134         /* istanbul ignore next: unreachable default */
   1135         default:
   1136           return c
   1137       }
   1138     })
   1139   }
   1140 
   1141   return json
   1142 }