response.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077
  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') {
  629. if (Array.isArray(value)) {
  630. throw new TypeError('Content-Type cannot be set to an Array');
  631. }
  632. if (!charsetRegExp.test(value)) {
  633. var charset = mime.charsets.lookup(value.split(';')[0]);
  634. if (charset) value += '; charset=' + charset.toLowerCase();
  635. }
  636. }
  637. this.setHeader(field, value);
  638. } else {
  639. for (var key in field) {
  640. this.set(key, field[key]);
  641. }
  642. }
  643. return this;
  644. };
  645. /**
  646. * Get value for header `field`.
  647. *
  648. * @param {String} field
  649. * @return {String}
  650. * @public
  651. */
  652. res.get = function(field){
  653. return this.getHeader(field);
  654. };
  655. /**
  656. * Clear cookie `name`.
  657. *
  658. * @param {String} name
  659. * @param {Object} [options]
  660. * @return {ServerResponse} for chaining
  661. * @public
  662. */
  663. res.clearCookie = function clearCookie(name, options) {
  664. var opts = merge({ expires: new Date(1), path: '/' }, options);
  665. return this.cookie(name, '', opts);
  666. };
  667. /**
  668. * Set cookie `name` to `value`, with the given `options`.
  669. *
  670. * Options:
  671. *
  672. * - `maxAge` max-age in milliseconds, converted to `expires`
  673. * - `signed` sign the cookie
  674. * - `path` defaults to "/"
  675. *
  676. * Examples:
  677. *
  678. * // "Remember Me" for 15 minutes
  679. * res.cookie('rememberme', '1', { expires: new Date(Date.now() + 900000), httpOnly: true });
  680. *
  681. * // save as above
  682. * res.cookie('rememberme', '1', { maxAge: 900000, httpOnly: true })
  683. *
  684. * @param {String} name
  685. * @param {String|Object} value
  686. * @param {Object} [options]
  687. * @return {ServerResponse} for chaining
  688. * @public
  689. */
  690. res.cookie = function (name, value, options) {
  691. var opts = merge({}, options);
  692. var secret = this.req.secret;
  693. var signed = opts.signed;
  694. if (signed && !secret) {
  695. throw new Error('cookieParser("secret") required for signed cookies');
  696. }
  697. var val = typeof value === 'object'
  698. ? 'j:' + JSON.stringify(value)
  699. : String(value);
  700. if (signed) {
  701. val = 's:' + sign(val, secret);
  702. }
  703. if ('maxAge' in opts) {
  704. opts.expires = new Date(Date.now() + opts.maxAge);
  705. opts.maxAge /= 1000;
  706. }
  707. if (opts.path == null) {
  708. opts.path = '/';
  709. }
  710. this.append('Set-Cookie', cookie.serialize(name, String(val), opts));
  711. return this;
  712. };
  713. /**
  714. * Set the location header to `url`.
  715. *
  716. * The given `url` can also be "back", which redirects
  717. * to the _Referrer_ or _Referer_ headers or "/".
  718. *
  719. * Examples:
  720. *
  721. * res.location('/foo/bar').;
  722. * res.location('http://example.com');
  723. * res.location('../login');
  724. *
  725. * @param {String} url
  726. * @return {ServerResponse} for chaining
  727. * @public
  728. */
  729. res.location = function location(url) {
  730. var loc = url;
  731. // "back" is an alias for the referrer
  732. if (url === 'back') {
  733. loc = this.req.get('Referrer') || '/';
  734. }
  735. // set location
  736. return this.set('Location', encodeUrl(loc));
  737. };
  738. /**
  739. * Redirect to the given `url` with optional response `status`
  740. * defaulting to 302.
  741. *
  742. * The resulting `url` is determined by `res.location()`, so
  743. * it will play nicely with mounted apps, relative paths,
  744. * `"back"` etc.
  745. *
  746. * Examples:
  747. *
  748. * res.redirect('/foo/bar');
  749. * res.redirect('http://example.com');
  750. * res.redirect(301, 'http://example.com');
  751. * res.redirect('../login'); // /blog/post/1 -> /blog/login
  752. *
  753. * @public
  754. */
  755. res.redirect = function redirect(url) {
  756. var address = url;
  757. var body;
  758. var status = 302;
  759. // allow status / url
  760. if (arguments.length === 2) {
  761. if (typeof arguments[0] === 'number') {
  762. status = arguments[0];
  763. address = arguments[1];
  764. } else {
  765. deprecate('res.redirect(url, status): Use res.redirect(status, url) instead');
  766. status = arguments[1];
  767. }
  768. }
  769. // Set location header
  770. address = this.location(address).get('Location');
  771. // Support text/{plain,html} by default
  772. this.format({
  773. text: function(){
  774. body = statuses[status] + '. Redirecting to ' + address
  775. },
  776. html: function(){
  777. var u = escapeHtml(address);
  778. body = '<p>' + statuses[status] + '. Redirecting to <a href="' + u + '">' + u + '</a></p>'
  779. },
  780. default: function(){
  781. body = '';
  782. }
  783. });
  784. // Respond
  785. this.statusCode = status;
  786. this.set('Content-Length', Buffer.byteLength(body));
  787. if (this.req.method === 'HEAD') {
  788. this.end();
  789. } else {
  790. this.end(body);
  791. }
  792. };
  793. /**
  794. * Add `field` to Vary. If already present in the Vary set, then
  795. * this call is simply ignored.
  796. *
  797. * @param {Array|String} field
  798. * @return {ServerResponse} for chaining
  799. * @public
  800. */
  801. res.vary = function(field){
  802. // checks for back-compat
  803. if (!field || (Array.isArray(field) && !field.length)) {
  804. deprecate('res.vary(): Provide a field name');
  805. return this;
  806. }
  807. vary(this, field);
  808. return this;
  809. };
  810. /**
  811. * Render `view` with the given `options` and optional callback `fn`.
  812. * When a callback function is given a response will _not_ be made
  813. * automatically, otherwise a response of _200_ and _text/html_ is given.
  814. *
  815. * Options:
  816. *
  817. * - `cache` boolean hinting to the engine it should cache
  818. * - `filename` filename of the view being rendered
  819. *
  820. * @public
  821. */
  822. res.render = function render(view, options, callback) {
  823. var app = this.req.app;
  824. var done = callback;
  825. var opts = options || {};
  826. var req = this.req;
  827. var self = this;
  828. // support callback function as second arg
  829. if (typeof options === 'function') {
  830. done = options;
  831. opts = {};
  832. }
  833. // merge res.locals
  834. opts._locals = self.locals;
  835. // default callback to respond
  836. done = done || function (err, str) {
  837. if (err) return req.next(err);
  838. self.send(str);
  839. };
  840. // render
  841. app.render(view, opts, done);
  842. };
  843. // pipe the send file stream
  844. function sendfile(res, file, options, callback) {
  845. var done = false;
  846. var streaming;
  847. // request aborted
  848. function onaborted() {
  849. if (done) return;
  850. done = true;
  851. var err = new Error('Request aborted');
  852. err.code = 'ECONNABORTED';
  853. callback(err);
  854. }
  855. // directory
  856. function ondirectory() {
  857. if (done) return;
  858. done = true;
  859. var err = new Error('EISDIR, read');
  860. err.code = 'EISDIR';
  861. callback(err);
  862. }
  863. // errors
  864. function onerror(err) {
  865. if (done) return;
  866. done = true;
  867. callback(err);
  868. }
  869. // ended
  870. function onend() {
  871. if (done) return;
  872. done = true;
  873. callback();
  874. }
  875. // file
  876. function onfile() {
  877. streaming = false;
  878. }
  879. // finished
  880. function onfinish(err) {
  881. if (err && err.code === 'ECONNRESET') return onaborted();
  882. if (err) return onerror(err);
  883. if (done) return;
  884. setImmediate(function () {
  885. if (streaming !== false && !done) {
  886. onaborted();
  887. return;
  888. }
  889. if (done) return;
  890. done = true;
  891. callback();
  892. });
  893. }
  894. // streaming
  895. function onstream() {
  896. streaming = true;
  897. }
  898. file.on('directory', ondirectory);
  899. file.on('end', onend);
  900. file.on('error', onerror);
  901. file.on('file', onfile);
  902. file.on('stream', onstream);
  903. onFinished(res, onfinish);
  904. if (options.headers) {
  905. // set headers on successful transfer
  906. file.on('headers', function headers(res) {
  907. var obj = options.headers;
  908. var keys = Object.keys(obj);
  909. for (var i = 0; i < keys.length; i++) {
  910. var k = keys[i];
  911. res.setHeader(k, obj[k]);
  912. }
  913. });
  914. }
  915. // pipe
  916. file.pipe(res);
  917. }
  918. /**
  919. * Stringify JSON, like JSON.stringify, but v8 optimized.
  920. * @private
  921. */
  922. function stringify(value, replacer, spaces) {
  923. // v8 checks arguments.length for optimizing simple call
  924. // https://bugs.chromium.org/p/v8/issues/detail?id=4730
  925. return replacer || spaces
  926. ? JSON.stringify(value, replacer, spaces)
  927. : JSON.stringify(value);
  928. }