lib/http/index.js
'use strict';
var path = require('path'),
router = new (require('./router').Router),
app,
settings = { // User-configurable settings with sensible defaults
'strategy': 'default',
'static': {
maxAge: 2592000000 // (30 * 24 * 60 * 60 * 1000) cache static assets in the browser for 30 days
},
compress: true,
secure: false // allow setting of the 'secure' cookie attribute when using SSL
// - see https://github.com/socketstream/socketstream/issues/349
},
useAfterStack = []; // Allow Connect middleware to be added AFTER SocketStream middleware has been added to the stack
/**
* We do this in development mode ONLY to make it easier to identify which file has an error in Chrome, etc, by
* showing the real file name instead of 'code', without breaking the event-based routing system. Improvements welcome
* e.g. this function transforms
*
* "/serveDev/code/app.js?ts=12345"
* to
* "/serveDev/code?app.js&ts=12345"
*
* @param {String} url Url string
* @return {String} Transformed url
*/
function transformURL(url) {
var i, x;
i = 0;
for (x = 0; x <= 1; x++) {
i = url.indexOf('/', i + 1);
}
if (url[i] === '/') {
url = url.replace('?', '&');
url = url.substr(0, i) + '?' + url.substr(i + 1);
}
return url;
}
/**
* req.url transformation middleware
*
* @param {[type]} req Request object
* @param {[type]} res Responce object
* @param {Function} next Success callback function
* @return {Void}
*/
function eventMiddleware(req, res, next) {
var initialDir = req.url.split('/')[1];
/* Rewrite incoming URLs when serving dev assets live */
if (initialDir === '_serveDev') {
req.url = transformURL(req.url);
}
/* Serve a static asset if the URL starts with a static asset dir OR the router cannot find a matching route */
if (settings.strategy.isStatic(req.url) || !router.route(req.url, req, res)) {
next();
}
}
function ensureStrategy() {
if (typeof settings.strategy === 'string') {
settings.strategy = require('./' + settings.strategy + '.strategy');
}
}
/**
* @ngdoc service
* @name http.index:index
* @function
*
* @description
* HTTP Server
* -----------
* SocketStream does not concern itself with web servers. It simply provides a stack of Connect Middleware
* which can be used by the application in any way it wishes.
*/
module.exports = function(root) {
return {
/* Return API */
get middleware() {
if (!app) {
// Create new Connect app instance which can be accessed from your app.js file with ss.http.middleware
ensureStrategy();
app = settings.strategy.init();
/* Alias app.use to indicate this will be added to the stack BEFORE SocketStream middleware */
app.prepend = app.use;
app.append = function() {
var args = Array.prototype.slice.call(arguments);
return useAfterStack.push(args);
};
}
return app;
},
set middleware(mw) {
ensureStrategy();
app = mw;
},
router : router,
/**
* @ngdoc service
* @name http.index:index#set
* @methodOf http.index:index
* @function
*
* @description
* Merge optional settings
*
* @param {Object} newSettings Object with settins, @link settings
*/
set: function(newSettings) {
var s = '';
if (typeof newSettings !== 'object') {
throw new Error('ss.http.set() takes an object e.g. {static: {maxAge: 60000}}');
}
for (s in newSettings) {
if (newSettings.hasOwnProperty(s)) {
settings[s] = newSettings[s]
}
}
},
/**
* @ngdoc service
* @name http.index:index#load
* @methodOf http.index:index
* @function
*
* @description
* Attached Middleware, Session store, staticCache, etc, to the this.middleware (`var app = connect()`)
*
* @param {String} staticPath Static path for connect for serving static assets as `*.js`, `*.css`, etc.
* @param {Object} sessionStore Session store instance object
* @param {Object} sessionOptions Session store options
* @return {Object} Updated, with attached Middleware, this.middleware(`var app = connect()`) instance
*/
load: function(staticPath, assetsPath, sessionStore, sessionOptions) {
if (!app) {
// if ss.http.middleware isn't used before server start, no need to load it
return;
}
var secret = sessionOptions.secret;
staticPath = path.join(root, staticPath);
assetsPath = path.join(root, assetsPath);
ensureStrategy();
settings.strategy.load({
root: root,
static: staticPath,
assets: assetsPath
},settings);
/* connect.compress() should be added to middleware stack on highest possible position */
if (settings.strategy.compressMiddleware) {
app.use(settings.strategy.compressMiddleware);
}
/* Append SocketStream middleware upon server load */
if (settings.strategy.cookieParser) {
app.use(settings.strategy.cookieParser(secret));
}
if (settings.strategy.favicon) {
app.use(settings.strategy.favicon(staticPath + '/favicon.ico'))
}
if (settings.strategy.session) {
var cookie = {
path: '/',
// prevents third-party scripts such as Google Analytics
// and XSS attackers from stealing cookies and gaining access
// to paywalled or otherwise login-protected or private resources
// see https://blog.codinghorror.com/protecting-your-cookies-httponly/
// It was set to false pre-0.4.6 to support sessions over
// flash transport of socket.io
httpOnly: true,
maxAge: sessionOptions.maxAge,
secure: settings.secure
};
if (settings.strategy.sessionCookie) {
for(var k in settings.strategy.sessionCookie) {
cookie[k] = settings.strategy.sessionCookie[k];
}
}
app.use(settings.strategy.session({
cookie: cookie,
store: sessionStore,
secret: secret,
resave: false,
saveUninitialized: true
}));
}
/* Append any custom-defined middleware (e.g. everyauth) */
useAfterStack.forEach(function(m) {
return app.use.apply(app, m);
});
/* Finally ensure static asset serving is last */
app.use(eventMiddleware)
// serve assets and static out of separate paths as the next step
.use(settings.strategy.assetsMiddleware)
.use(settings.strategy.staticMiddleware);
/* Allow for the use of staticCache, if the option is passed */
if (settings.strategy.cacheMiddleware) {
app.use(settings.strategy.cacheMiddleware);
}
return app;
},
unload: function() {},
/**
* @ngdoc service
* @name http.index:index#route
* @methodOf http.index:index
* @function
*
* @description
* Expose short-form routing API
*
* @param {String} url Url string for routing
* @param {Function} fn Callback function for url
* @return {Object} res.serveClient(name) object
*/
route: function(url, fn) {
if (fn && typeof fn === 'function') {
return router.on(url, fn);
} else {
return {
serveClient: function(name) {
return router.on(url, function(req, res) {
return res.serveClient(name);
});
}
}
}
}
}
}