annotate node_modules/express/lib/response.js @ 101:52e44ee1c791 tip master

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