You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1065 lines
24 KiB

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