github.com/cloudcredo/cloudrocker@v0.0.0-20160108110610-1320f8cc2dfd/sample-apps/node/node_modules/express/lib/response.js (about)

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