response.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783
  1. /**
  2. * Module dependencies.
  3. */
  4. var escapeHtml = require('escape-html');
  5. var http = require('http');
  6. var path = require('path');
  7. var mixin = require('utils-merge');
  8. var sign = require('cookie-signature').sign;
  9. var normalizeType = require('./utils').normalizeType;
  10. var normalizeTypes = require('./utils').normalizeTypes;
  11. var setCharset = require('./utils').setCharset;
  12. var contentDisposition = require('./utils').contentDisposition;
  13. var deprecate = require('./utils').deprecate;
  14. var statusCodes = http.STATUS_CODES;
  15. var cookie = require('cookie');
  16. var send = require('send');
  17. var basename = path.basename;
  18. var extname = path.extname;
  19. var mime = send.mime;
  20. var vary = require('vary');
  21. /**
  22. * Response prototype.
  23. */
  24. var res = module.exports = {
  25. __proto__: http.ServerResponse.prototype
  26. };
  27. /**
  28. * Set status `code`.
  29. *
  30. * @param {Number} code
  31. * @return {ServerResponse}
  32. * @api public
  33. */
  34. res.status = function(code){
  35. this.statusCode = code;
  36. return this;
  37. };
  38. /**
  39. * Set Link header field with the given `links`.
  40. *
  41. * Examples:
  42. *
  43. * res.links({
  44. * next: 'http://api.example.com/users?page=2',
  45. * last: 'http://api.example.com/users?page=5'
  46. * });
  47. *
  48. * @param {Object} links
  49. * @return {ServerResponse}
  50. * @api public
  51. */
  52. res.links = function(links){
  53. var link = this.get('Link') || '';
  54. if (link) link += ', ';
  55. return this.set('Link', link + Object.keys(links).map(function(rel){
  56. return '<' + links[rel] + '>; rel="' + rel + '"';
  57. }).join(', '));
  58. };
  59. /**
  60. * Send a response.
  61. *
  62. * Examples:
  63. *
  64. * res.send(new Buffer('wahoo'));
  65. * res.send({ some: 'json' });
  66. * res.send('<p>some html</p>');
  67. * res.send(404, 'Sorry, cant find that');
  68. * res.send(404);
  69. *
  70. * @api public
  71. */
  72. res.send = function(body){
  73. var req = this.req;
  74. var head = 'HEAD' == req.method;
  75. var type;
  76. var encoding;
  77. var len;
  78. // settings
  79. var app = this.app;
  80. // allow status / body
  81. if (2 == arguments.length) {
  82. // res.send(body, status) backwards compat
  83. if ('number' != typeof body && 'number' == typeof arguments[1]) {
  84. this.statusCode = arguments[1];
  85. } else {
  86. this.statusCode = body;
  87. body = arguments[1];
  88. }
  89. }
  90. switch (typeof body) {
  91. // response status
  92. case 'number':
  93. this.get('Content-Type') || this.type('txt');
  94. this.statusCode = body;
  95. body = http.STATUS_CODES[body];
  96. break;
  97. // string defaulting to html
  98. case 'string':
  99. if (!this.get('Content-Type')) this.type('html');
  100. break;
  101. case 'boolean':
  102. case 'object':
  103. if (null == body) {
  104. body = '';
  105. } else if (Buffer.isBuffer(body)) {
  106. this.get('Content-Type') || this.type('bin');
  107. } else {
  108. return this.json(body);
  109. }
  110. break;
  111. }
  112. // write strings in utf-8
  113. if ('string' === typeof body) {
  114. encoding = 'utf8';
  115. type = this.get('Content-Type');
  116. // reflect this in content-type
  117. if ('string' === typeof type) {
  118. this.set('Content-Type', setCharset(type, 'utf-8'));
  119. }
  120. }
  121. // populate Content-Length
  122. if (undefined !== body && !this.get('Content-Length')) {
  123. len = Buffer.isBuffer(body)
  124. ? body.length
  125. : Buffer.byteLength(body, encoding);
  126. this.set('Content-Length', len);
  127. }
  128. // ETag support
  129. var etag = len !== undefined && app.get('etag fn');
  130. if (etag && ('GET' === req.method || 'HEAD' === req.method)) {
  131. if (!this.get('ETag')) {
  132. etag = etag(body, encoding);
  133. etag && this.set('ETag', etag);
  134. }
  135. }
  136. // freshness
  137. if (req.fresh) this.statusCode = 304;
  138. // strip irrelevant headers
  139. if (204 == this.statusCode || 304 == this.statusCode) {
  140. this.removeHeader('Content-Type');
  141. this.removeHeader('Content-Length');
  142. this.removeHeader('Transfer-Encoding');
  143. body = '';
  144. }
  145. // respond
  146. this.end((head ? null : body), encoding);
  147. return this;
  148. };
  149. /**
  150. * Send JSON response.
  151. *
  152. * Examples:
  153. *
  154. * res.json(null);
  155. * res.json({ user: 'tj' });
  156. * res.json(500, 'oh noes!');
  157. * res.json(404, 'I dont have that');
  158. *
  159. * @api public
  160. */
  161. res.json = function(obj){
  162. // allow status / body
  163. if (2 == arguments.length) {
  164. // res.json(body, status) backwards compat
  165. if ('number' == typeof arguments[1]) {
  166. this.statusCode = arguments[1];
  167. return 'number' === typeof obj
  168. ? jsonNumDeprecated.call(this, obj)
  169. : jsonDeprecated.call(this, obj);
  170. } else {
  171. this.statusCode = obj;
  172. obj = arguments[1];
  173. }
  174. }
  175. // settings
  176. var app = this.app;
  177. var replacer = app.get('json replacer');
  178. var spaces = app.get('json spaces');
  179. var body = JSON.stringify(obj, replacer, spaces);
  180. // content-type
  181. this.get('Content-Type') || this.set('Content-Type', 'application/json');
  182. return this.send(body);
  183. };
  184. var jsonDeprecated = deprecate(res.json,
  185. 'res.json(obj, status): Use res.json(status, obj) instead');
  186. var jsonNumDeprecated = deprecate(res.json,
  187. 'res.json(num, status): Use res.status(status).json(num) instead');
  188. /**
  189. * Send JSON response with JSONP callback support.
  190. *
  191. * Examples:
  192. *
  193. * res.jsonp(null);
  194. * res.jsonp({ user: 'tj' });
  195. * res.jsonp(500, 'oh noes!');
  196. * res.jsonp(404, 'I dont have that');
  197. *
  198. * @api public
  199. */
  200. res.jsonp = function(obj){
  201. // allow status / body
  202. if (2 == arguments.length) {
  203. // res.json(body, status) backwards compat
  204. if ('number' == typeof arguments[1]) {
  205. this.statusCode = arguments[1];
  206. return 'number' === typeof obj
  207. ? jsonpNumDeprecated.call(this, obj)
  208. : jsonpDeprecated.call(this, obj);
  209. } else {
  210. this.statusCode = obj;
  211. obj = arguments[1];
  212. }
  213. }
  214. // settings
  215. var app = this.app;
  216. var replacer = app.get('json replacer');
  217. var spaces = app.get('json spaces');
  218. var body = JSON.stringify(obj, replacer, spaces)
  219. .replace(/\u2028/g, '\\u2028')
  220. .replace(/\u2029/g, '\\u2029');
  221. var callback = this.req.query[app.get('jsonp callback name')];
  222. // content-type
  223. this.get('Content-Type') || this.set('Content-Type', 'application/json');
  224. // fixup callback
  225. if (Array.isArray(callback)) {
  226. callback = callback[0];
  227. }
  228. // jsonp
  229. if (callback && 'string' === typeof callback) {
  230. this.set('Content-Type', 'text/javascript');
  231. var cb = callback.replace(/[^\[\]\w$.]/g, '');
  232. body = 'typeof ' + cb + ' === \'function\' && ' + cb + '(' + body + ');';
  233. }
  234. return this.send(body);
  235. };
  236. var jsonpDeprecated = deprecate(res.json,
  237. 'res.jsonp(obj, status): Use res.jsonp(status, obj) instead');
  238. var jsonpNumDeprecated = deprecate(res.json,
  239. 'res.jsonp(num, status): Use res.status(status).jsonp(num) instead');
  240. /**
  241. * Transfer the file at the given `path`.
  242. *
  243. * Automatically sets the _Content-Type_ response header field.
  244. * The callback `fn(err)` is invoked when the transfer is complete
  245. * or when an error occurs. Be sure to check `res.sentHeader`
  246. * if you wish to attempt responding, as the header and some data
  247. * may have already been transferred.
  248. *
  249. * Options:
  250. *
  251. * - `maxAge` defaulting to 0
  252. * - `root` root directory for relative filenames
  253. * - `hidden` serve hidden files, defaulting to false
  254. *
  255. * Other options are passed along to `send`.
  256. *
  257. * Examples:
  258. *
  259. * The following example illustrates how `res.sendfile()` may
  260. * be used as an alternative for the `static()` middleware for
  261. * dynamic situations. The code backing `res.sendfile()` is actually
  262. * the same code, so HTTP cache support etc is identical.
  263. *
  264. * app.get('/user/:uid/photos/:file', function(req, res){
  265. * var uid = req.params.uid
  266. * , file = req.params.file;
  267. *
  268. * req.user.mayViewFilesFrom(uid, function(yes){
  269. * if (yes) {
  270. * res.sendfile('/uploads/' + uid + '/' + file);
  271. * } else {
  272. * res.send(403, 'Sorry! you cant see that.');
  273. * }
  274. * });
  275. * });
  276. *
  277. * @api public
  278. */
  279. res.sendfile = function(path, options, fn){
  280. options = options || {};
  281. var self = this;
  282. var req = self.req;
  283. var next = this.req.next;
  284. var done;
  285. // support function as second arg
  286. if ('function' == typeof options) {
  287. fn = options;
  288. options = {};
  289. }
  290. // socket errors
  291. req.socket.on('error', error);
  292. // errors
  293. function error(err) {
  294. if (done) return;
  295. done = true;
  296. // clean up
  297. cleanup();
  298. if (!self.headersSent) self.removeHeader('Content-Disposition');
  299. // callback available
  300. if (fn) return fn(err);
  301. // list in limbo if there's no callback
  302. if (self.headersSent) return;
  303. // delegate
  304. next(err);
  305. }
  306. // streaming
  307. function stream(stream) {
  308. if (done) return;
  309. cleanup();
  310. if (fn) stream.on('end', fn);
  311. }
  312. // cleanup
  313. function cleanup() {
  314. req.socket.removeListener('error', error);
  315. }
  316. // Back-compat
  317. options.maxage = options.maxage || options.maxAge || 0;
  318. // transfer
  319. var file = send(req, path, options);
  320. file.on('error', error);
  321. file.on('directory', next);
  322. file.on('stream', stream);
  323. file.pipe(this);
  324. this.on('finish', cleanup);
  325. };
  326. /**
  327. * Transfer the file at the given `path` as an attachment.
  328. *
  329. * Optionally providing an alternate attachment `filename`,
  330. * and optional callback `fn(err)`. The callback is invoked
  331. * when the data transfer is complete, or when an error has
  332. * ocurred. Be sure to check `res.headersSent` if you plan to respond.
  333. *
  334. * This method uses `res.sendfile()`.
  335. *
  336. * @api public
  337. */
  338. res.download = function(path, filename, fn){
  339. // support function as second arg
  340. if ('function' == typeof filename) {
  341. fn = filename;
  342. filename = null;
  343. }
  344. filename = filename || path;
  345. this.set('Content-Disposition', contentDisposition(filename));
  346. return this.sendfile(path, fn);
  347. };
  348. /**
  349. * Set _Content-Type_ response header with `type` through `mime.lookup()`
  350. * when it does not contain "/", or set the Content-Type to `type` otherwise.
  351. *
  352. * Examples:
  353. *
  354. * res.type('.html');
  355. * res.type('html');
  356. * res.type('json');
  357. * res.type('application/json');
  358. * res.type('png');
  359. *
  360. * @param {String} type
  361. * @return {ServerResponse} for chaining
  362. * @api public
  363. */
  364. res.contentType =
  365. res.type = function(type){
  366. return this.set('Content-Type', ~type.indexOf('/')
  367. ? type
  368. : mime.lookup(type));
  369. };
  370. /**
  371. * Respond to the Acceptable formats using an `obj`
  372. * of mime-type callbacks.
  373. *
  374. * This method uses `req.accepted`, an array of
  375. * acceptable types ordered by their quality values.
  376. * When "Accept" is not present the _first_ callback
  377. * is invoked, otherwise the first match is used. When
  378. * no match is performed the server responds with
  379. * 406 "Not Acceptable".
  380. *
  381. * Content-Type is set for you, however if you choose
  382. * you may alter this within the callback using `res.type()`
  383. * or `res.set('Content-Type', ...)`.
  384. *
  385. * res.format({
  386. * 'text/plain': function(){
  387. * res.send('hey');
  388. * },
  389. *
  390. * 'text/html': function(){
  391. * res.send('<p>hey</p>');
  392. * },
  393. *
  394. * 'appliation/json': function(){
  395. * res.send({ message: 'hey' });
  396. * }
  397. * });
  398. *
  399. * In addition to canonicalized MIME types you may
  400. * also use extnames mapped to these types:
  401. *
  402. * res.format({
  403. * text: function(){
  404. * res.send('hey');
  405. * },
  406. *
  407. * html: function(){
  408. * res.send('<p>hey</p>');
  409. * },
  410. *
  411. * json: function(){
  412. * res.send({ message: 'hey' });
  413. * }
  414. * });
  415. *
  416. * By default Express passes an `Error`
  417. * with a `.status` of 406 to `next(err)`
  418. * if a match is not made. If you provide
  419. * a `.default` callback it will be invoked
  420. * instead.
  421. *
  422. * @param {Object} obj
  423. * @return {ServerResponse} for chaining
  424. * @api public
  425. */
  426. res.format = function(obj){
  427. var req = this.req;
  428. var next = req.next;
  429. var fn = obj.default;
  430. if (fn) delete obj.default;
  431. var keys = Object.keys(obj);
  432. var key = req.accepts(keys);
  433. this.vary("Accept");
  434. if (key) {
  435. this.set('Content-Type', normalizeType(key).value);
  436. obj[key](req, this, next);
  437. } else if (fn) {
  438. fn();
  439. } else {
  440. var err = new Error('Not Acceptable');
  441. err.status = 406;
  442. err.types = normalizeTypes(keys).map(function(o){ return o.value });
  443. next(err);
  444. }
  445. return this;
  446. };
  447. /**
  448. * Set _Content-Disposition_ header to _attachment_ with optional `filename`.
  449. *
  450. * @param {String} filename
  451. * @return {ServerResponse}
  452. * @api public
  453. */
  454. res.attachment = function(filename){
  455. if (filename) this.type(extname(filename));
  456. this.set('Content-Disposition', contentDisposition(filename));
  457. return this;
  458. };
  459. /**
  460. * Set header `field` to `val`, or pass
  461. * an object of header fields.
  462. *
  463. * Examples:
  464. *
  465. * res.set('Foo', ['bar', 'baz']);
  466. * res.set('Accept', 'application/json');
  467. * res.set({ Accept: 'text/plain', 'X-API-Key': 'tobi' });
  468. *
  469. * Aliased as `res.header()`.
  470. *
  471. * @param {String|Object|Array} field
  472. * @param {String} val
  473. * @return {ServerResponse} for chaining
  474. * @api public
  475. */
  476. res.set =
  477. res.header = function(field, val){
  478. if (2 == arguments.length) {
  479. if (Array.isArray(val)) val = val.map(String);
  480. else val = String(val);
  481. if ('content-type' == field.toLowerCase() && !/;\s*charset\s*=/.test(val)) {
  482. var charset = mime.charsets.lookup(val.split(';')[0]);
  483. if (charset) val += '; charset=' + charset.toLowerCase();
  484. }
  485. this.setHeader(field, val);
  486. } else {
  487. for (var key in field) {
  488. this.set(key, field[key]);
  489. }
  490. }
  491. return this;
  492. };
  493. /**
  494. * Get value for header `field`.
  495. *
  496. * @param {String} field
  497. * @return {String}
  498. * @api public
  499. */
  500. res.get = function(field){
  501. return this.getHeader(field);
  502. };
  503. /**
  504. * Clear cookie `name`.
  505. *
  506. * @param {String} name
  507. * @param {Object} options
  508. * @param {ServerResponse} for chaining
  509. * @api public
  510. */
  511. res.clearCookie = function(name, options){
  512. var opts = { expires: new Date(1), path: '/' };
  513. return this.cookie(name, '', options
  514. ? mixin(opts, options)
  515. : opts);
  516. };
  517. /**
  518. * Set cookie `name` to `val`, with the given `options`.
  519. *
  520. * Options:
  521. *
  522. * - `maxAge` max-age in milliseconds, converted to `expires`
  523. * - `signed` sign the cookie
  524. * - `path` defaults to "/"
  525. *
  526. * Examples:
  527. *
  528. * // "Remember Me" for 15 minutes
  529. * res.cookie('rememberme', '1', { expires: new Date(Date.now() + 900000), httpOnly: true });
  530. *
  531. * // save as above
  532. * res.cookie('rememberme', '1', { maxAge: 900000, httpOnly: true })
  533. *
  534. * @param {String} name
  535. * @param {String|Object} val
  536. * @param {Options} options
  537. * @api public
  538. */
  539. res.cookie = function(name, val, options){
  540. options = mixin({}, options);
  541. var secret = this.req.secret;
  542. var signed = options.signed;
  543. if (signed && !secret) throw new Error('cookieParser("secret") required for signed cookies');
  544. if ('number' == typeof val) val = val.toString();
  545. if ('object' == typeof val) val = 'j:' + JSON.stringify(val);
  546. if (signed) val = 's:' + sign(val, secret);
  547. if ('maxAge' in options) {
  548. options.expires = new Date(Date.now() + options.maxAge);
  549. options.maxAge /= 1000;
  550. }
  551. if (null == options.path) options.path = '/';
  552. var headerVal = cookie.serialize(name, String(val), options);
  553. // supports multiple 'res.cookie' calls by getting previous value
  554. var prev = this.get('Set-Cookie');
  555. if (prev) {
  556. if (Array.isArray(prev)) {
  557. headerVal = prev.concat(headerVal);
  558. } else {
  559. headerVal = [prev, headerVal];
  560. }
  561. }
  562. this.set('Set-Cookie', headerVal);
  563. return this;
  564. };
  565. /**
  566. * Set the location header to `url`.
  567. *
  568. * The given `url` can also be "back", which redirects
  569. * to the _Referrer_ or _Referer_ headers or "/".
  570. *
  571. * Examples:
  572. *
  573. * res.location('/foo/bar').;
  574. * res.location('http://example.com');
  575. * res.location('../login');
  576. *
  577. * @param {String} url
  578. * @api public
  579. */
  580. res.location = function(url){
  581. var req = this.req;
  582. // "back" is an alias for the referrer
  583. if ('back' == url) url = req.get('Referrer') || '/';
  584. // Respond
  585. this.set('Location', url);
  586. return this;
  587. };
  588. /**
  589. * Redirect to the given `url` with optional response `status`
  590. * defaulting to 302.
  591. *
  592. * The resulting `url` is determined by `res.location()`, so
  593. * it will play nicely with mounted apps, relative paths,
  594. * `"back"` etc.
  595. *
  596. * Examples:
  597. *
  598. * res.redirect('/foo/bar');
  599. * res.redirect('http://example.com');
  600. * res.redirect(301, 'http://example.com');
  601. * res.redirect('http://example.com', 301);
  602. * res.redirect('../login'); // /blog/post/1 -> /blog/login
  603. *
  604. * @param {String} url
  605. * @param {Number} code
  606. * @api public
  607. */
  608. res.redirect = function(url){
  609. var head = 'HEAD' == this.req.method;
  610. var status = 302;
  611. var body;
  612. // allow status / url
  613. if (2 == arguments.length) {
  614. if ('number' == typeof url) {
  615. status = url;
  616. url = arguments[1];
  617. } else {
  618. status = arguments[1];
  619. }
  620. }
  621. // Set location header
  622. this.location(url);
  623. url = this.get('Location');
  624. // Support text/{plain,html} by default
  625. this.format({
  626. text: function(){
  627. body = statusCodes[status] + '. Redirecting to ' + encodeURI(url);
  628. },
  629. html: function(){
  630. var u = escapeHtml(url);
  631. body = '<p>' + statusCodes[status] + '. Redirecting to <a href="' + u + '">' + u + '</a></p>';
  632. },
  633. default: function(){
  634. body = '';
  635. }
  636. });
  637. // Respond
  638. this.statusCode = status;
  639. this.set('Content-Length', Buffer.byteLength(body));
  640. this.end(head ? null : body);
  641. };
  642. /**
  643. * Add `field` to Vary. If already present in the Vary set, then
  644. * this call is simply ignored.
  645. *
  646. * @param {Array|String} field
  647. * @param {ServerResponse} for chaining
  648. * @api public
  649. */
  650. res.vary = function(field){
  651. // checks for back-compat
  652. if (!field) return this;
  653. if (Array.isArray(field) && !field.length) return this;
  654. vary(this, field);
  655. return this;
  656. };
  657. /**
  658. * Render `view` with the given `options` and optional callback `fn`.
  659. * When a callback function is given a response will _not_ be made
  660. * automatically, otherwise a response of _200_ and _text/html_ is given.
  661. *
  662. * Options:
  663. *
  664. * - `cache` boolean hinting to the engine it should cache
  665. * - `filename` filename of the view being rendered
  666. *
  667. * @api public
  668. */
  669. res.render = function(view, options, fn){
  670. options = options || {};
  671. var self = this;
  672. var req = this.req;
  673. var app = req.app;
  674. // support callback function as second arg
  675. if ('function' == typeof options) {
  676. fn = options, options = {};
  677. }
  678. // merge res.locals
  679. options._locals = self.locals;
  680. // default callback to respond
  681. fn = fn || function(err, str){
  682. if (err) return req.next(err);
  683. self.send(str);
  684. };
  685. // render
  686. app.render(view, options, fn);
  687. };