File: lib/server.js

Recommend this page to a friend!
  Classes of Gamaiel Zavala   cmbn   lib/server.js   Download  
File: lib/server.js
Role: Class source
Content type: text/plain
Description: Class source
Class: cmbn
Generate combos of JavaScript code served via CDNs
Author: By
Last change: Update of lib/server.js
Date: 2 years ago
Size: 6,625 bytes
 

Contents

Class file image Download
/* Copyright (c) 2012 Yahoo! Inc. All rights reserved. Copyrights licensed under the MIT License. See the accompanying LICENSE file for terms. */ var liburl = require('url'), md5 = require('MD5'), lru = require('lru-cache'), async = require('async'), request = require('request'), express = require('express'), cdns = require('./cdns'), gzippo; // fallback to connect for compress() if gzippo isn't available try { gzippo = require('gzippo'); } catch (e) { console.log('Notice: gzippo not found, using connect instead.'); gzippo = require('connect'); } var middleware = function (options) { var opts = options || {}, cache = lru(opts.cacheSize || 5000000, opts.cacheFunc || function (item){ return item.length; }); return function combine (req, res, next) { var assets = [], reqs = [], contentType = '', maxAge = null, regex = /\/(~.+)/, incoming = liburl.parse(req.url), matches = regex.exec(incoming.pathname), segments; if(matches) { segments = matches[1].split('/'); } else { return next(); } segments.forEach(function (segment) { var paths = segment.split(';'), cdnSem = paths.shift().substr(1).split('@'), cdnCode = cdnSem[0], ver = cdnSem[1] || null, cdn = cdns.getCdn(cdnCode), root; if(cdn) { root = (cdn.protocol || 'http') + '://' + cdn.host + '/' + (cdn.prefix || ''); paths.forEach(function (path) { var path = path.replace(/,/g, '/'), url = root + (cdns.isFunc(cdn.formatter) ? cdn.formatter(path, ver) : path), hash = md5(url), asset = cache.get(hash) || {url: url, hash: hash, content: null}; assets.push(asset); }); } else { respond({msg: 'Invalid CDN', code: 400}); } }); assets.forEach(function (asset) { if(asset.content === null) { reqs.push(function(cb) { request(asset.url, function (err, resp, body) { handleResp (err, resp, body, cb); }); }); } }); if(reqs.length) { async.parallel(reqs, respond); } else { respond(); } function handleResp (err, resp, body, cb) { if (err || resp.statusCode !== 200) { respond({msg: 'Error fetching ' + resp.request.href, code: resp.statusCode}); } else { var url = resp.request.href, type = resp.headers['content-type'], cc = resp.headers['cache-control'], maxAge = cc ? cc.match(/max\-age=([0-9]+)/) : null; // normalize js type if(type.match('javascript')) { type = 'application/javascript'; } // fix relative paths for css else if(type.match('css')) { resp.body = fixCssPaths(url, resp.body); } // find asset by url and set content assets.forEach(function (asset, index, assets) { if(asset.url === url) { asset.type = type; asset.age = maxAge ? maxAge[1] : null; asset.content = resp.body; cache.set(asset.hash, asset); } }); cb(); } } function respond (err, results) { if (err) { res.send(err.msg || "", err.code || 404); res.end(); } else { var combined = ''; assets.forEach(function (asset) { combined += "\n" + asset.content; // set content type or use default if diff types were returned if(asset.type !== contentType) { contentType = contentType ? 'text/plain' : asset.type; } // set max age to a minimum of all assets if(asset.age !== null) { maxAge = maxAge === null ? asset.age : Math.min(maxAge, asset.age); } }); res.setHeader('Content-Type', contentType); res.setHeader('Cache-Control', 'public,max-age=' + maxAge); res.write(combined); res.end(); } } }; } var createServer = function (options) { var opts = options || {}, app = express.createServer(); // enable compression if available if(gzippo.compress) { app.use(gzippo.compress()); } // add cmbn middleware app.use(middleware(opts)); // optional dir for local assets that can be comboed if(opts.staticDir) { app.use(express.static(opts.staticDir, {maxAge: opts.staticMaxAge * 1000 || 1e15})); } app.listen(opts.port || 80); } var fixCssPaths = function (assetUrl, assetContent) { var regex = /url\(\s*(?:['"])?([^'")]+)(?:['"])?\s*\)/g, matches; while(matches = regex.exec(assetContent)) { var match = matches[0], path = matches[1]; // skip canonical and data uris if('http' === path.substr(0, 4) || 'data:' === path.substr(0, 5)) { continue; } // replace path w/ canonical assetContent = assetContent.replace(match, 'url(' + liburl.resolve(assetUrl, path) + ')'); }; return assetContent; } module.exports = { middleware: middleware, createServer: createServer }