/node_modules/express/lib/response.js

https://bitbucket.org/gagginaspinnata/todo-app-with-angularjs · JavaScript · 719 lines · 289 code · 80 blank · 350 comment · 91 complexity · fcac5a74cc92a6091e8952dacec7212c MD5 · raw file

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