PageRenderTime 67ms CodeModel.GetById 40ms app.highlight 24ms RepoModel.GetById 0ms app.codeStats 0ms

/node_modules/express/lib/response.js

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