# 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 &amp;&amp; !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 !== &quot;GET&quot;) {
                next();
                return;
            }

            var key = req.headers[&quot;accept-encoding&quot;] + 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[&quot;Content-Length&quot;] = 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 &lt; 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 &amp;&amp; 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 = &quot;&quot;;

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

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

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

                    res.writeHead(200, {
                        &quot;Content-Type&quot;: &quot;text/cache-manifest&quot;,
                        &quot;Last-Modified&quot;: latestMtime.toUTCString(),
                        &quot;Content-Length&quot;: 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 &quot;enable&quot; 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 &lt; 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 &gt; 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:

- sass to css
- less to css
- coffee to js

    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 === &quot;GET&quot; &amp;&amp;
                 (req.headers[&quot;if-modified-since&quot;] || req.headers[&quot;if-none-match&quot;])
               )) {
                next();
                return;
            }

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

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

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

                // If there is no match, then move on.
                if (!(code === 200 &amp;&amp;
                      ((since &amp;&amp; lastModified === since) || (etag &amp;&amp; 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(&quot;Content&quot;) &lt; 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();
            }
        };
    };