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