s

High performance web framework for node.

response

lib/express/response.js

Module dependencies.

var fs = require('fs')
  , http = require('http')
  , path = require('path')
  , pump = require('sys').pump
  , utils = require('connect/utils')
  , mime = require('connect/utils').mime
  , parseRange = require('./utils').parseRange;

Header fields supporting multiple values.

  • type: Array

var multiple = ['Set-Cookie'];

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

http.ServerResponse.prototype.send = function(body, headers, status){
  // Allow status as second arg
  if (typeof headers === 'number') {
    status = headers,
    headers = null;
  }

  // Defaults
  status = status || 200;

  // Allow 0 args as 204
  if (!arguments.length) {
    body = status = 204;
  }

  // Determine content type
  switch (typeof body) {
    case 'number':
      if (!this.headers['Content-Type']) {
        this.contentType('.txt');
      }
      body = http.STATUS_CODES[status = body];
      break;
    case 'string':
      if (!this.headers['Content-Type']) {
        this.contentType('.html');
      }
      break;
    case 'object':
      if (body instanceof Buffer) {
        if (!this.headers['Content-Type']) {
          this.contentType('.bin');
        }
      } else {
        if (!this.headers['Content-Type']) {
          this.contentType('.json');
        }
        body = JSON.stringify(body);
        if (this.req.query.callback &amp;&amp; this.app.settings['jsonp callback']) {
          this.header('Content-Type', 'text/javascript');
          body = this.req.query.callback + '(' + body + ');';
        }
      }
      break;
  }

  // Populate Content-Length
  if (!this.headers['Content-Length']) {
    this.header('Content-Length', body instanceof Buffer
      ? body.length
      : Buffer.byteLength(body));
  }

  // Merge headers passed
  if (headers) {
    var fields = Object.keys(headers);
    for (var i = 0, len = fields.length; i &lt; len; ++i) {
      var field = fields[i];
      this.header(field, headers[field]);
    }
  }

  // Strip irrelevant headers
  if (204 === status) {
    delete this.headers['Content-Type'];
    delete this.headers['Content-Length'];
  }

  // Respond
  this.writeHead(status, this.headers);
  this.end('HEAD' == this.req.method ? undefined : body);
};

Transfer the file at the given path. Automatically sets the Content-Type response header via res.contentType().

The given callback fn is invoked when an error occurs, passing it as the first argument, or when the file is transferred, passing the path as the second argument.

When the filesize is >= "stream threshold" (defaulting to 32k), the file will be streamed using an fs.ReadStream and sys.pump().

  • param: String path

  • param: Function fn

  • api: public

http.ServerResponse.prototype.sendfile = function(path, fn){
  var self = this
    , streamThreshold = this.app.set('stream threshold') || 32 * 1024
    , ranges = self.req.headers.range;

  if (~path.indexOf('..')) this.send(403);

  function error(err) {
    delete self.headers['Content-Disposition'];
    if (fn) {
      fn(err, path);
    } else {
      self.req.next(err);
    }
  }

  fs.stat(path, function(err, stat){
    if (err) return error(err);
    if (stat.size &gt;= streamThreshold) {
      var status = 200;
      if (ranges) ranges = parseRange(stat.size, ranges);
      if (ranges) {
        var stream = fs.createReadStream(path, ranges[0])
          , start = ranges[0].start
          , end = ranges[0].end;
        status = 206;
        self.header('Content-Range', 'bytes '
          + start
          + '-'
          + end
          + '/'
          + stat.size);
        self.header('Content-Length', end - start + 1);
      } else {
        var stream = fs.createReadStream(path);
        self.header('Content-Length', stat.size);
      }
      self.contentType(path);
      self.header('Accept-Ranges', 'bytes');
      self.writeHead(status, self.headers);
      pump(stream, self, function(err){
        fn &amp;&amp; fn(err, path, true);
      });
    } else {
      fs.readFile(path, function(err, buf){
        if (err) return error(err);
        self.contentType(path);
        self.send(buf);
        fn &amp;&amp; fn(null, path);
      });
    }
  });
};

Set Content-Type response header passed through mime.type().

Examples

var filename = 'path/to/image.png';
res.contentType(filename);
// res.headers['Content-Type'] is now "image/png"

  • param: String type

  • return: String the resolved mime type

  • api: public

http.ServerResponse.prototype.contentType = function(type){
  return this.header('Content-Type', mime.type(type));
};

Set Content-Disposition header to attachment with optional filename.

  • param: String filename

  • return: ServerResponse

  • api: public

http.ServerResponse.prototype.attachment = function(filename){
  this.header('Content-Disposition', filename
    ? 'attachment; filename="' + path.basename(filename) + '"'
    : 'attachment');
  return this;
};

Transfer the file at the given path, with optional filename as an attachment. Once transferred, or if an error occurs fn is called with the error and path.

  • param: String path

  • param: String filename

  • param: Function fn

  • return: Type

  • api: public

http.ServerResponse.prototype.download = function(path, filename, fn){
  this.attachment(filename || path).sendfile(path, fn);
};

Set or get response header name with optional val.

Headers that may be set multiple times (as indicated by the multiple array) can be called with a value several times, for example:

res.header('Set-Cookie', '...'); res.header('Set-Cookie', '...');

  • param: String name

  • param: String val

  • return: String

  • api: public

http.ServerResponse.prototype.header = function(name, val){
  if (val === undefined) {
    return this.headers[name];
  } else {
    if (this.headers[name] &amp;&amp; ~multiple.indexOf(name)) {
      return this.headers[name] += '\r\n' + name + ': ' + val;
    } else {
      return this.headers[name] = val;
    }
  }
};

Clear cookie name.

  • param: String name

  • api: public

http.ServerResponse.prototype.clearCookie = function(name){
  this.cookie(name, '', { expires: new Date(1) });
};

Set cookie name to val.

Examples

// "Remember Me" for 15 minutes res.cookie('rememberme', '1', { expires: new Date(Date.now() + 900000), httpOnly: true });

  • param: String name

  • param: String val

  • param: Options options

  • api: public

http.ServerResponse.prototype.cookie = function(name, val, options){
  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 "home" setting. Express also supports "home" out of the box, which can be set via app.set('home', '/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');
 });

  • param: String url

  • param: Number code

  • api: public

http.ServerResponse.prototype.redirect = function(url, status){
  var basePath = this.app.set('home') || '/'
    , status = status || 302
    , body;

  // Setup redirect map
  var map = {
    back: this.req.headers.referrer || this.req.headers.referer || basePath,
    home: basePath
  };

  // Support custom redirect map
  map.__proto__ = this.app.redirects;

  // Attempt mapped redirect
  var mapped = typeof map[url] === 'function'
    ? map[url](this.req, this)
    : map[url];

  // Perform redirect
  url = mapped || url;

  // Support text/{plain,html} by default
  if (this.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.send(body, { Location: url }, status);
};

index

lib/express/index.js

Re-export connect auto-loaders.

This prevents the need to require('connect') in order to access core middleware, so for example express.logger() instead of require('connect').logger().

var exports = module.exports = require('connect').middleware;

Framework version.

exports.version = '1.0.0rc4';

Module dependencies.

var Server = exports.Server = require('./server');

Shortcut for new Server(...).

  • param: Function ...

  • return: Server

  • api: public

exports.createServer = function(){
  return new Server(Array.prototype.slice.call(arguments));
};

View extensions.

require('./view');
require('./response');
require('./request');

server

lib/express/server.js

Module dependencies.

var url = require('url')
  , view = require('./view')
  , connect = require('connect')
  , utils = connect.utils
  , queryString = require('querystring')
  , router = require('connect/middleware/router');

Initialize a new Server with optional middleware.

  • param: Array middleware

  • api: public

var Server = exports = module.exports = function Server(middleware){
  var self = this;
  this.config = {};
  this.settings = {};
  this.redirects = {};
  this.isCallbacks = {};
  this.viewHelpers = {};
  this.dynamicViewHelpers = {};
  this.errorHandlers = [];
  connect.Server.call(this, middleware || []);

  // Default "home" to / 
  this.set('home', '/');

  // DEPRECATED: remove in 1.0
  if (process.env.EXPRESS_ENV) {
    process.env.NODE_ENV = process.env.EXPRESS_ENV;
    console.warn('\x1b[33mWarning\x1b[0m: EXPRESS_ENV is deprecated, use NODE_ENV.');
  }

  // TODO: remove when Connect removes "Server" ...
  this.showVersion = false;

  // Set "env" to NODE_ENV, defaulting to "development"
  this.set('env', process.env.NODE_ENV || 'development');

  // Expose objects to each other
  this.use(function(req, res, next){
    req.query = {};
    res.headers = { 'X-Powered-By': 'Express' };
    req.app = res.app = self;
    req.res = res;
    res.req = req;
    req.next = next;
    // Assign req.params.get
    if (req.url.indexOf('?') &gt; 0) {
      var query = url.parse(req.url).query;
      req.query = exports.parseQueryString(query);
    }
    next();
  });

  // Use router, expose as app.get(), etc
  var fn = router(function(app){ self.routes = app; });
  this.__defineGetter__('router', function(){
    this.__usedRouter = true;
    return fn;
  });
};

Inherit from connect.Server.

Server.prototype.__proto__ = connect.Server.prototype;

Support swappable querystring parsers.

exports.parseQueryString = queryString.parse;

Proxy in order to register error handlers.

Server.prototype.listen = function(){
  this.registerErrorHandlers();
  connect.Server.prototype.listen.apply(this, arguments);
};

Proxy in order to register error handlers.

Server.prototype.listenFD = function(){
  this.registerErrorHandlers();
  connect.Server.prototype.listenFD.apply(this, arguments);
};

Register error handlers. This is automatically called from within Server#listen() and Server#listenFD().

  • return: Server for chaining

  • api: public

Server.prototype.registerErrorHandlers = function(){
  this.errorHandlers.forEach(function(fn){
    this.use(function(err, req, res, next){
      fn.apply(this, arguments);
    });
  }, this);
  return this;
};

Proxy connect.Server#use() to apply settings to mounted applications.

  • param: String | Function | Server route

  • param: Function | Server middleware

  • return: Server for chaining

  • api: public

Server.prototype.use = function(route, middleware){
  if (typeof route !== 'string') {
    middleware = route, route = '/';
  }

  connect.Server.prototype.use.call(this, route, middleware);

  // Mounted an app
  if (middleware instanceof Server) {
    // Home is /:route/:home
    var app = middleware
      , home = app.set('home');
    if (home === '/') home = '';
    app.set('home', (app.route || '') + home);
    app.parent = this;
    // Mounted hook
    if (app.__mounted) app.__mounted.call(app, this);
  }

  return this;
};

Assign a callback fn which is called when this Server is passed to Server#use().

Examples

var app = express.createServer(), blog = express.createServer();

blog.mounted(function(parent){ // parent is app // "this" is blog });

app.use(blog);

  • param: Function fn

  • return: Server for chaining

  • api: public

Server.prototype.mounted = function(fn){
  this.__mounted = fn;
  return this;
};

See view.register.

  • return: Server for chaining

  • api: public

Server.prototype.register = function(){
  view.register.apply(this, arguments);
  return this;
};

Register the given view helpers obj. This method can be called several times to apply additional helpers.

  • param: Object obj

  • return: Server for chaining

  • api: public

Server.prototype.helpers = function(obj){
  utils.merge(this.viewHelpers, obj);
  return this;
};

Register the given dynamic view helpers obj. This method can be called several times to apply additional helpers.

  • param: Object obj

  • return: Server for chaining

  • api: public

Server.prototype.dynamicHelpers = function(obj){
  utils.merge(this.dynamicViewHelpers, obj);
  return this;
};

Assign a custom exception handler callback fn. These handlers are always last in the middleware stack.

  • param: Function fn

  • return: Server for chaining

  • api: public

Server.prototype.error = function(fn){
  this.errorHandlers.push(fn);
  return this;
};

Register the given callback fn for the given type.

  • param: String type

  • param: Function fn

  • return: Server for chaining

  • api: public

Server.prototype.is = function(type, fn){
  if (!fn) return this.isCallbacks[type];
  this.isCallbacks[type] = fn;
  return this;
};

Assign setting to val, or return setting's value. Mounted servers inherit their parent server's settings.

  • param: String setting

  • param: String val

  • return: Server | Mixed for chaining, or the setting value

  • api: public

Server.prototype.set = function(setting, val){
  if (val === undefined) {
    if (this.settings.hasOwnProperty(setting)) {
      return this.settings[setting];
    } else if (this.parent) {
      return this.parent.set(setting);
    }
  } else {
    this.settings[setting] = val;
    return this;
  }
};

Enable setting.

  • param: String setting

  • return: Server for chaining

  • api: public

Server.prototype.enable = function(setting){
  return this.set(setting, true);
};

Disable setting.

  • param: String setting

  • return: Server for chaining

  • api: public

Server.prototype.disable = function(setting){
  return this.set(setting, false);
};

Redirect key to url.

  • param: String key

  • param: String url

  • return: Server for chaining

  • api: public

Server.prototype.redirect = function(key, url){
  this.redirects[key] = url;
  return this;
};

Configure callback for the given env.

  • param: String env

  • param: Function fn

  • return: Server for chaining

  • api: public

Server.prototype.configure = function(env, fn){
  if (typeof env === 'function') {
    fn = env, env = 'all';
  }
  if (env === 'all' || this.set('env') === env) {
    fn.call(this);
  }
  return this;
};

// Generate routing methods

(function(method){
  Server.prototype[method] = function(path, fn){
    var self = this;

    // Ensure router is mounted
    if (!this.__usedRouter) {
      this.use(this.router);
    }

    // Route specific middleware support
    if (arguments.length &gt; 2) {
      var args = Array.prototype.slice.call(arguments, 1);
      fn = args.pop();
      (function stack(middleware){
        middleware.forEach(function(fn){
          if (Array.isArray(fn)) {
            stack(fn);
          } else {
            self[method](path, fn);
          }
        });
      })(args);
    }

    // Generate the route
    this.routes[method](path, fn);
    return this;
  };
  return arguments.callee;
})('get')('post')('put')('delete');

// Alias delete as "del"

Server.prototype.del = Server.prototype.delete;

utils

lib/express/utils.js

Parse mini markdown implementation. The following conversions are supported, primarily for the "flash" middleware:

foo or foo become <em>foo</em> foo or foo become <strong>foo</strong> A becomes <a href="B">A</a>

  • param: String str

  • return: String

  • api: private

exports.miniMarkdown = function(str){
  return String(str)
    .replace(/(__|\*\*)(.*?)\1/g, '<strong>$2</strong>')
    .replace(/(_|\*)(.*?)\1/g, '<em>$2</em>')
    .replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
};

Escape special characters in the given string of html.

  • param: String html

  • return: String

  • api: private

exports.htmlEscape = function(html) {
  return String(html)
    .replace(/&/g, '&')
    .replace(/"/g, '"')
    .replace(/</g, '<')
    .replace(/>/g, '>');
};

Parse "Range" header str relative to the given file size.

  • param: Number size

  • param: String str

  • return: Array

  • api: private

exports.parseRange = function(size, str){
  var valid = true;
  var arr = str.substr(6).split(',').map(function(range){
    var range = range.split('-')
      , start = parseInt(range[0], 10)
      , end = parseInt(range[1], 10);

    // -500
    if (isNaN(start)) {
      start = size - end;
      end = size - 1;
    // 500-
    } else if (isNaN(end)) {
      end = size - 1;
    }

    // Invalid
    if (isNaN(start) || isNaN(end) || start &gt; end) valid = false;

    return { start: start, end: end };
  });
  return valid ? arr : undefined;
};

request

lib/express/request.js

Module dependencies.

var http = require('http')
  , utils = require('./utils')
  , mime = require('connect/utils').mime;

Default flash formatters.

  • type: Object

var flashFormatters = exports.flashFormatters = {
  s: function(val){
    return String(val);
  }
};

Return request header or optional default.

Examples

req.header('Content-Type');
// => "text/plain"

req.header('content-type');
// => "text/plain"

req.header('Accept');
// => undefined

req.header('Accept', 'text/html');
// => "text/html"

  • param: String name

  • param: String defaultValue

  • return: String

  • api: public

http.IncomingMessage.prototype.header = function(name, defaultValue){
  return this.headers[name.toLowerCase()] || defaultValue;
};

Check if the Accept header is present, and includes the given type.

When the Accept header is not present true is returned. Otherwise the given type is matched by an exact match, and then subtypes. You may pass the subtype such as "html" which is then converted internally to "text/html" using the mime lookup table.

Examples

// Accept: text/html
req.accepts('html');
// => true

// Accept: text/*; application/json
req.accepts('html');
req.accepts('text/html');
req.accepts('text/plain');
req.accepts('application/json');
// => true

req.accepts('image/png');
req.accepts('png');
// => false

  • param: String type

  • return: Boolean

  • api: public

http.IncomingMessage.prototype.accepts = function(type){
  var accept = this.header('Accept');
  if (!accept || accept === '*

') { return true; } else if (type) { // Allow "html" vs "text/html" etc if (type.indexOf('/') < 0) { type = mime.types['.' + type]; } // Check if we have a direct match if (accept.indexOf(type) >= 0) { return true; // Check if we have type/ } else { type = type.split('/')[0] + '/'; return accept.indexOf(type) >= 0; } } else { return false; } };

/** Return the value of param name when present.

  • Checks route placeholders, ex: /user/:id
  • Checks query string params, ex: ?id=12
  • Checks urlencoded body params, ex: id=12

To utilize urlencoded request bodies, req.body should be an object. This can be done by using the connect.bodyDecoder middleware.

  • param: String name

  • return: String

  • api: public

http.IncomingMessage.prototype.param = function(name){
  // Route params like /user/:id
  if (this.params[name] !== undefined) {
    return this.params[name]; 
  }
  // Query string params
  if (this.query[name] !== undefined) {
    return this.query[name]; 
  }
  // Request body params via connect.bodyDecoder
  if (this.body &amp;&amp; this.body[name] !== undefined) {
    return this.body[name];
  }
};

Queue flash msg of the given type.

Examples

 req.flash('info', 'email sent');
 req.flash('error', 'email delivery failed');
 req.flash('info', 'email re-sent');
 // => 2

 req.flash('info');
 // => ['email sent', 'email re-sent']

 req.flash('info');
 // => []

 req.flash();
 // => { error: ['email delivery failed'], info: [] }

Formatting

Flash notifications also support arbitrary formatting support. For example you may pass variable arguments to req.flash() and use the %s specifier to be replaced by the associated argument:

req.flash('info', 'email has been sent to %s.', userName);

To add custom formatters use the exports.flashFormatters object.

  • param: String type

  • param: String msg

  • return: Array | Object | Number

  • api: public

http.IncomingMessage.prototype.flash = function(type, msg){
  var msgs = this.session.flash = this.session.flash || {};
  if (type &amp;&amp; msg) {
    var i = 2
      , args = arguments
      , formatters = this.app.flashFormatters || {};
    formatters.__proto__ = flashFormatters;
    msg = utils.miniMarkdown(utils.htmlEscape(msg));
    msg = msg.replace(/%([a-zA-Z])/g, function(_, format){
      var formatter = formatters[format];
      if (formatter) return formatter(args[i++]);
    });
    return (msgs[type] = msgs[type] || []).push(msg);
  } else if (type) {
    var arr = msgs[type];
    delete msgs[type];
    return arr || [];
  } else {
    this.session.flash = {};
    return msgs;
  }
};

Check if the incoming request contains the "Content-Type" header field, and it contains the give mime type.

Examples

 // With Content-Type: text/html; charset=utf-8
 req.is('html');
 req.is('text/html');
 // => true

 // When Content-Type is application/json
 req.is('json');
 req.is('application/json');
 // => true

 req.is('html');
 // => false

Ad-hoc callbacks can also be registered with Express, to perform assertions again the request, for example if we need an expressive way to check if our incoming request is an image, we can register "an image" callback:

  app.is('an image', function(req){
    return 0 == req.headers['content-type'].indexOf('image');
  });

Now within our route callbacks, we can use to to assert content types such as "image/jpeg", "image/png", etc.

 app.post('/image/upload', function(req, res, next){
   if (req.is('an image')) {
     // do something
   } else {
     next();
   }
 });

  • param: String type

  • return: Boolean

  • api: public

http.IncomingMessage.prototype.is = function(type){
  var fn = this.app.is(type);
  if (fn) return fn(this);
  var contentType = this.headers['content-type'];
  if (!contentType) return;
  if (!~type.indexOf('/')) type = mime.type('.' + type);
  // TODO: remove when connect no longer appends charset...
  if (~type.indexOf(';')) type = type.split(';')[0];
  if (~type.indexOf('*')) {
    type = type.split('/')
    contentType = contentType.split('/');
    if ('*' == type[0] &amp;&amp; type[1] == contentType[1]) return true;
    if ('*' == type[1] &amp;&amp; type[0] == contentType[0]) return true;
  }
  return ~contentType.indexOf(type);
};

// Callback for isXMLHttpRequest / xhr

function isxhr() {
  return this.header('X-Requested-With', '').toLowerCase() === 'xmlhttprequest';
}

Check if the request was an XMLHttpRequest.

  • return: Boolean

  • api: public

http.IncomingMessage.prototype.__defineGetter__('isXMLHttpRequest', isxhr);
http.IncomingMessage.prototype.__defineGetter__('xhr', isxhr);

view

lib/express/view.js

Module dependencies.

var extname = require('path').extname
  , utils = require('connect').utils
  , http = require('http')
  , fs = require('fs')
  , mime = utils.mime;

Cache supported template engine exports to increase performance by lowering the number of calls to require().

  • type: Object

var cache = {};

Cache view contents to prevent I/O hits.

  • type: Object

var viewCache = {};

Synchronously cache view at the given path.

  • param: String path

  • return: String

  • api: private

function cacheViewSync(path) {
  return viewCache[path] = fs.readFileSync(path, 'utf8');
}

Return view root path for the given app.

  • param: express.Server app

  • return: String

  • api: private

function viewRoot(app) {
  return app.set('views') || process.cwd() + '/views';
}

Register the given template engine exports as ext. For example we may wish to map ".html" files to jade:

app.register('.html', require('jade'));

This is also useful for libraries that may not match extensions correctly. For example my haml.js library is installed from npm as "hamljs" so instead of layout.hamljs, we can register the engine as ".haml":

app.register('.haml', require('haml-js'));

For engines that do not comply with the Express specification, we can also wrap their api this way.

app.register('.foo', { render: function(str, options) { // perhaps their api is // return foo.toHTML(str, options); } });

  • param: String ext

  • param: Object obj

  • api: public

exports.register = function(ext, exports) {
  cache[ext] = exports;
};

Render view partial with the given options.

Options

  • object Single object with name derived from the view (unless as is present)

  • as Variable name for each collection value, defaults to the view name.

    • as: 'something' will add the something local variable
    • as: this will use the collection value as the template context
    • as: global will merge the collection value's properties with locals

  • collection Array of objects, the name is derived from the view name itself. For example video.html will have a object video available to it.

  • param: String view

  • param: Object | Array options or collection

  • return: String

  • api: public

http.ServerResponse.prototype.partial = function(view, options, ext, locals){
  // Inherit parent view extension when not present
  if (ext &amp;&amp; view.indexOf('.') &lt; 0) {
    view += ext;
  }

  // Allow collection to be passed as second param
  if (options &amp;&amp; options.hasOwnProperty('length')) {
    options = { collection: options };
  }

  // Defaults
  options = options || {};

  // Inherit locals from parent
  options.locals = options.locals || {};
  utils.merge(options.locals, locals);

  // Partials dont need layouts
  options.partial = true;
  options.layout = false;

  // Deduce name from view path
  var name = options.as || view.split('/').slice(-1)[0].split('.')[0];

  // Collection support
  var collection = options.collection;
  if (collection) {
    var len = collection.length
      , buf = '';
    delete options.collection;
    options.locals.collectionLength = len;
    for (var i = 0; i &lt; len; ++i) {
      var val = collection[i];
      options.locals.firstInCollection = i === 0;
      options.locals.indexInCollection = i;
      options.locals.lastInCollection = i === len - 1;
      options.object = val;
      buf += this.partial(view, options);
    }
    return buf;
  } else {
    if (options.object) {
      if ('string' == typeof name) {
        options.locals[name] = options.object;
      } else if (name === global) {
        utils.merge(options.locals, options.object);
      } else {
        options.scope = options.object;
      }
    }
    return this.render(view, options);
  }
};

Render view with the given options and optional callback fn. When a callback function is given a response will not be made automatically, however otherwise a response of 200 and text/html is given.

Options

Most engines accept one or more of the following options, both haml and jade accept all:

  • scope Template evaluation context (the value of this)
  • locals Object containing local variables
  • debug Output debugging information
  • status Response status code, defaults to 200
  • headers Response headers object

  • param: String view

  • param: Object | Function options or callback function

  • param: Function fn

  • api: public

http.ServerResponse.prototype.render = function(view, options, fn){
  // Support callback function as second arg
  if (typeof options === 'function') {
    fn = options, options = {};
  }
  
  var options = options || {}
    , app = this.app
    , viewOptions = app.settings['view options']
    , defaultEngine = app.settings['view engine'];

  // Mixin "view options"
  if (viewOptions) options.__proto__ = viewOptions;

  // Support "view engine" setting
  if (view.indexOf('.') &lt; 0 &amp;&amp; defaultEngine) {
    view += '.' + defaultEngine;
  }

  // Defaults
  var self = this
    , helpers = this.app.viewHelpers
    , dynamicHelpers = this.app.dynamicViewHelpers
    , root = viewRoot(this.app)
    , ext = extname(view)
    , partial = options.partial
    , layout = options.layout === undefined ? true : options.layout
    , layout = layout === true
      ? 'layout' + ext
      : layout;

  // Allow layout name without extension
  if (typeof layout === 'string' &amp;&amp; layout.indexOf('.') &lt; 0) {
    layout += ext;
  }

  // Default execution scope to the response
  options.scope = options.scope || this.req;

  // Auto-cache in production
  if (this.app.set('env') === 'production') {
    options.cache = true;
  }

  // Partials support
  if (options.partial) {
    root = app.settings.partials || root + '/partials';
  }

  // View path
  var path = view[0] === '/'
    ? view
    : root + '/' + view;

  // Pass filename to the engine and view
  var locals = options.locals = options.locals || {};
  options.locals.__filename = options.filename = path;

  // Dynamic helper support
  if (false !== options.dynamicHelpers) {
    // cache
    if (!this.__dynamicHelpers) {
      this.__dynamicHelpers = {};
      var keys = Object.keys(dynamicHelpers);
      for (var i = 0, len = keys.length; i &lt; len; ++i) {
        var key = keys[i]
          , val = dynamicHelpers[key];
        if (typeof val === 'function') {
          this.__dynamicHelpers[key] = val.call(
                this.app
              , this.req
              , this);
        }
      }
    }

    // apply
    helpers.__proto__ = this.__dynamicHelpers;
  }

  // Merge view helpers
  options.locals.__proto__ = helpers;

  // Always expose partial() as a local
  options.locals.partial = function(view, options){
    return self.partial.call(self, view, options, ext, locals);
  };

  function error(err) {
    if (fn) {
      fn(err);
    } else {
      self.req.next(err);
    }
  }

  // Cache contents
  try {
    var str = (options.cache ? viewCache[path] : null) || cacheViewSync(path);
  } catch (err) {
    return error(err);
  }

  // Cache template engine exports
  var engine = cache[ext] || (cache[ext] = require(ext.substr(1)));

  // Attempt render
  try {
    var str = engine.render(str, options);
  } catch (err) {
    return error(err);
  }

  // Layout support
  if (layout) {
    options.layout = false;
    options.locals.body = str;
    options.isLayout = true;
    self.render(layout, options, fn);
  } else if (partial) {
    return str;
  } else if (fn) {
    fn(null, str);
  } else {
    this.send(str, options.headers, options.status);
  }
};