Ein Projekt das es ermöglicht Beerpong über das Internet von zwei unabhängigen positionen aus zu spielen. Entstehung im Rahmen einer Praktikumsaufgabe im Fach Interaktion.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

response.js 23KB

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