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