response.js 25 KB

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