Connect

High performance middleware for node.

bodyDecoder

lib/connect/middleware/bodyDecoder.js

Module dependencies.

var queryString = require('querystring');

Decode request bodies.

  • return: Function

  • api: public

exports = module.exports = function bodyDecoder(){
    return function bodyDecoder(req, res, next) {
        var decoder = exports.decode[mime(req)];
        if (decoder && !req.body) {
            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();
        }
    }
};

Supported decoders.

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

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["Content-Length"] = resp.body.length;
            if (!resp.body.length) resp.body = undefined;

            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 && chunk.length) {
                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'),
    url = require('url');

Require cache.

  • type: Object

var cache = {};

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
  • coffeescript Compiles coffee to js

  • param: Object options

  • api: public

exports = 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){
        if (req.method !== 'GET') return next();
        var pathname = url.parse(req.url).pathname;
        for (var i = 0, len = enable.length; i < len; ++i) {
            var name = enable[i],
                compiler = compilers[name];
            if (compiler.match.test(pathname)) {
                var src = (srcDir + pathname).replace(compiler.match, compiler.ext),
                    dest = destDir + pathname;

                // Compare mtimes
                fs.stat(src, function(err, srcStats){
                    if (err) {
                        if (err.errno === process.ENOENT) {
                            next();
                        } 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();
    };
};

Bundled compilers:

var compilers = exports.compilers = {
    sass: {
        match: /\.css$/,
        ext: '.sass',
        compile: function(str, fn){
            var sass = cache.sass || (cache.sass = require('sass'));
            try {
                fn(null, sass.render(str));
            } catch (err) {
                fn(err);
            }
        }
    },
    less: {
        match: /\.css$/,
        ext: '.less',
        compile: function(str, fn){
            var less = cache.less || (cache.less = require('less'));
            try {
                less.render(str, fn);
            } catch (err) {
                fn(err);
            }
        }
    },
    coffeescript: {
      match: /\.js$/,
      ext: '.coffee',
      compile: function(str, fn){
          var coffee = cache.coffee || (cache.coffee = require('coffee-script'));
          try {
              fn(null, coffee.compile(str));
          } catch (err) {
              fn(err);
          }
      }
    }
};

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 &&
                  ((since && lastModified === since) || (etag && 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 = {};
                Object.keys(headers).forEach(function (key) {
                    if (key.indexOf("Content") < 0) {
                        newHeaders[key] = headers[key];
                    }
                });
                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'),
    url = require('url'),
    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,
        formatUrl = options.formatUrl;

    // --showErrorStack
    if (process.connectEnv.showErrorStack !== undefined) {
        showStack = 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);
    }
    
    // --decorate stack trace file URLs
    var formatLine = function(v){ return '<li>' + v + '</li>'; }; // pass straight by default
    if (formatUrl) {
        var parts, re = /(\/[^\(\)]+):(\d+):(\d+)/;
        var formatters = {
            'file': function(parts) { return {'protocol':'file', 'hostname':''+parts[1]}; }, // file: URL (will probably be blocked by the browser)
            'txmt': function(parts) { return {      // TextMate formatter
                'protocol':'txmt', 'hostname':'//open',
                'query':{'url':'file://'+parts[1],'line':parts[2],'column':parts[3]}};
            }
        };
        formatLine = function(v) {
            parts = v.match(re);
            if (parts) v = v.replace(parts[0],'<a href="'+url.format( formatters[formatUrl](parts) )+'">'+parts[0]+'</a>');
            return '<li>' + v + '</li>';
        };
    }


    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(formatLine).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': 'text/plain' });
                res.end(err.stack);
            }
        } else {
            var body = showMessage
                ? err.toString()
                : 'Internal Server Error';
            res.writeHead(500, { 'Content-Type': 'text/plain' });
            res.end(body);
        }
    };
};

favicon

lib/connect/middleware/favicon.js

Module dependencies.

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

Favicon cache.

  • type: Object

var icon;

By default serves the connect favicon, or the favicon located by the given path.

Examples

connect.createServer(
    connect.favicon()    
);

connect.createServer(
    connect.favicon(__dirname + '/public/favicon.ico')    
);

  • param: String path

  • return: Function

  • api: public

module.exports = function favicon(path){
    path = path || __dirname + '/../public/favicon.ico';
    return function favicon(req, res, next){
        if (req.url === '/favicon.ico') {
            if (icon) {
                res.writeHead(200, icon.headers);
                res.end(icon.body);
            } else {
                fs.readFile(path, function(err, buf){
                    if (err) return next(err);
                    icon = {
                        headers: {
                            'Content-Type': 'image/x-icon',
                            'Content-Length': buf.length,
                            'ETag': utils.md5(buf),
                            'Cache-Control': 'public max-age=3600'
                        },
                        body: buf
                    }
                    res.writeHead(200, icon.headers);
                    res.end(icon.body);
                });
            }
        } else {
            next();
        }
    };
};

gzip-compress

lib/connect/middleware/gzip-compress.js

Module dependencies.

var 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; }

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 buffer.

  • type: Array

var buf = [];

Default log buffer duration.

  • type: Number

var defaultBufferDuration = 1000;

Log requests with the given options.

Options

  • format Format string, see below for tokens
  • stream Output stream, defaults to stdout
  • buffer Buffer duration, defaults to 1000ms when true

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,
        buffer = options.buffer;

    // Buffering support
    if (buffer) {
        var realStream = stream;
        setInterval(function(){
            if (buf.length) {
                realStream.write(buf.join(''), 'ascii');
                buf.length = 0;
            }
        }, typeof buffer === 'number' ? buffer : defaultBufferDuration); 
        stream = {
            write: function(str){
                buf.push(str);
            }
        };
    }

    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();
    }
};

router

lib/connect/middleware/router.js

Module dependencies.

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

Provides Sinatra and Express like routing capabilities.

Examples

connect.router(function(app){
    app.get('/user/:id', function(req, res, next){
        // populates req.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: fn,
                path: path,
                keys: keys
            });
            return self;
        };
    }

    return function router(req, res, next){
        var route,
            self = this;
        (function pass(i){
            if (route = match(req, routes, i)) {
                req.params = route._params;
                try { 
                    route.call(self, req, res, function(err){
                        if (err === true) {
                            next();
                        } else if (err) {
                            next(err);
                        } else {
                            pass(route._index+1);
                        }
                    });
                } 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 sid.

  • param: String sid

  • param: Function fn

  • api: public

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

Commit the given sess object associated with the given sid.

  • param: String sid

  • param: Session sess

  • param: Function fn

  • api: public

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

Destroy the session associated with the given sid.

  • param: String sid

  • api: public

MemoryStore.prototype.destroy = function(sid, fn){
    delete this.sessions[sid];
    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.sessionID, 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 sid by passing null to Store#get().

  • param: String sid

  • param: Function fn

  • api: public

Store.prototype.destroy = function(sid, fn){
    this.set(sid, 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.sessionID, function(err, destroyed){
        self.generate();
        fn(err, destroyed);
    });
};

session

lib/connect/middleware/session.js

Module dependencies.

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

Setup session store with the given options.

Options

  • store Session store instance
  • fingerprint Custom fingerprint generating function

  • param: Object options

  • return: Function

  • api: public

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

    // The cookie key to store the session id in.
    var key = options.key || 'connect.sid';

    // Default memory store
    var store = options.store || new (require('./session/memory'));

    // Used to verify session ids / defaults to user agent
    var fingerprint = options.fingerprint || function fingerprint(req) {
        return req.headers['user-agent'] || '';
    };

    // This should be set by the app to make the session key tamper proof
    var secret = options.secret || &quot;hackme&quot;;

    return function sessionHandle(req, res, next) {

        if (!req.cookies) {
            next(new Error(&quot;session requires cookieDecoder to work properly&quot;));
            return;
        }

        // Wrap writeHead as a hook to save the session and send the cookie
        var writeHead = res.writeHead;
        res.writeHead = function(status, headers){

            // Update the session in the store if there is one
            if (req.session) {
                req.session.touch();
                store.set(req.sessionID, req.session);
            }

            // Send an updated cookie to the browser
            store.cookie.expires = new Date(Date.now() + store.maxAge);

            // Multiple Set-Cookie headers
            headers = headers || {};
            var cookie = utils.serializeCookie(key, req.sessionID, store.cookie);
            if (headers['Set-Cookie']) {
                headers['Set-Cookie'] += '\r\nSet-Cookie: ' + cookie;
            } else {
                headers['Set-Cookie'] = cookie;
            }

            // Pass through the writeHead call
            res.writeHead = writeHead;
            return res.writeHead(status, headers);
        };

        // Calculates the security hash to prevent session hijacking
        // Uses information on the user-agent that created the session as it's fingerprint
        function hash(base) {
          return utils.md5(base + fingerprint(req) + secret, 'base64').replace(/=*$/, '');
        }

        // Generates the new session
        var generate = store.generate = function(){
            var base = utils.uid();
            var sessionID = base + &quot;.&quot; + hash(base);
            req.session = new Session(req, sessionID);
            req.sessionID = sessionID;
        };

        // Expose store
        req.sessionStore = store;

        // Get the sessionID from the cookie
        req.sessionID = req.cookies[key];

        // Make a new session if the browser doesn't send a sessionID
        if (!req.sessionID) {
            generate();
            next();
            return;
        }

        // Check the fingerprint
        var parts = req.sessionID.split('.');
        if (parts[1] !== hash(parts[0])) {
            // Make a new session if it doesn't check out
            generate();
            next();
            return;
        }

        // Generate the session object
        store.get(req.sessionID, function (err, sess) {
            // Error/missing handling
            if (err) {
                if (err.errno === process.ENOENT) {
                    // If the session ID is invalid, generate a new session
                    generate();
                    next();
                } else {
                    // Otherwise pass the exception along
                    next(err);
                }
                return;
            }
            if (!sess) {
              // Some stores use a falsy result to signify no result
              generate();
              next();
              return;
            }

            // Load the session into the request
            req.session = new Session(req, sess);
            next();
        });
    };
};

Expose constructors.

exports.Session = Session;
exports.Store = require('./session/store');
exports.MemoryStore = require('./session/memory');

staticGzip

lib/connect/middleware/staticGzip.js

Module dependencies.

var fs = require('fs'),
    parse = require('url').parse
    utils = require('../utils'),
    path = require('path'),
    exec = require('child_process').exec;

Expose staticGzip as the module.

exports = module.exports = staticGzip;

Gzip binary.

  • type: String

exports.bin = 'gzip';

Flags passed to gzip.

  • type: String

exports.flags = '--best';

staticGzip gzips statics via whitelist of mime types specified by the compress option. Once created staticProvider can continue on to serve the gzipped version of the file.

Options

  • root Root direction from which to generate gzipped statics
  • compress Array of mime types serving as a whitelist
  • flags String of flags passed to the binary
  • bin Binary executable defaulting to "gzip"

  • param: Object options

  • api: public

function staticGzip(options){
    var options = options || {},
        root = options.root,
        compress = options.compress,
        flags = options.flags || exports.flags,
        bin = options.bin || exports.bin;

    if (!root) throw new Error('staticGzip root must be set');
    if (!compress) throw new Error('staticGzip compress array must be passed');

    return function(req, res, next){
        if (req.method !== 'GET') return next();

        var acceptEncoding = req.headers['accept-encoding'] || '';

        // Ignore when Accept-Encoding does not allow gzip
        if (acceptEncoding &amp;&amp; !~acceptEncoding.indexOf('gzip')) return next();

        // Parse the url
        var url = parse(req.url),
            filename = path.join(root, url.pathname),
            mime = utils.mime.type(filename).split(';')[0];

        // MIME type not white-listed
        if (!~compress.indexOf(mime)) return next();

        // Check if gzipped static is available
        gzipped(filename, function(err, path, ext){
            if (err &amp;&amp; err.errno === process.ENOENT) {
                next();
                // We were looking for a gzipped static,
                // so lets gzip it!
                if (err.path.indexOf('.gz') === err.path.length - 3) {
                    gzip(filename, path, flags, bin);
                }
            } else if (err) {
                next(err);
            } else {
                // Re-write the url to serve the gzipped static
                req.url = url.pathname + ext;
                var writeHead = res.writeHead;
                res.writeHead = function(status, headers){
                    headers = headers || {};
                    res.writeHead = writeHead;
                    headers['Content-Type'] = mime;
                    headers['Content-Encoding'] = 'gzip';
                    res.writeHead(status, headers);
                };
                next();
            }
        });
    }
};

staticProvider

lib/connect/middleware/staticProvider.js

Module dependencies.

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

Default browser cache maxAge of one year.

const MAX_AGE = 31557600000;

File buffer cache.

var _cache = {};

Static file server.

Options

  • root Root path from which to serve static files.
  • maxAge Browser cache maxAge in milliseconds
  • cache When true cache files in memory indefinitely, until invalidated by a conditional GET request. When given, maxAge will be derived from this value.

  • param: Object options

  • return: Function

  • api: public

module.exports = function staticProvider(options){
    var cache, maxAge, root;

    // Support options object and root string
    if (typeof options == 'string') {
        root = options;
        maxAge = MAX_AGE;
    } else {
        options = options || {};
        maxAge = options.maxAge;
        root = process.connectEnv.staticRoot || options.root || process.cwd();
        cache = options.cache;
        if (cache &amp;&amp; !maxAge) maxAge = cache;
        maxAge = maxAge || MAX_AGE;
    }

    return function staticProvider(req, res, next) {
        if (req.method != 'GET' &amp;&amp; req.method != 'HEAD') return next();

        var hit, 
            head = req.method == 'HEAD',
            filename, url = parseUrl(req.url);

        // Potentially malicious path
        if (~url.pathname.indexOf('..')) {
            return forbidden(res);
        }

        // Absolute path
        filename = Path.join(root, queryString.unescape(url.pathname));

        // Index.html support
        if (filename[filename.length - 1] === '/') {
            filename += &quot;index.html&quot;;
        }
        
        // Cache hit
        if (cache &amp;&amp; !conditionalGET(req) &amp;&amp; (hit = _cache[req.url])) {
            res.writeHead(200, hit.headers);
            res.end(head ? undefined : hit.body);
            return;
        }

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

            // Pass through for missing files, thow error for other problems
            if (err) {
                return err.errno === process.ENOENT
                    ? next()
                    : next(err);
            } else if (stat.isDirectory()) {
                return next();
            }

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

                // Response headers
                var headers = {
                    &quot;Content-Type&quot;: utils.mime.type(filename),
                    &quot;Content-Length&quot;: stat.size,
                    &quot;Last-Modified&quot;: stat.mtime.toUTCString(),
                    &quot;Cache-Control&quot;: &quot;public max-age=&quot; + (maxAge / 1000),
                    &quot;ETag&quot;: etag(stat)
                };

                // Conditional GET
                if (!modified(req, headers)) {
                    return notModified(res, headers);
                }
                
                res.writeHead(200, headers);
                res.end(head ? undefined : data);

                // Cache support
                if (cache) {
                    _cache[req.url] = {
                        headers: headers,
                        body: data
                    };
                }
            }

            fs.readFile(filename, onRead);
        });
    };
};

Clear the memory cache for key or the entire store.

  • param: String key

  • api: public

exports.clearCache = function(key){
    if (key) {
        delete _cache[key];
    } else {
        _cache = {};
    }
};

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){
        if (!req.headers.host) next();
        var host = req.headers.host.split(':')[0];
        if (host === hostname) {
            server.handle(req, res, next);
        } else {
            next();
        }
    };
};