source: Dev/trunk/src/node_modules/express/lib/response.js @ 484

Last change on this file since 484 was 484, checked in by hendrikvanantwerpen, 11 years ago

Commit node_modules, to make checkouts and builds more deterministic.

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