Connect

High performance middleware for node.

bodyDecoder

lib/connect/middleware/bodyDecoder.js

Module dependencies.

var queryString = require('querystring');

Supported decoders.

  • application/x-www-form-urlencoded
  • application/json
exports.decode = {
    'application/x-www-form-urlencoded': queryString.parse,
    'application/json': JSON.parse
};

Decode request bodies.

  • return: Function

  • api: public

module.exports = function bodyDecoder(){
    return function bodyDecoder(req, res, next) {
        var decoder = exports.decode[mime(req)];
        if (decoder) {
            var data = '';
            req.setEncoding('utf8');
            req.addListener('data', function(chunk) { data += chunk; });
            req.addListener('end', function() {
                req.rawBody = data;
                try {
                    req.body = data
                        ? decoder(data)
                        : {};
                } catch (err) {
                    return next(err);
                }
                next();
            });
        } else {
            next();
        }
    }
};

cache

lib/connect/middleware/cache.js

Module dependencies.

var Buffer = require('buffer').Buffer;

Cache in memory for the given cacheDuration.

  • param: Number cacheDuration

  • return: Function

  • api: public

module.exports = function cache(cacheDuration){
    var cache = {},
        queue = {};

    return function cache(req, res, next) {

        // Skip all requests that are not GET method
        if (req.method !== "GET") {
            next();
            return;
        }

        var key = req.headers["accept-encoding"] + req.url,
            writeHead = res.writeHead,
            write = res.write,
            end = res.end,
            code,
            headers,
            chunks = [],
            totalSize = 0;

        function serve() {
            var resp = cache[key];
            var headers = resp.headers;
            headers["Date"] = (new Date).toUTCString();
            headers["Content-Length"] = resp.body.length;

            if (localQueue) {
                // Send everyone in the wait queue the response.
                for (var i = 0, l = localQueue.length; i < l; i++) {
                    var localRes = localQueue[i];
                    localRes.writeHead(resp.code, headers);
                    localRes.end(resp.body);
                }
                // Kill the wait queue
                delete queue[key];
            } else {
                res.writeHead(resp.code, headers);
                res.end(resp.body);
            }

        }

        // If there is a cache-hit, serve it right away!
        if (cache[key]) {
            serve();
            return;
        }

        var localQueue = queue[key];
        if (localQueue) {
            localQueue[localQueue.length] = res;
            return;
        }
        localQueue = queue[key] = [res];

        // Defer the call to writeHead
        res.writeHead = function (setCode, setHeaders) {
            code = setCode;
            headers = setHeaders;
        };

        // Buffer the response body as an array of Buffer objects
        res.write = function (chunk, encoding) {
            if (typeof chunk === 'string') {
                var length;
                if (!encoding || encoding === 'utf8') {
                    length = Buffer.byteLength(chunk);
                }
                var buffer = new Buffer(length);
                buffer.write(chunk, encoding);
                chunks.push(buffer);
            } else {
                chunks.push(chunk);
            }
            totalSize += chunk.length;
        };

        res.end = function (chunk, encoding) {
            if (chunk) {
                res.write(chunk, encoding);
            }

            // Combine the buffer array into a single buffer
            var body = new Buffer(totalSize);
            var offset = 0;
            chunks.forEach(function (chunk) {
                chunk.copy(body, offset);
                offset += chunk.length;
            });

            // Store the result in the cache
            cache[key] = {
                body: body,
                code: code,
                headers: headers
            };

            // Put the original methods back in place
            res.writeHead = writeHead;
            res.write = write;
            res.end = end;

            // Serve the response from the cache
            serve();

            if (cacheDuration) {
                // Expire the ram cache after 100ms or the specified length
                setTimeout(function(){
                    delete cache[key];
                }, cacheDuration);
            } else {
                // When the timeout is zero, just kill it right away
                delete cache[key];
            }

        };

        next();
    };
};

cacheManifest

lib/connect/middleware/cacheManifest.js

Module dependencies.

var fs = require('fs'),
    Utils = require('../utils'),
    Url = require('url'),
    Path = require('path');

Generate cache manifest for the given root, networks, and fallbacks.

  • param: String root

  • param: Array networks

  • param: Array fallbacks

  • return: Function

  • api: public

module.exports = function cacheManifest(root, networks, fallbacks) {
    root = root || process.cwd();
    var suffix = "";

    // List of networks as an array of strings
    if (networks) {
        suffix += "\n\nNETWORK:\n" + networks.join("\n");
    }

    // List of fallbacks as key/value pairs
    if (fallbacks) {
        suffix += "\n\nFALLBACK:\n" +
            fallbacks.map(function (second, first) {
                return first + " " + second;
            }).join("\n");
    }

    return function cacheManifest(req, res, next) {
        if (Url.parse(req.url).pathname === "/cache.manifest") {
            Utils.find(root, (/./), function (err, files) {
                var latestMtime = 0;
                files = files.map(function (entry) {
                    if (entry.mtime > latestMtime) {
                        latestMtime = entry.mtime;
                    }
                    return entry.path.substr(1);
                });
                var manifest = "CACHE MANIFEST\n"
                    + "# " + latestMtime.toUTCString() + "\n"
                    + files.join("\n")
                    + suffix;

                res.writeHead(200, {
                    "Content-Type": "text/cache-manifest",
                    "Last-Modified": latestMtime.toUTCString(),
                    "Content-Length": manifest.length
                });
                res.end(manifest);
            });
            return;
        }
        next();
    };
};

compiler

lib/connect/middleware/compiler.js

Module dependencies.

var fs = require('fs'),
    path = require('path');

Bundled compilers:

var compilers = exports.compilers = {
    sass: {
        match: /\.css$/,
        ext: '.sass',
        compile: function(str, fn){
            require.async('sass', function(err, sass){
                if (err) {
                    fn(err);
                } else {
                    try {
                        fn(null, sass.render(str));
                    } catch (err) {
                        fn(err);
                    }
                }
            });
        }
    },
    less: {
        match: /\.css$/,
        ext: '.less',
        compile: function(str, fn){
            require.async('less', function(err, less){
                if (err) {
                    fn(err);
                } else {
                    try {
                        less.render(str, fn);
                    } catch (err) {
                        fn(err);
                    }
                }
            });
        }
    }
};

Setup compiler.

Options

  • src Source directory, defaults to CWD.
  • dest Destination directory, defaults src.
  • enable Array of enabled compilers.

Compilers

  • sass Compiles cass to css
  • less Compiles less to css

  • param: Object options

  • api: public

module.exports = function compiler(options){
    options = options || {};

    var srcDir = process.connectEnv.compilerSrc || options.src || process.cwd(),
        destDir = process.connectEnv.compilerDest || options.dest || srcDir,
        enable = options.enable;

    if (!enable || enable.length === 0) {
        throw new Error(s "enable" option is not set, nothing will be compiled.');
    }

    return function compiler(req, res, next){
        for (var i = 0, len = enable.length; i < len; ++i) {
            var name = enable[i],
                compiler = compilers[name];
            if (compiler.match.test(req.url)) {
                var src = (srcDir + req.url).replace(compiler.match, compiler.ext),
                    dest = destDir + req.url;

                // Compare mtimes
                fs.stat(src, function(err, srcStats){
                    if (err) {
                        if (err.errno === process.ENOENT) {
                            res.writeHead(404, { 'Content-Type': 'text/plain' });
                            res.end('Not Found');
                        } else {
                            next(err);
                        }
                    } else {
                        fs.stat(dest, function(err, destStats){
                            if (err) {
                                // Oh snap! it does not exist, compile it
                                if (err.errno === process.ENOENT) {
                                    compile();
                                } else {
                                    next(err);
                                }
                            } else {
                                // Source has changed, compile it
                                if (srcStats.mtime > destStats.mtime) {
                                    compile();
                                } else {
                                    // Defer file serving
                                    next();
                                }
                            }
                        })
                    }
                });

                // Compile to the destination
                function compile() {
                    fs.readFile(src, 'utf8', function(err, str){
                        if (err) {
                            next(err);
                        } else {
                            compiler.compile(str, function(err, str){
                                if (err) {
                                    next(err);
                                } else {
                                    fs.writeFile(dest, str, 'utf8', function(err){
                                        next(err);
                                    });
                                }
                            });
                        }
                    });
                }
                return;
            }
        }
        next();
    };
};

conditionalGet

lib/connect/middleware/conditionalGet.js

Conditional GET request support.

  • return: Function

  • api: public

module.exports = function conditionalGet(){
    return function conditionalGet(req, res, next) {
        // Skip all requests that are not conditional gets.
        if (!(req.method === "GET" &&
             (req.headers["if-modified-since"] || req.headers["if-none-match"])
           )) {
            next();
            return;
        }

        var since = req.headers["if-modified-since"],
            oldEtag = req.headers["if-none-match"],
            writeHead = res.writeHead,
            write = res.write,
            end = res.end;

        since = since && Date.parse(since).valueOf();

        res.writeHead = function (code, headers) {
            var lastModified = headers["Last-Modified"],
                etag = headers["Etag"];
            lastModified = lastModified && Date.parse(lastModified).valueOf();

            // If there is no match, then move on.
            if (!(code === 200 &&
                  (lastModified === since || oldEtag === etag)
               )) {
                res.writeHead = writeHead;
                res.writeHead(code, headers);
                return;
            }

            // Ignore writes
            res.write = function () {};

            res.end = function () {
                // Put the original functions back on.
                res.writeHead = writeHead;
                res.write = write;
                res.end = end;

                // Filter out any Content based headers since there is no
                // content.
                var newHeaders = {};
                headers.forEach(function (value, key) {
                    if (key.indexOf("Content") < 0) {
                        newHeaders[key] = value;
                    }
                });
                res.writeHead(304, newHeaders);
                res.end();
            };
        };

        next();
    };
};

cookieDecoder

lib/connect/middleware/cookieDecoder.js

Module dependencies.

var utils = require('./../utils');

Parse Cookie header and populate req.cookies.

  • return: Function

  • api: public

module.exports = function cookieDecoder(){
    return function cookieDecoder(req, res, next) {
        var cookie = req.headers.cookie;
        req.cookies = {};
        if (cookie) {
            try {
                req.cookies = utils.parseCookie(cookie);
                delete req.headers.cookie;
            } catch (err) {
                // Ignore
            }
            next();
        } else {
            next();
        }
    };
};

errorHandler

lib/connect/middleware/errorHandler.js

Module dependencies.

var utils = require('./../utils'),
    sys = require('sys'),
    fs = require('fs');

Setup error handler with the given options.

Options

  • showStack respond with both the error message and stack trace. Defaults to false
  • showMessage respond with the exception message only. Defaults to false
  • dumpExceptions dump exceptions to stderr (without terminating the process). Defaults to false

  • param: Object options

  • return: Function

  • api: public

module.exports = function errorHandler(options){
    options = options || {};

    // Defaults
    var showStack = options.showStack,
        showMessage = options.showMessage,
        dumpExceptions = options.dumpExceptions;

    // --showErrorStack
    if (process.connectEnv.showErrorStack !== undefined) {
        showStack = op = utils.toBoolean(process.connectEnv.showErrorStack);
    }

    // --showErrorMessage
    if (process.connectEnv.showErrorMessage !== undefined) {
        showMessage = utils.toBoolean(process.connectEnv.showErrorMessage);
    }

    // --dumpExceptions
    if (process.connectEnv.dumpExceptions !== undefined) {
        dumpExceptions = utils.toBoolean(process.connectEnv.dumpExceptions);
    }

    return function errorHandler(err, req, res, next){
        if (dumpExceptions) {
            sys.error(err.stack);
        }
        if (showStack) {
            var accept = req.headers.accept || '';
            if (accept.indexOf('html') !== -1) {
                fs.readFile(__dirname + '/../public/style.css', function(e, style){
                    style = style.toString('ascii');
                    fs.readFile(__dirname + '/../public/error.html', function(e, html){
                        var stack = err.stack
                            .split('\n').slice(1)
                            .map(function(v){ return '<li>' + v + '</li>'; }).join('');
                        html = html
                            .toString('utf8')
                            .replace('{style}', style)
                            .replace('{stack}', stack)
                            .replace(/\{error\}/g, err.toString());
                        res.writeHead(500, { 'Content-Type': 'text/html' });
                        res.end(html);
                    });
                });
            } else if (accept.indexOf('json') !== -1) {
                var json = JSON.stringify({ error: err });
                res.writeHead(500, { 'Content-Type': 'application/json' });
                res.end(json);
            } else {
                res.writeHead(500, { 'Content-Type': 'application/json' });
                res.end(err.stack);
            }
        } else {
            var body = showMessage
                ? err.toString()
                : 'Internal Server Error';
            res.writeHead(500, { 'Content-Type': 'text/plain' });
            res.end(body);
        }
    };
};

gzip-compress

lib/connect/middleware/gzip-compress.js

Module dependencies.

var child_process = require('child_process'),
    sys = require('sys'),
    compress = require('compress');

Provides gzip compression via the node-compress library.

  • return: Function

  • api: public

module.exports = function gzip(){
    return function gzip(req, res, next) {
        var writeHead = res.writeHead,
            write = res.write,
            end = res.end;

        res.writeHead = function (code, headers) {
            var type = headers[&quot;Content-Type&quot;],
                accept = req.headers[&quot;accept-encoding&quot;];

            if (!(code === 200 &amp;&amp; accept &amp;&amp; accept.indexOf('gzip') &gt;= 0
                  &amp;&amp; type &amp;&amp; (/(text|javascript|json)/).test(type)
                  &amp;&amp; headers[&quot;Content-Encoding&quot;] === undefined)) {
                res.writeHead = writeHead;
                res.writeHead(code, headers);
                return;
            }

            headers[&quot;Content-Encoding&quot;] = &quot;gzip&quot;;
            delete headers[&quot;Content-Length&quot;];

            var gzip = new compress.GzipStream();

            res.write = function (chunk, encoding) {
                gzip.setInputEncoding(encoding);
                gzip.write(chunk);
            };

            res.end = function (chunk, encoding) {
                if (chunk) {
                    res.write(chunk, encoding);
                }
                gzip.close();
            };

            gzip.addListener('data', function (chunk) {
                write.call(res, chunk);
            });
            gzip.addListener('error', function(err) {
                res.write = write;
                res.end = end;
                next(err);
            });
            gzip.addListener('end', function (code) {
                res.write = write;
                res.end = end;
                res.end();
            });

            res.writeHead = writeHead;
            res.writeHead(code, headers);

        };

        next();
    };
};

gzip-proc

lib/connect/middleware/gzip-proc.js

Module dependencies.

var child_process = require('child_process'),
    sys = require('sys');

Provides gzip compression via the gzip executable.

  • return: Function

  • api: public

module.exports = function gzip(){
    return function gzip(req, res, next) {
        var writeHead = res.writeHead,
            write = res.write,
            end = res.end;

        res.writeHead = function (code, headers) {
            var type = headers[&quot;Content-Type&quot;],
                accept = req.headers[&quot;accept-encoding&quot;];

            if (!(code === 200 &amp;&amp; accept &amp;&amp; accept.indexOf('gzip') &gt;= 0
                  &amp;&amp; type &amp;&amp; (/(text|javascript|json)/).test(type)
                  &amp;&amp; headers[&quot;Content-Encoding&quot;] === undefined)) {
                res.writeHead = writeHead;
                res.writeHead(code, headers);
                return;
            }

            headers[&quot;Content-Encoding&quot;] = &quot;gzip&quot;;
            delete headers[&quot;Content-Length&quot;];

            var gzip = child_process.spawn(&quot;gzip&quot;, [&quot;-9&quot;]);

            res.write = function (chunk, encoding) {
                gzip.stdin.write(chunk, encoding);
            };

            res.end = function (chunk, encoding) {
                if (chunk) {
                    res.write(chunk, encoding);
                }
                gzip.stdin.end();
            };

            gzip.stdout.addListener('data', function (chunk) {
                write.call(res, chunk);
            });

            gzip.addListener(&quot;exit&quot;, function (code) {
                res.write = write;
                res.end = end;
                res.end();
            });

            res.writeHead = writeHead;
            res.writeHead(code, headers);

        };

        next();
    };
};

gzip

lib/connect/middleware/gzip.js

try { module.exports = require('./gzip-compress'); } catch (e) { if (/^Cannot find module /.test(e.message)) module.exports = require('./gzip-proc'); else throw e; }

jsonrpc

lib/connect/middleware/jsonrpc.js

Module dependencies.

var sys = require('sys'),
    parse = require('url').parse,
    Buffer = require('buffer').Buffer,
    http = require('http');

Export the setup() function.

exports = module.exports = jsonrpc;

JSON-RPC version.

var VERSION = exports.VERSION = '2.0';

JSON parse error.

var PARSE_ERROR = exports.PARSE_ERROR = -32700;

Invalid request due to invalid or missing properties.

var INVALID_REQUEST = exports.INVALID_REQUEST = -32600;

Service method does not exist.

var METHOD_NOT_FOUND = exports.METHOD_NOT_FOUND = -32601;

Invalid parameters.

var INVALID_PARAMS = exports.INVALID_PARAMS = -32602;

Internal JSON-RPC error.

var INTERNAL_ERROR = exports.INTERNAL_ERROR = -32603;

Default error messages.

var errorMessages = exports.errorMessages = {};
errorMessages[PARSE_ERROR] = 'Parse Error.';
errorMessages[INVALID_REQUEST] = 'Invalid Request.';
errorMessages[METHOD_NOT_FOUND] = 'Method Not Found.';
errorMessages[INVALID_PARAMS] = 'Invalid Params.';
errorMessages[INTERNAL_ERROR] = 'Internal Error.';

Accepts any number of objects, exposing their methods.

  • param: Object ...

  • return: Function

  • api: public

function jsonrpc(services) {
    services = services || {};

    // Merge methods
    for (var i = 0, len = arguments.length; i &lt; len; ++i) {
        arguments[i].forEach(function(val, key){
            services[key] = val;
        });
    }

Handle JSON-RPC request.

  • param: Object rpc

  • param: Function respond

function handleRequest(rpc, respond){
        if (validRequest(rpc)) {
            var method = services[rpc.method];
            if (typeof method === 'function') {
                var params = [];
                if (rpc.params instanceof Array) {
                    params = rpc.params;
                } else if (typeof rpc.params === 'object') {
                    var names = method.toString().match(/\((.*?)\)/)[1].match(/[\w]+/g);
                    if (names) {
                        for (var i = 0, len = names.length; i &lt; len; ++i) {
                            params.push(rpc.params[names[i]]);
                        }
                    } else {
                        // Function does not have named parameters
                        return respond({ error: { code: INVALID_PARAMS, message: 'This service does not support named parameters.' }});
                    }
                }
                function reply(err, result){
                    if (err) {
                        if (typeof err === 'number') {
                            respond({
                                error: {
                                    code: err
                                }
                            });
                        } else {
                            respond({
                                error: {
                                    code: err.code || INTERNAL_ERROR,
                                    message: err.message
                                }
                            });
                        }
                    } else {
                        respond({
                            result: result
                        });
                    }
                }
                method.apply(reply, params);
            } else {
                respond({ error: { code: METHOD_NOT_FOUND }});
            }
        } else {
            respond({ error: { code: INVALID_REQUEST }});
        }
    }

    return function jsonrpc(req, res, next) {
        var me = this,
            contentType = req.headers['content-type'] || '';
        if (req.method === 'POST' &amp;&amp; contentType.indexOf('application/json') &gt;= 0) {
            var data = '';
            req.setEncoding('utf8');
            req.addListener('data', function(chunk) { data += chunk; });
            req.addListener('end', function() {

                // Attempt to parse incoming JSON string

                try {
                    var rpc = JSON.parse(data),
                        batch = rpc instanceof Array;
                } catch (err) {
                    return respond(normalize(rpc, { error: { code: PARSE_ERROR }}));
                }

Normalize response object.

function normalize(rpc, obj) {
                    obj.id = rpc &amp;&amp; typeof rpc.id === 'number'
                        ? rpc.id
                        : null;
                    obj.jsonrpc = VERSION;
                    if (obj.error &amp;&amp; !obj.error.message) {
                        obj.error.message = errorMessages[obj.error.code];
                    }
                    return obj;
                }

Respond with the given response object.

function respond(obj) {
                    var body = JSON.stringify(obj);
                    res.writeHead(200, {
                        'Content-Type': 'application/json',
                        'Content-Length': Buffer.byteLength(body)
                    });
                    res.end(body);
                }

                // Handle requests

                if (batch) {
                    var responses = [],
                        len = rpc.length,
                        pending = len;
                    for (var i = 0; i &lt; len; ++i) {
                        (function(rpc){
                            handleRequest.call(me, rpc, function(obj){
                                responses.push(normalize(rpc, obj));
                                if (!--pending) {
                                    respond(responses);
                                }
                            });
                        })(rpc[i]);
                    }
                } else {
                    handleRequest.call(me, rpc, function(obj){
                        respond(normalize(rpc, obj));
                    });
                }
            });
        } else {
            next();
        }
    };
};

lint

lib/connect/middleware/lint.js

Module dependencies.

var connect = require('./../index'),
    sys = require('sys');

Setup lint for the given server.

  • return: Function

  • api: public

module.exports = function lint(server){

    // Ensure a server is passed
    if (!server) {
        throw new Error('lint "server" must be passed.');
    }

    // Warn unless in development mode
    if (process.connectEnv.name !== 'development') {
        warn('"lint" middleware should never be enabled outside of the development environment');
    }

    // Check the stack
    checkStack(server.stack);

    // Do nothing
    return function(req, res, next){
        next();
    }
};

logger

lib/connect/middleware/logger.js

Log requests with the given options.

Options

  • format Format string, see below for tokens
  • stream Output stream, defaults to stdout

Tokens

  • :req[header] ex: :req[Accept]
  • :res[header] ex: :res[Content-Length]
  • :http-version
  • :response-time
  • :remote-addr
  • :date
  • :method
  • :url
  • :referrer
  • :user-agent
  • :status

  • return: Function

  • api: public

module.exports = function logger(options) {
    options = options || {};

    var fmt = process.connectEnv.logFormat || options.format,
        stream = options.stream || process.stdout;

    return function logger(req, res, next) {
        var start = +new Date,
            statusCode,
            resHeaders,
            writeHead = res.writeHead,
            end = res.end,
            url = req.url;

        // Proxy for statusCode.
        res.writeHead = function(code, headers){
            res.writeHead = writeHead;
            res.writeHead(code, headers);
            res.statusCode = statusCode = code;
            res.headers = resHeaders = headers;
        };

        // Proxy end to output a line to the provided logger.
        if (fmt) {
            res.end = function(chunk, encoding) {
                res.end = end;
                res.end(chunk, encoding);
                res.responseTime = +new Date - start;
                stream.write(format(fmt, req, res) + '\n', 'ascii');
            };
        } else {
            res.end = function(chunk, encoding) {
                res.end = end;
                res.end(chunk, encoding);

                stream.write((req.socket &amp;&amp; req.socket.remoteAddress)
                 + ' - - [' + (new Date).toUTCString() + ']'
                 + ' "' + req.method + ' ' + url
                 + ' HTTP/' + req.httpVersionMajor + '.' + req.httpVersionMinor + '" '
                 + statusCode + ' ' + (resHeaders['Content-Length'] || '-')
                 + ' "' + (req.headers['referer'] || req.headers['referrer'] || '')
                 + '" "' + (req.headers['user-agent'] || '') + '"\n', 'ascii');
            };
        }

        // Fall through to the next layer.
        next();
    };

};

methodOverride

lib/connect/middleware/methodOverride.js

Module dependencies.

var queryString = require('querystring');

Valid http methods.

  • type: Array

var methods = ['GET', 'POST', 'PUT', 'HEAD', 'DELETE', 'OPTIONS'];

Pass an optional key to use when checking for a method override, othewise defaults to __method_.

  • param: String key

  • return: Function

  • api: public

module.exports = function methodOverride(key){
    key = key || &quot;_method&quot;;
    return function methodOverride(req, res, next) {
        var method = req.method;

        // Check req.body (bodyDecoder)
        if (typeof req.body === 'object' &amp;&amp; key in req.body) {
            method = req.body[key];
            delete req.body[key];
        // Check X-HTTP-Method-Override
        } else if (req.headers['x-http-method-override']) {
            method = req.headers['x-http-method-override'];
        }

        // Ensure method is valid, and normalize
        method = method.toUpperCase();
        if (methods.indexOf(method) &gt;= 0) {
            req.method = method;
        }
        
        next();
    };
};

repl

lib/connect/middleware/repl.js

Module dependencies.

var net = require('net'),
    repl = require('repl');

Start a REPL on the given unix domain socket path.

Options

  • sockect Unix domain socket path. Defaults to "/tmp/connect.sock"
  • prompt REPL prompt string. Defaults to "node> "

Example

$ rlwrap telnet /tmp/connect.sock

  • param: String prompt

  • param: String socket path

  • return: Function

  • api: public

module.exports = function repl(prompt, socket){
    prompt = process.connectEnv.replPrompt || prompt || 'node> ';
    socket = process.connectEnv.replSocket || socket || '/tmp/connect.sock';
    net.createServer(function (stream) {
        repl.start(prompt, stream);
    }).listen(socket);
    return function repl(req, res, next){
        // Pass through for now
        next();
    }
};

responseTime

lib/connect/middleware/responseTime.js

Responds with the X-Response-Time header in milliseconds.

  • return: Function

  • api: public

module.exports = function responseTime(){
    return function responseTime(req, res, next){
        var start = new Date,
            writeHead = res.writeHead;

        res.writeHead = function(code, headers){
            res.writeHead = writeHead;
            headers['X-Response-Time'] = (new Date - start) + &quot;ms&quot;;
            res.writeHead(code, headers);
        };

        next();
    };
};

router

lib/connect/middleware/router.js

Module dependencies.

var parse = require('url').parse;

Provides Sinatra and Express like routing capabilities.

Examples

connect.router(function(app){
    app.get('/user/:id', function(req, res, params){
        // populates params.id
    });
})

  • param: Function fn

  • return: Function

  • api: public

module.exports = function router(fn){
    var routes;

    if (fn) {
        routes = {};
        fn.call(this, {
            post: method.call(this, 'post'),
            get: method.call(this, 'get'),
            put: method.call(this, 'put'),
            del: method.call(this, 'del')
        });
    } else {
        throw new Error('router provider requires a callback function');
    }

    function method(name) {
        var self = this,
            localRoutes = routes[name] = routes[name] || [];
        return function(path, fn){
            var keys = [];
            path = path instanceof RegExp
                ? path
                : normalizePath(path, keys);
            localRoutes.push(fn, path, keys);
            return self;
        };
    }

    return function router(req, res, next){
        var route,
            self = this;
        (function pass(i){
            if (route = match(req, routes, i)) {
                req.params = req.params || {};
                req.params.path = route._params;
                try { 
                    route.call(self, req, res, route._params, function(err){
                        if (err) {
                            next(err);
                        } else {
                            pass(++route._index);
                        }
                    });
                } catch (err) {
                    next(err);
                }
            } else {
                next();
            }
        })();
    };
}

memory

lib/connect/middleware/session/memory.js

Module dependencies.

var sys = require('sys'),
    Store = require('./store'),
    utils = require('./../../utils'),
    Session = require('./session');

Initialize MemoryStore with the given options.

  • param: Object options

  • api: public

var MemoryStore = module.exports = function MemoryStore(options) {
    options = options || {};
    Store.call(this, options);
    this.sessions = {};

    // Default reapInterval to 10 minutes
    this.reapInterval = options.reapInterval || 600000;

    // Reap stale sessions
    if (this.reapInterval !== -1) {
        setInterval(function(self){
            self.reap(self.maxAge);
        }, this.reapInterval, this);
    }
};

sys.inherits(MemoryStore, Store);

Attempt to fetch session by the given hash.

  • param: String hash

  • param: Function fn

  • api: public

MemoryStore.prototype.get = function(hash, fn){
    if (hash in this.sessions) {
        fn(null, this.sessions[hash]);
    } else {
        fn();
    }
};

Commit the given sess object associated with the given hash.

  • param: String hash

  • param: Session sess

  • param: Function fn

  • api: public

MemoryStore.prototype.set = function(hash, sess, fn){
    this.sessions[hash] = sess;
    fn &amp;&amp; fn();
};

Destroy the session associated with the given hash.

  • param: String hash

  • api: public

MemoryStore.prototype.destroy = function(hash, fn){
    delete this.sessions[hash];
    fn &amp;&amp; fn();
};

Invoke the given callback fn with all active sessions.

  • param: Function fn

  • api: public

MemoryStore.prototype.all = function(fn){
    var arr = [],
        keys = Object.keys(this.sessions);
    for (var i = 0, len = keys.length; i &lt; len; ++i) {
        arr.push(this.sessions[keys[i]]);
    }
    fn(null, arr);
};

Clear all sessions.

  • param: Function fn

  • api: public

MemoryStore.prototype.clear = function(fn){
    this.sessions = {};
    fn &amp;&amp; fn();
};

Fetch number of sessions.

  • param: Function fn

  • api: public

MemoryStore.prototype.length = function(fn){
    fn(null, Object.keys(this.sessions).length);
};

session

lib/connect/middleware/session/session.js

Module dependencies.

var utils = require('./../../utils');

Update lastAccess timestamp.

  • api: public

Session.prototype.touch = function(){
    this.lastAccess = +new Date;
};

Destroy this session.

  • param: Function fn

  • api: public

Session.prototype.destroy = function(fn){
    delete this.req.session;
    this.req.sessionStore.destroy(this.req.sessionHash, fn);
};

Regenerate this request's session.

  • param: Function fn

  • api: public

Session.prototype.regenerate = function(fn){
    this.req.sessionStore.regenerate(this.req, fn);
};

store

lib/connect/middleware/session/store.js

Module dependencies.

var Session = require('./session'),
    utils = require('./../../utils');

Destroy session associated with the given hash by passing null to Store#get().

  • param: String hash

  • param: Function fn

  • api: public

Store.prototype.destroy = function(hash, fn){
    this.set(hash, null, fn);
};

Re-generate the given requests's session.

  • param: IncomingRequest req

  • return: Function fn

  • api: public

Store.prototype.regenerate = function(req, fn){
    var self = this;
    this.destroy(req.sessionHash, function(err, destroyed){
        req.session = new Session(req, utils.uid());
        req.sessionHash = self.hash(req);
        fn(err, destroyed);
    });
};

session

lib/connect/middleware/session.js

Module dependencies.

var MemoryStore = require('./session/memory'),
    Session = require('./session/session'),
    utils = require('./../utils');

Setup session store with the given options.

Options

  • store Session store instance
  • fingerprint Custom fingerprint hashing function

  • param: Object options

  • return: Function

  • api: public

module.exports = function session(options){
    options = options || {};
    var key = 'connect.sid';

    // Default memory store
    store = options.store || new MemoryStore;

    // Default fingerprint hashing function
    var hash = store.hash = options.fingerprint || function(req){
        return utils.md5(req.session.id
            + req.socket.remoteAddress
            + (req.headers['user-agent'] || ''));
    };

    return function session(req, res, next){
        // Expose store
        req.sessionStore = store;

        if (req.cookies) {
            // Commit session
            var writeHead = res.writeHead;
            res.writeHead = function(status, headers){
                headers = headers || {};
                res.writeHead = writeHead;
                store.set(req.sessionHash, req.session);
                store.cookie.expires = new Date(+new Date() + store.maxAge);
                headers['Set-Cookie'] = utils.serializeCookie(key, req.session.id, store.cookie);
                return res.writeHead(status, headers);
            };

            // Generates the new session
            function generate() {
                req.session = new Session(req, utils.uid());
                req.sessionHash = hash(req);
                next();
            }
            
            // We have an sid
            if (req.cookies[key]) {
                req.session = new Session(req, req.cookies[key]);
                req.sessionHash = hash(req);
                // See if the hash is valid
                store.get(req.sessionHash, function(err, sess){
                    if (err) {
                        next(err);
                    } else if (sess) {
                        // Valid; apply session data and update lastAccess
                        req.session = new Session(req, sess);
                        req.session.touch();
                        next();
                    } else {
                        // Invalid; generate
                        generate();
                    }
                });
            } else {
                // No sid; generate
                generate();
            }
        } else {
            next();
        }
    }
};

staticProvider

lib/connect/middleware/staticProvider.js

Module dependencies.

var fs = require('fs'),
    Url = require('url'),
    Buffer = require('buffer').Buffer,
    Path = require('path'),
    queryString = require('querystring'),
    utils = require('./../utils');

Browser cache maxAge of one hour.

  • type: Number

var maxAge = 1000 * 60 * 60;

Static file server.

Options

  • root Root path from which to serve static files.

  • param: String root

  • return: Function

  • api: public

module.exports = function staticProvider(root){
    root = process.connectEnv.staticRoot || root || process.cwd();
    return function staticProvider(req, res, next) {
        if (req.method !== &quot;GET&quot;) {
            next();
            return;
        }
        var url = Url.parse(req.url);

        var pathname = url.pathname.replace(/\.\.+/g, '.'),
            filename = Path.join(root, queryString.unescape(pathname));

        if (filename[filename.length - 1] === &quot;/&quot;) {
            filename += &quot;index.html&quot;;
        }

        // Buffer any events that fire while waiting on the stat.
        var events = [];
        function onData() {
            events.push([&quot;data&quot;].concat(toArray(arguments)));
        }
        function onEnd() {
            events.push([&quot;end&quot;].concat(toArray(arguments)));
        }
        req.addListener(&quot;data&quot;, onData);
        req.addListener(&quot;end&quot;, onEnd);

        fs.stat(filename, function (err, stat) {

            // Stop buffering events
            req.removeListener(&quot;data&quot;, onData);
            req.removeListener(&quot;end&quot;, onEnd);

            // Fall through for missing files, thow error for other problems
            if (err) {
                if (err.errno === process.ENOENT) {
                    next();
                    // Refire the buffered events
                    for (var i = 0, len = events.length; i &lt; len; ++i) {
                        req.emit.apply(req, events[i]);
                    }
                    return;
                }
                next(err);
                return;
            }

            // Serve the file directly using buffers
            function onRead(err, data) {
                if (err) {
                    next(err);
                    return;
                }

                // For older versions of node convert the string to a buffer.
                if (typeof data === 'string') {
                    var b = new Buffer(data.length);
                    b.write(data, &quot;binary&quot;);
                    data = b;
                }

                // Zero length buffers act funny, use a string
                if (data.length === 0) {
                  data = &quot;&quot;;
                }

                res.writeHead(200, {
                    &quot;Content-Type&quot;: utils.mime.type(filename),
                    &quot;Content-Length&quot;: data.length,
                    &quot;Last-Modified&quot;: stat.mtime.toUTCString(),
                    // Cache in browser for 1 year
                    &quot;Cache-Control&quot;: &quot;public max-age=&quot; + 31536000
                });
                res.end(data);
            }

            // Node before 0.1.95 doesn't do buffers for fs.readFile
            if (process.version &lt; &quot;0.1.95&quot; &amp;&amp; process.version &gt; &quot;0.1.100&quot;) {
                // sys.debug("Warning: Old node version has slower static file loading");
                fs.readFile(filename, &quot;binary&quot;, onRead);
            } else {
                fs.readFile(filename, onRead);
            }
        });
    };

};

vhost

lib/connect/middleware/vhost.js

Setup vhost for the given hostname and server.

Examples

connect.createServer(
  connect.vhost('foo.com',
     connect.createServer(...middleware...)
 ),
  connect.vhost('bar.com',
      connect.createServer(...middleware...)
  )
);

  • param: String hostname

  • param: Server server

  • return: Function

  • api: public

module.exports = function vhost(hostname, server){
    if (!hostname) {
        throw new Error('vhost hostname required');
    }
    if (!server) {
        throw new Error('vhost server required');
    }
    return function vhost(req, res, next){
        var host = req.headers.host.split(':')[0];
        if (host === hostname) {
            server.handle(req, res);
        } else {
            next();
        }
    };
};