response.js 24 KB

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