459 lines · 10.9 KB · JavaScript
RawDownload
 /*! * Express - response * Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca> * MIT Licensed */ /** * Module dependencies. */ var fs = require('fs')  , http = require('http')  , path = require('path')  , connect = require('connect')  , utils = connect.utils  , parseRange = require('./utils').parseRange  , res = http.ServerResponse.prototype  , send = connect.static.send  , mime = require('mime')  , basename = path.basename  , join = path.join; /** * Send a response with the given `body` and optional `headers` and `status` code. * * Examples: * *     res.send(); *     res.send(new Buffer('wahoo')); *     res.send({ some: 'json' }); *     res.send('<p>some html</p>'); *     res.send('Sorry, cant find that', 404); *     res.send('text', { 'Content-Type': 'text/plain' }, 201); *     res.send(404); * * @param {String|Object|Number|Buffer} body or status * @param {Object|Number} headers or status * @param {Number} status * @return {ServerResponse} * @api public */ res.send = function(body, headers, status){  // allow status as second arg  if ('number' == typeof headers) {    status = headers,    headers = null;  }   // default status  status = status || this.statusCode;   // allow 0 args as 204  if (!arguments.length || undefined === body) status = 204;   // determine content type  switch (typeof body) {    case 'number':      if (!this.header('Content-Type')) {        this.contentType('.txt');      }      body = http.STATUS_CODES[status = body];      break;    case 'string':      if (!this.header('Content-Type')) {        this.charset = this.charset || 'utf-8';        this.contentType('.html');      }      break;    case 'boolean':    case 'object':      if (Buffer.isBuffer(body)) {        if (!this.header('Content-Type')) {          this.contentType('.bin');        }      } else {        return this.json(body, headers, status);      }      break;  }   // populate Content-Length  if (undefined !== body && !this.header('Content-Length')) {    this.header('Content-Length', Buffer.isBuffer(body)      ? body.length      : Buffer.byteLength(body));  }   // merge headers passed  if (headers) {    var fields = Object.keys(headers);    for (var i = 0, len = fields.length; i < len; ++i) {      var field = fields[i];      this.header(field, headers[field]);    }  }   // strip irrelevant headers  if (204 == status || 304 == status) {    this.removeHeader('Content-Type');    this.removeHeader('Content-Length');    body = '';  }   // respond  this.statusCode = status;  this.end('HEAD' == this.req.method ? null : body);  return this;}; /** * Send JSON response with `obj`, optional `headers`, and optional `status`. * * Examples: * *     res.json(null); *     res.json({ user: 'tj' }); *     res.json('oh noes!', 500); *     res.json('I dont have that', 404); * * @param {Mixed} obj * @param {Object|Number} headers or status * @param {Number} status * @return {ServerResponse} * @api public */ res.json = function(obj, headers, status){  var body = JSON.stringify(obj)    , callback = this.req.query.callback    , jsonp = this.app.enabled('jsonp callback');   this.charset = this.charset || 'utf-8';  this.header('Content-Type', 'application/json');   if (callback && jsonp) {    this.header('Content-Type', 'text/javascript');    body = callback.replace(/[^\w$.]/g, '') + '(' + body + ');';  }   return this.send(body, headers, status);}; /** * Set status `code`. * * @param {Number} code * @return {ServerResponse} * @api public */ res.status = function(code){  this.statusCode = code;  return this;}; /** * Transfer the file at the given `path`. Automatically sets  * the _Content-Type_ response header field. `next()` is called * when `path` is a directory, or when an error occurs. * * Options: * *   - `maxAge` defaulting to 0 *   - `root`   root directory for relative filenames * * @param {String} path * @param {Object|Function} options or fn * @param {Function} fn * @api public */ res.sendfile = function(path, options, fn){  var next = this.req.next;  options = options || {};   // support function as second arg  if ('function' == typeof options) {    fn = options;    options = {};  }   options.path = encodeURIComponent(path);  options.callback = fn;  send(this.req, this, next, options);}; /** * Set _Content-Type_ response header passed through `mime.lookup()`. * * Examples: * *     var filename = 'path/to/image.png'; *     res.contentType(filename); *     // res.headers['Content-Type'] is now "image/png" * *     res.contentType('.html'); *     res.contentType('html'); *     res.contentType('json'); *     res.contentType('png'); * * @param {String} type * @return {String} the resolved mime type * @api public */ res.contentType = function(type){  return this.header('Content-Type', mime.lookup(type));}; /** * Set _Content-Disposition_ header to _attachment_ with optional `filename`. * * @param {String} filename * @return {ServerResponse} * @api public */ res.attachment = function(filename){  if (filename) this.contentType(filename);  this.header('Content-Disposition', filename    ? 'attachment; filename="' + basename(filename) + '"'    : 'attachment');  return this;}; /** * Transfer the file at the given `path`, with optional  * `filename` as an attachment and optional callback `fn(err)`, * and optional `fn2(err)` which is invoked when an error has * occurred after header has been sent. * * @param {String} path * @param {String|Function} filename or fn * @param {Function} fn * @param {Function} fn2 * @api public */ res.download = function(path, filename, fn, fn2){  var self = this;   // support callback as second arg  if ('function' == typeof filename) {    fn2 = fn;    fn = filename;    filename = null;  }   // transfer the file  this.attachment(filename || path).sendfile(path, function(err){    var sentHeader = self._header;    if (err) {      if (!sentHeader) self.removeHeader('Content-Disposition');      if (sentHeader) {        fn2 && fn2(err);      } else if (fn) {        fn(err);      } else {        self.req.next(err);      }    } else if (fn) {      fn();    }  });}; /** * Set or get response header `name` with optional `val`. * * @param {String} name * @param {String} val * @return {ServerResponse} for chaining * @api public */ res.header = function(name, val){  if (1 == arguments.length) return this.getHeader(name);  this.setHeader(name, val);  return this;}; /** * Clear cookie `name`. * * @param {String} name * @param {Object} options * @api public */ res.clearCookie = function(name, options){  var opts = { expires: new Date(1) };  this.cookie(name, '', options    ? utils.merge(options, opts)    : opts);}; /** * Set cookie `name` to `val`, with the given `options`. * * Options: * *    - `maxAge`   max-age in milliseconds, converted to `expires` *    - `path`     defaults to the "basepath" setting which is typically "/" * * Examples: * *    // "Remember Me" for 15 minutes *    res.cookie('rememberme', '1', { expires: new Date(Date.now() + 900000), httpOnly: true }); * *    // save as above *    res.cookie('rememberme', '1', { maxAge: 900000, httpOnly: true }) * * @param {String} name * @param {String} val * @param {Options} options * @api public */ res.cookie = function(name, val, options){  options = options || {};  if ('maxAge' in options) options.expires = new Date(Date.now() + options.maxAge);  if (undefined === options.path) options.path = this.app.set('basepath');  var cookie = utils.serializeCookie(name, val, options);  this.header('Set-Cookie', cookie);}; /** * Redirect to the given `url` with optional response `status` * defauling to 302. * * The given `url` can also be the name of a mapped url, for * example by default express supports "back" which redirects * to the _Referrer_ or _Referer_ headers or the application's * "basepath" setting. Express also supports "basepath" out of the box, * which can be set via `app.set('basepath', '/blog');`, and defaults * to '/'. * * Redirect Mapping: *  *  To extend the redirect mapping capabilities that Express provides, *  we may use the `app.redirect()` method: *  *     app.redirect('google', 'http://google.com'); *  *  Now in a route we may call: * *     res.redirect('google'); * *  We may also map dynamic redirects: * *      app.redirect('comments', function(req, res){ *          return '/post/' + req.params.id + '/comments'; *      }); * *  So now we may do the following, and the redirect will dynamically adjust to *  the context of the request. If we called this route with _GET /post/12_ our *  redirect _Location_ would be _/post/12/comments_. * *      app.get('/post/:id', function(req, res){ *        res.redirect('comments'); *      }); * *  Unless an absolute `url` is given, the app's mount-point *  will be respected. For example if we redirect to `/posts`, *  and our app is mounted at `/blog` we will redirect to `/blog/posts`. * * @param {String} url * @param {Number} code * @api public */ res.redirect = function(url, status){  var app = this.app    , req = this.req    , base = app.set('basepath') || app.route    , status = status || 302    , head = 'HEAD' == req.method    , body;   // Setup redirect map  var map = {      back: req.header('Referrer', base)    , home: base  };   // Support custom redirect map  map.__proto__ = app.redirects;   // Attempt mapped redirect  var mapped = 'function' == typeof map[url]    ? map[url](req, this)    : map[url];   // Perform redirect  url = mapped || url;   // Relative  if (!~url.indexOf('://')) {    // Respect mount-point    if ('/' != base && 0 != url.indexOf(base)) url = base + url;     // Absolute    var host = req.headers.host;    url = req.protocol + '://' + host + url;  }   // Support text/{plain,html} by default  if (req.accepts('html')) {    body = '<p>' + http.STATUS_CODES[status] + '. Redirecting to <a href="' + url + '">' + url + '</a></p>';    this.header('Content-Type', 'text/html');  } else {    body = http.STATUS_CODES[status] + '. Redirecting to ' + url;    this.header('Content-Type', 'text/plain');  }   // Respond  this.statusCode = status;  this.header('Location', url);  this.end(head ? null : body);}; /** * Assign the view local variable `name` to `val` or return the * local previously assigned to `name`. * * @param {String} name * @param {Mixed} val * @return {Mixed} val * @api public */ res.local = function(name, val){  this._locals = this._locals || {};  return undefined === val    ? this._locals[name]    : this._locals[name] = val;}; /** * Assign several locals with the given `obj`, * or return the locals. * * @param {Object} obj * @return {Object|Undefined} * @api public */ res.locals =res.helpers = function(obj){  if (obj) {    for (var key in obj) {      this.local(key, obj[key]);    }  } else {    return this._locals;  }};