ExpressHigh performance web framework for node. | |
| 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.0';
|
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');
|
Response extensions.
|
require('./response');
|
Request extensions.
|
require('./request');
|
| lib/express/request.js |
Module dependencies.
|
var http = require('http')
, utils = require('./utils')
, mime = require('connect/utils').mime;
|
Default flash formatters.
|
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"
|
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){
if (this.params[name] !== undefined) {
return this.params[name];
}
if (this.query[name] !== undefined) {
return this.query[name];
}
if (this.body && 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.
|
http.IncomingMessage.prototype.flash = function(type, msg){
var msgs = this.session.flash = this.session.flash || {};
if (type && 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);
if (~type.indexOf(';')) type = type.split(';')[0];
if (~type.indexOf('*')) {
type = type.split('/')
contentType = contentType.split('/');
if ('*' == type[0] && type[1] == contentType[1]) return true;
if ('*' == type[1] && type[0] == contentType[0]) return true;
}
return ~contentType.indexOf(type);
};
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);
|
| 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.
|
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);
|
http.ServerResponse.prototype.send = function(body, headers, status){
if (typeof headers === 'number') {
status = headers,
headers = null;
}
status = status || 200;
if (!arguments.length) {
body = status = 204;
}
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 && this.app.settings['jsonp callback']) {
this.header('Content-Type', 'text/javascript');
body = this.req.query.callback + '(' + body + ');';
}
}
break;
}
if (!this.headers['Content-Length']) {
this.header('Content-Length', body instanceof Buffer
? body.length
: Buffer.byteLength(body));
}
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]);
}
}
if (204 === status) {
delete this.headers['Content-Type'];
delete this.headers['Content-Length'];
}
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 >= 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);
} else {
var stream = fs.createReadStream(path);
}
self.contentType(path);
self.header('Accept-Ranges', 'bytes');
self.writeHead(status, self.headers);
pump(stream, self, function(err){
fn && fn(err, path, true);
});
} else {
fs.readFile(path, function(err, buf){
if (err) return error(err);
self.contentType(path);
self.send(buf);
fn && 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"
|
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] && ~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;
var map = {
back: this.req.headers.referrer || this.req.headers.referer || basePath,
home: basePath
};
map.__proto__ = this.app.redirects;
var mapped = typeof map[url] === 'function'
? map[url](this.req, this)
: map[url];
url = mapped || url;
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');
}
this.send(body, { Location: url }, status);
};
|
| 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 || []);
this.set('home', '/');
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.');
}
this.showVersion = false;
this.set('env', process.env.NODE_ENV || 'development');
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;
if (req.url.indexOf('?') > 0) {
var query = url.parse(req.url).query;
req.query = exports.parseQueryString(query);
}
next();
});
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() .
|
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);
if (middleware instanceof Server) {
var app = middleware
, home = app.set('home');
if (home === '/') home = '';
app.set('home', (app.route || '') + home);
app.parent = this;
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);
|
Server.prototype.mounted = function(fn){
this.__mounted = fn;
return this;
};
|
See view.register.
|
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.
|
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.
|
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.
|
Server.prototype.error = function(fn){
this.errorHandlers.push(fn);
return this;
};
|
Register the given callback fn for the given type .
|
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.
|
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 .
|
Server.prototype.enable = function(setting){
return this.set(setting, true);
};
|
Disable setting .
|
Server.prototype.disable = function(setting){
return this.set(setting, false);
};
|
Redirect key to url .
|
Server.prototype.redirect = function(key, url){
this.redirects[key] = url;
return this;
};
|
Configure callback for the given env .
|
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;
};
(function(method){
Server.prototype[method] = function(path, fn){
var self = this;
if (!this.__usedRouter) {
this.use(this.router);
}
if (arguments.length > 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);
}
this.routes[method](path, fn);
return this;
};
return arguments.callee;
})('get')('post')('put')('delete')('all');
Server.prototype.del = Server.prototype.delete;
|
| 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);
if (isNaN(start)) {
start = size - end;
end = size - 1;
} else if (isNaN(end)) {
end = size - 1;
}
if (isNaN(start) || isNaN(end) || start > end) valid = false;
return { start: start, end: end };
});
return valid ? arr : undefined;
};
|
| 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() .
|
var cache = {};
|
Cache view contents to prevent I/O hits.
|
var viewCache = {};
|
Cache view path derived names.
|
var viewNameCache = {};
|
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 .
|
function viewRoot(app) {
return app.set('views') || process.cwd() + '/views';
}
|
Return object name deduced from the given view path.
Examples
"movie/director" -> director
"movie.director" -> director
"forum/thread/post" -> post
"forum/thread.post" -> post
param: String view return: String api: private
|
function objectName(view) {
return view.split('/').slice(-1)[0].split('.')[0];
}
|
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.
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.
|
http.ServerResponse.prototype.partial = function(view, options, ext, locals){
locals = locals || {};
if (ext && view.indexOf('.') < 0) {
view += ext;
}
if (options) {
if ('length' in options) {
options = { collection: options };
} else if (!options.collection && !options.locals && !options.object) {
options = { object: options };
}
} else {
options = {};
}
options.locals = options.locals
? utils.merge(locals, options.locals)
: locals;
options.partial = true;
options.layout = false;
var name = options.as
|| viewNameCache[view]
|| (viewNameCache[view] = objectName(view));
var collection = options.collection;
if (collection) {
var len = collection.length
, buf = '';
delete options.collection;
options.locals.collectionLength = len;
for (var i = 0; i < 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 variablesdebug Output debugging informationstatus Response status code, defaults to 200headers Response headers object
|
http.ServerResponse.prototype.render = function(view, options, fn){
if (typeof options === 'function') {
fn = options, options = {};
}
var options = options || {}
, app = this.app
, viewOptions = app.settings['view options']
, defaultEngine = app.settings['view engine'];
if (viewOptions) options.__proto__ = viewOptions;
if (view.indexOf('.') < 0 && defaultEngine) {
view += '.' + defaultEngine;
}
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;
if (typeof layout === 'string' && layout.indexOf('.') < 0) {
layout += ext;
}
options.scope = options.scope || this.req;
if ('production' == app.settings.env) {
options.cache = true;
}
if (options.partial) {
root = app.settings.partials || root + '/partials';
}
var path = view[0] === '/'
? view
: root + '/' + view;
var locals = options.locals = options.locals || {};
options.locals.__filename = options.filename = path;
if (false !== options.dynamicHelpers) {
if (!this.__dynamicHelpers) {
this.__dynamicHelpers = {};
var keys = Object.keys(dynamicHelpers);
for (var i = 0, len = keys.length; i < len; ++i) {
var key = keys[i]
, val = dynamicHelpers[key];
if (typeof val === 'function') {
this.__dynamicHelpers[key] = val.call(
this.app
, this.req
, this);
}
}
}
helpers.__proto__ = this.__dynamicHelpers;
}
options.locals.__proto__ = helpers;
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);
}
}
try {
var str = (options.cache ? viewCache[path] : null) || cacheViewSync(path);
} catch (err) {
return error(err);
}
var engine = cache[ext] || (cache[ext] = require(ext.substr(1)));
try {
var str = engine.render(str, options);
} catch (err) {
return error(err);
}
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);
}
};
|