text: /**
   * Module dependencies.
   */
  
  var http = require('http')
    , path = require('path')
    , connect = require('connect')
    , utils = connect.utils
    , sign = require('cookie-signature').sign
    , normalizeType = require('./utils').normalizeType
    , normalizeTypes = require('./utils').normalizeTypes
    , etag = require('./utils').etag
    , statusCodes = http.STATUS_CODES
    , cookie = require('cookie')
    , send = require('send')
    , mime = connect.mime
    , basename = path.basename
    , extname = path.extname
    , join = path.join;
  
  /**
   * Response prototype.
   */
  
  var res = module.exports = {
    __proto__: http.ServerResponse.prototype
  };
  
  /**
   * Set status `code`.
   *
   * @param {Number} code
   * @return {ServerResponse}
   * @api public
   */
  
  res.status = function(code){
    this.statusCode = code;
    return this;
  };
  
  /**
   * Set Link header field with the given `links`.
   *
   * Examples:
   *
   *    res.links({
   *      next: 'http://api.example.com/users?page=2',
   *      last: 'http://api.example.com/users?page=5'
   *    });
   *
   * @param {Object} links
   * @return {ServerResponse}
   * @api public
   */
  
  res.links = function(links){
    return this.set('Link', Object.keys(links).map(function(rel){
      return '<' + links[rel] + '>; rel="' + rel + '"';
    }).join(', '));
  };
  
  /**
   * Send a response.
   *
   * Examples:
   *
   *     res.send(new Buffer('wahoo'));
   *     res.send({ some: 'json' });
   *     res.send('<p>some html</p>');
   *     res.send(404, 'Sorry, cant find that');
   *     res.send(404);
   *
   * @param {Mixed} body or status
   * @param {Mixed} body
   * @return {ServerResponse}
   * @api public
   */
  
  res.send = function(body){
    var req = this.req
      , head = 'HEAD' == req.method
      , len;
  
    // allow status / body
    if (2 == arguments.length) {
      // res.send(body, status) backwards compat
      if ('number' != typeof body && 'number' == typeof arguments[1]) {
        this.statusCode = arguments[1];
      } else {
        this.statusCode = body;
        body = arguments[1];
      }
    }
  
    switch (typeof body) {
      // response status
      case 'number':
        this.get('Content-Type') || this.type('txt');
        this.statusCode = body;
        body = http.STATUS_CODES[body];
        break;
      // string defaulting to html
      case 'string':
        if (!this.get('Content-Type')) {
          this.charset = this.charset || 'utf-8';
          this.type('html');
        }
        break;
      case 'boolean':
      case 'object':
        if (null == body) {
          body = '';
        } else if (Buffer.isBuffer(body)) {
          this.get('Content-Type') || this.type('bin');
        } else {
          return this.json(body);
        }
        break;
    }
  
    // populate Content-Length
    if (undefined !== body && !this.get('Content-Length')) {
      this.set('Content-Length', len = Buffer.isBuffer(body)
        ? body.length
        : Buffer.byteLength(body));
    }
  
    // ETag support
    // TODO: W/ support
    if (len > 1024) {
      if (!this.get('ETag')) {
        this.set('ETag', etag(body));
      }
    }
  
    // freshness
    if (req.fresh) this.statusCode = 304;
  
    // strip irrelevant headers
    if (204 == this.statusCode || 304 == this.statusCode) {
      this.removeHeader('Content-Type');
      this.removeHeader('Content-Length');
      this.removeHeader('Transfer-Encoding');
      body = '';
    }
  
    // respond
    this.end(head ? null : body);
    return this;
  };
  
  /**
   * Send JSON response.
   *
   * Examples:
   *
   *     res.json(null);
   *     res.json({ user: 'tj' });
   *     res.json(500, 'oh noes!');
   *     res.json(404, 'I dont have that');
   *
   * @param {Mixed} obj or status
   * @param {Mixed} obj
   * @return {ServerResponse}
   * @api public
   */
  
  res.json = function(obj){
    // allow status / body
    if (2 == arguments.length) {
      // res.json(body, status) backwards compat
      if ('number' == typeof arguments[1]) {
        this.statusCode = arguments[1];
      } else {
        this.statusCode = obj;
        obj = arguments[1];
      }
    }
  
    // settings
    var app = this.app;
    var replacer = app.get('json replacer');
    var spaces = app.get('json spaces');
    var body = JSON.stringify(obj, replacer, spaces);
  
    // content-type
    this.charset = this.charset || 'utf-8';
    this.get('Content-Type') || this.set('Content-Type', 'application/json');
  
    return this.send(body);
  };
  
  /**
   * Send JSON response with JSONP callback support.
   *
   * Examples:
   *
   *     res.jsonp(null);
   *     res.jsonp({ user: 'tj' });
   *     res.jsonp(500, 'oh noes!');
   *     res.jsonp(404, 'I dont have that');
   *
   * @param {Mixed} obj or status
   * @param {Mixed} obj
   * @return {ServerResponse}
   * @api public
   */
  
  res.jsonp = function(obj){
    // allow status / body
    if (2 == arguments.length) {
      // res.json(body, status) backwards compat
      if ('number' == typeof arguments[1]) {
        this.statusCode = arguments[1];
      } else {
        this.statusCode = obj;
        obj = arguments[1];
      }
    }
  
    // settings
    var app = this.app;
    var replacer = app.get('json replacer');
    var spaces = app.get('json spaces');
    var body = JSON.stringify(obj, replacer, spaces)
      .replace(/\u2028/g, '\\u2028')
      .replace(/\u2029/g, '\\u2029');
    var callback = this.req.query[app.get('jsonp callback name')];
  
    // content-type
    this.charset = this.charset || 'utf-8';
    this.set('Content-Type', 'application/json');
  
    // jsonp
    if (callback) {
      this.set('Content-Type', 'text/javascript');
      var cb = callback.replace(/[^\[\]\w$.]/g, '');
      body = cb + ' && ' + cb + '(' + body + ');';
    }
  
    return this.send(body);
  };
  
  /**
   * Transfer the file at the given `path`.
   *
   * Automatically sets the _Content-Type_ response header field.
   * The callback `fn(err)` is invoked when the transfer is complete
   * or when an error occurs. Be sure to check `res.sentHeader`
   * if you wish to attempt responding, as the header and some data
   * may have already been transferred.
   *
   * Options:
   *
   *   - `maxAge` defaulting to 0
   *   - `root`   root directory for relative filenames
   *
   * Examples:
   *
   *  The following example illustrates how `res.sendfile()` may
   *  be used as an alternative for the `static()` middleware for
   *  dynamic situations. The code backing `res.sendfile()` is actually
   *  the same code, so HTTP cache support etc is identical.
   *
   *     app.get('/user/:uid/photos/:file', function(req, res){
   *       var uid = req.params.uid
   *         , file = req.params.file;
   *
   *       req.user.mayViewFilesFrom(uid, function(yes){
   *         if (yes) {
   *           res.sendfile('/uploads/' + uid + '/' + file);
   *         } else {
   *           res.send(403, 'Sorry! you cant see that.');
   *         }
   *       });
   *     });
   *
   * @param {String} path
   * @param {Object|Function} options or fn
   * @param {Function} fn
   * @api public
   */
  
  res.sendfile = function(path, options, fn){
    var self = this
      , req = self.req
      , next = this.req.next
      , options = options || {}
      , done;
  
    // support function as second arg
    if ('function' == typeof options) {
      fn = options;
      options = {};
    }
  
    // socket errors
    req.socket.on('error', error);
  
    // errors
    function error(err) {
      if (done) return;
      done = true;
  
      // clean up
      cleanup();
      if (!self.headerSent) self.removeHeader('Content-Disposition');
  
      // callback available
      if (fn) return fn(err);
  
      // list in limbo if there's no callback
      if (self.headerSent) return;
  
      // delegate
      next(err);
    }
  
    // streaming
    function stream() {
      if (done) return;
      cleanup();
      if (fn) self.on('finish', fn);
    }
  
    // cleanup
    function cleanup() {
      req.socket.removeListener('error', error);
    }
  
    // transfer
    var file = send(req, path);
    if (options.root) file.root(options.root);
    file.maxage(options.maxAge || 0);
    file.on('error', error);
    file.on('directory', next);
    file.on('stream', stream);
    file.pipe(this);
    this.on('finish', cleanup);
  };
  
  /**
   * Transfer the file at the given `path` as an attachment.
   *
   * Optionally providing an alternate attachment `filename`,
   * and optional callback `fn(err)`. The callback is invoked
   * when the data transfer is complete, or when an error has
   * ocurred. Be sure to check `res.headerSent` if you plan to respond.
   *
   * This method uses `res.sendfile()`.
   *
   * @param {String} path
   * @param {String|Function} filename or fn
   * @param {Function} fn
   * @api public
   */
  
  res.download = function(path, filename, fn){
    // support function as second arg
    if ('function' == typeof filename) {
      fn = filename;
      filename = null;
    }
  
    filename = filename || path;
    this.set('Content-Disposition', 'attachment; filename="' + basename(filename) + '"');
    return this.sendfile(path, fn);
  };
  
  /**
   * Set _Content-Type_ response header with `type` through `mime.lookup()`
   * when it does not contain "/", or set the Content-Type to `type` otherwise.
   *
   * Examples:
   *
   *     res.type('.html');
   *     res.type('html');
   *     res.type('json');
   *     res.type('application/json');
   *     res.type('png');
   *
   * @param {String} type
   * @return {ServerResponse} for chaining
   * @api public
   */
  
  res.contentType =
  res.type = function(type){
    return this.set('Content-Type', ~type.indexOf('/')
      ? type
      : mime.lookup(type));
  };
  
  /**
   * Respond to the Acceptable formats using an `obj`
   * of mime-type callbacks.
   *
   * This method uses `req.accepted`, an array of
   * acceptable types ordered by their quality values.
   * When "Accept" is not present the _first_ callback
   * is invoked, otherwise the first match is used. When
   * no match is performed the server responds with
   * 406 "Not Acceptable".
   *
   * Content-Type is set for you, however if you choose
   * you may alter this within the callback using `res.type()`
   * or `res.set('Content-Type', ...)`.
   *
   *    res.format({
   *      'text/plain': function(){
   *        res.send('hey');
   *      },
   *
   *      'text/html': function(){
   *        res.send('<p>hey</p>');
   *      },
   *
   *      'appliation/json': function(){
   *        res.send({ message: 'hey' });
   *      }
   *    });
   *
   * In addition to canonicalized MIME types you may
   * also use extnames mapped to these types:
   *
   *    res.format({
   *      text: function(){
   *        res.send('hey');
   *      },
   *
   *      html: function(){
   *        res.send('<p>hey</p>');
   *      },
   *
   *      json: function(){
   *        res.send({ message: 'hey' });
   *      }
   *    });
   *
   * By default Express passes an `Error`
   * with a `.status` of 406 to `next(err)`
   * if a match is not made. If you provide
   * a `.default` callback it will be invoked
   * instead.
   *
   * @param {Object} obj
   * @return {ServerResponse} for chaining
   * @api public
   */
  
  res.format = function(obj){
    var req = this.req
      , next = req.next;
  
    var fn = obj.default;
    if (fn) delete obj.default;
    var keys = Object.keys(obj);
  
    var key = req.accepts(keys);
  
    this.set('Vary', 'Accept');
  
    if (key) {
      this.set('Content-Type', normalizeType(key));
      obj[key](req, this, next);
    } else if (fn) {
      fn();
    } else {
      var err = new Error('Not Acceptable');
      err.status = 406;
      err.types = normalizeTypes(keys);
      next(err);
    }
  
    return this;
  };
  
  /**
   * Set _Content-Disposition_ header to _attachment_ with optional `filename`.
   *
   * @param {String} filename
   * @return {ServerResponse}
   * @api public
   */
  
  res.attachment = function(filename){
    if (filename) this.type(extname(filename));
    this.set('Content-Disposition', filename
      ? 'attachment; filename="' + basename(filename) + '"'
      : 'attachment');
    return this;
  };
  
  /**
   * Set header `field` to `val`, or pass
   * an object of header fields.
   *
   * Examples:
   *
   *    res.set('Foo', ['bar', 'baz']);
   *    res.set('Accept', 'application/json');
   *    res.set({ Accept: 'text/plain', 'X-API-Key': 'tobi' });
   *
   * Aliased as `res.header()`.
   *
   * @param {String|Object|Array} field
   * @param {String} val
   * @return {ServerResponse} for chaining
   * @api public
   */
  
  res.set =
  res.header = function(field, val){
    if (2 == arguments.length) {
      if (Array.isArray(val)) val = val.map(String);
      else val = String(val);
      this.setHeader(field, val);
    } else {
      for (var key in field) {
        this.set(key, field[key]);
      }
    }
    return this;
  };
  
  /**
   * Get value for header `field`.
   *
   * @param {String} field
   * @return {String}
   * @api public
   */
  
  res.get = function(field){
    return this.getHeader(field);
  };
  
  /**
   * Clear cookie `name`.
   *
   * @param {String} name
   * @param {Object} options
   * @param {ServerResponse} for chaining
   * @api public
   */
  
  res.clearCookie = function(name, options){
    var opts = { expires: new Date(1), path: '/' };
    return this.cookie(name, '', options
      ? utils.merge(opts, options)
      : opts);
  };
  
  /**
   * Set cookie `name` to `val`, with the given `options`.
   *
   * Options:
   *
   *    - `maxAge`   max-age in milliseconds, converted to `expires`
   *    - `signed`   sign the cookie
   *    - `path`     defaults to "/"
   *
   * 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|Object} val
   * @param {Options} options
   * @api public
   */
  
  res.cookie = function(name, val, options){
    options = utils.merge({}, options);
    var secret = this.req.secret;
    var signed = options.signed;
    if (signed && !secret) throw new Error('connect.cookieParser("secret") required for signed cookies');
    if ('object' == typeof val) val = 'j:' + JSON.stringify(val);
    if (signed) val = 's:' + sign(val, secret);
    if ('maxAge' in options) {
      options.expires = new Date(Date.now() + options.maxAge);
      options.maxAge /= 1000;
    }
    if (null == options.path) options.path = '/';
    this.set('Set-Cookie', cookie.serialize(name, String(val), options));
    return this;
  };
  
  
  /**
   * Set the location header to `url`.
   *
   * 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 "/".
   *
   * Examples:
   *
   *    res.location('/foo/bar').;
   *    res.location('http://example.com');
   *    res.location('../login'); // /blog/post/1 -> /blog/login
   *
   * Mounting:
   *
   *   When an application is mounted and `res.location()`
   *   is given a path that does _not_ lead with "/" it becomes
   *   relative to the mount-point. For example if the application
   *   is mounted at "/blog", the following would become "/blog/login".
   *
   *      res.location('login');
   *
   *   While the leading slash would result in a location of "/login":
   *
   *      res.location('/login');
   *
   * @param {String} url
   * @api public
   */
  
  res.location = function(url){
    var app = this.app
      , req = this.req;
  
    // setup redirect map
    var map = { back: req.get('Referrer') || '/' };
  
    // perform redirect
    url = map[url] || url;
  
    // relative
    if (!~url.indexOf('://') && 0 != url.indexOf('//')) {
      var path
  
      // relative to path
      if ('.' == url[0]) {
        path = req.originalUrl.split('?')[0]
        url =  path + ('/' == path[path.length - 1] ? '' : '/') + url;
        // relative to mount-point
      } else if ('/' != url[0]) {
        path = app.path();
        url = path + '/' + url;
      }
    }
  
    // Respond
    this.set('Location', url);
    return this;
  };
  
  /**
   * Redirect to the given `url` with optional response `status`
   * defaulting to 302.
   *
   * The resulting `url` is determined by `res.location()`, so
   * it will play nicely with mounted apps, relative paths,
   * `"back"` etc.
   *
   * Examples:
   *
   *    res.redirect('/foo/bar');
   *    res.redirect('http://example.com');
   *    res.redirect(301, 'http://example.com');
   *    res.redirect('http://example.com', 301);
   *    res.redirect('../login'); // /blog/post/1 -> /blog/login
   *
   * @param {String} url
   * @param {Number} code
   * @api public
   */
  
  res.redirect = function(url){
    var app = this.app
      , head = 'HEAD' == this.req.method
      , status = 302
      , body;
  
    // allow status / url
    if (2 == arguments.length) {
      if ('number' == typeof url) {
        status = url;
        url = arguments[1];
      } else {
        status = arguments[1];
      }
    }
  
    // Set location header
    this.location(url);
    url = this.get('Location');
  
    // Support text/{plain,html} by default
    this.format({
      text: function(){
        body = statusCodes[status] + '. Redirecting to ' + encodeURI(url);
      },
  
      html: function(){
        var u = utils.escape(url);
        body = '<p>' + statusCodes[status] + '. Redirecting to <a href="' + u + '">' + u + '</a></p>';
      },
  
      default: function(){
        body = '';
      }
    });
  
    // Respond
    this.statusCode = status;
    this.set('Content-Length', Buffer.byteLength(body));
    this.end(head ? null : body);
  };
  
  /**
   * Render `view` with the given `options` and optional callback `fn`.
   * When a callback function is given a response will _not_ be made
   * automatically, otherwise a response of _200_ and _text/html_ is given.
   *
   * Options:
   *
   *  - `cache`     boolean hinting to the engine it should cache
   *  - `filename`  filename of the view being rendered
   *
   * @param  {String} view
   * @param  {Object|Function} options or callback function
   * @param  {Function} fn
   * @api public
   */
  
  res.render = function(view, options, fn){
    var self = this
      , options = options || {}
      , req = this.req
      , app = req.app;
  
    // support callback function as second arg
    if ('function' == typeof options) {
      fn = options, options = {};
    }
  
    // merge res.locals
    options._locals = self.locals;
  
    // default callback to respond
    fn = fn || function(err, str){
      if (err) return req.next(err);
      self.send(str);
    };
  
    // render
    app.render(view, options, fn);
  };
  