diff options
Diffstat (limited to 'infrastructure/framework-src/modules/faststatic.js')
-rw-r--r-- | infrastructure/framework-src/modules/faststatic.js | 318 |
1 files changed, 318 insertions, 0 deletions
diff --git a/infrastructure/framework-src/modules/faststatic.js b/infrastructure/framework-src/modules/faststatic.js new file mode 100644 index 0000000..5cca676 --- /dev/null +++ b/infrastructure/framework-src/modules/faststatic.js @@ -0,0 +1,318 @@ +/** + * Copyright 2009 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * @fileOverview serving static files, including js and css, and cacheing + * and minifying. + * + * Terminology Note: + * "path" is confusing because paths can be part of URLs and part + * of filesystem paths, and static files have both types of paths + * associated with them. Therefore, in this module: + * + * LOCALDIR or LOCALFILE refers to directories or files on the filesystem. + * + * HREF is used to describe things that go in a URL. + */ + +import("fileutils.{readFile,readFileBytes}"); +import("yuicompressor"); +import("stringutils"); +import("varz"); +import("ejs.EJS"); + +jimport("java.lang.System.out.println"); + +//---------------------------------------------------------------- +// Content Type Guessing +//---------------------------------------------------------------- + +var _contentTypes = { + 'gif': 'image/gif', + 'png': 'image/png', + 'jpg': 'image/jpeg', + 'jpeg': 'image/jpeg', + 'css': 'text/css', + 'js': 'application/x-javascript', + 'txt': 'text/plain', + 'html': 'text/html; charset=utf-8', + 'ico': 'image/x-icon', + 'swf': 'application/x-shockwave-flash', + 'zip': 'application/zip', + 'xml': 'application/xml' +}; + +var _gzipableTypes = { + 'text/css': true, + 'application/x-javascript': true, + 'text/html; charset=utf-8': true +}; + +function _guessContentType(path) { + var ext = path.split('.').pop().toLowerCase(); + return _contentTypes[ext] || 'text/plain'; +} + +//---------------------------------------------------------------- + +function _getCache(name) { + var m = 'faststatic'; + if (!appjet.cache[m]) { + appjet.cache[m] = {}; + } + var c = appjet.cache[m]; + if (!c[name]) { + c[name] = {}; + } + return c[name]; +} + +var _mtimeCheckInterval = 5000; // 5 seconds + +function _getMTime(f) { + var mcache = _getCache('mtimes'); + var now = +(new Date); + if (appjet.config.devMode || + !(mcache[f] && (now - mcache[f].lastCheck < _mtimeCheckInterval))) { + var jfile = new net.appjet.oui.JarVirtualFile(f); + if (jfile.exists() && !jfile.isDirectory()) { + mcache[f] = { + lastCheck: now, + mtime: jfile.lastModified() + }; + } else { + mcache[f] = null; + } + } + if (mcache[f]) { + return +mcache[f].mtime; + } else { + return null; + } +} + +function _wrapFile(localFile) { + return { + getPath: function() { return localFile; }, + getMTime: function() { return _getMTime(localFile); }, + getContents: function() { return _readFileAndProcess(localFile, 'string'); } + }; +} + +function _readFileAndProcess(fileName, type) { + if (fileName.slice(-8) == "_ejs.css") { + // run CSS through EJS + var template = readFile(fileName); + var ejs = new EJS({text:template, name:fileName}); + var resultString = ejs.render({}); + if (type == 'bytes') { + return new java.lang.String(resultString).getBytes("UTF-8"); + } + else { + return resultString; + } + } + else if (type == 'string') { + return readFile(fileName); + } + else if (type == 'bytes') { + return readFileBytes(fileName); + } +} + +function _cachedFileBytes(f) { + var mtime = _getMTime(f); + if (!mtime) { return null; } + var fcache = _getCache('file-bytes-cache'); + if (!(fcache[f] && (fcache[f].mtime == mtime))) { + varz.incrementInt("faststatic-file-bytes-cache-miss"); + var bytes = _readFileAndProcess(f, 'bytes'); + if (bytes) { + fcache[f] = {mtime: mtime, bytes: bytes}; + }; + } + if (fcache[f] && fcache[f].bytes) { + return fcache[f].bytes; + } else { + return null; + } +} + +function _shouldGzip(contentType) { + var userAgent = request.headers["User-Agent"]; + if (! userAgent) return false; + if (! (/Firefox/.test(userAgent) || /webkit/i.test(userAgent))) return false; + if (! _gzipableTypes[contentType]) return false; + + return request.acceptsGzip; +} + +function _getCachedGzip(original, key) { + var c = _getCache("gzipped"); + if (! c[key] || ! java.util.Arrays.equals(c[key].original, original)) { + c[key] = {original: original, + gzip: stringutils.gzip(original)}; + } + return c[key].gzip; +} + +function _setGzipHeader() { + response.setHeader("Content-Encoding", "gzip"); +} + +//---------------------------------------------------------------- + +/** + * Function for serving a single static file. + */ +function singleFileServer(localPath, opts) { + var contentType = _guessContentType(localPath); + + return function() { + (opts.cache ? response.alwaysCache() : response.neverCache()); + response.setContentType(contentType); + var bytes = _cachedFileBytes(localPath); + if (bytes) { + if (_shouldGzip(contentType)) { + bytes = _getCachedGzip(bytes, "file:"+localPath); + _setGzipHeader(); + } + response.writeBytes(bytes); + return true; + } else { + return false; + } + }; +} + +/** + * valid opts: + * alwaysCache: default false + */ +function directoryServer(localDir, opts) { + if (stringutils.endsWith(localDir, "/")) { + localDir = localDir.substr(0, localDir.length-1); + } + return function(relpath) { + if (stringutils.startsWith(relpath, "/")) { + relpath = relpath.substr(1); + } + if (relpath.indexOf('..') != -1) { + response.forbid(); + } + (opts.cache ? response.alwaysCache() : response.neverCache()); + var contentType = _guessContentType(relpath); + response.setContentType(contentType); + var fullPath = localDir + "/" + relpath; + var bytes = _cachedFileBytes(fullPath); + + if (bytes) { + if (_shouldGzip(contentType)) { + bytes = _getCachedGzip(bytes, "file:"+fullPath); + _setGzipHeader(); + } + response.writeBytes(bytes); + return true; + } else { + return false; + } + }; +} + +/** + * Serves cat files, which are concatenated versions of many files. + */ +function compressedFileServer(opts) { + var cfcache = _getCache('compressed-files'); + return function() { + var key = request.path.split('/').slice(-1)[0]; + var contentType = _guessContentType(request.path); + response.setContentType(contentType); + response.alwaysCache(); + var data = cfcache[key]; + if (data) { + if (_shouldGzip(contentType)) { + data = _getCachedGzip((new java.lang.String(data)).getBytes(response.getCharacterEncoding()), "comp:"+key); + _setGzipHeader(); + response.writeBytes(data); + } else { + response.write(data); + } + return true; + } else { + return false; + } + }; +} + +function getCompressedFilesKey(type, baseLocalDir, localFileList) { + if (stringutils.endsWith(baseLocalDir, '/')) { + baseLocalDir = baseLocalDir.substr(0, baseLocalDir.length-1); + } + + var fileList = []; + // convert passed-in file list into list of our file objects + localFileList.forEach(function(f) { + if (typeof(f) == 'string') { + fileList.push(_wrapFile(baseLocalDir+'/'+f)); + } else { + fileList.push(f); + } + }); + + // have we seen this exact fileset before? + var fsId = fileList.map(function(f) { return f.getPath(); }).join('|'); + var fsMTime = Math.max.apply(this, + fileList.map(function(f) { return f.getMTime(); })); + + var kdcache = _getCache('fileset-keydata-cache'); + if (!(kdcache[fsId] && (kdcache[fsId].mtime == fsMTime))) { + //println("cache miss for fileset: "+fsId); + //println("compressing fileset..."); + kdcache[fsId] = { + mtime: fsMTime, + keyString: _compressFilesAndMakeKey(type, fileList) + }; + } + return kdcache[fsId].keyString; +} + +function _compressFilesAndMakeKey(type, fileList) { + function _compress(s) { + if (type == 'css') { + varz.incrementInt("faststatic-yuicompressor-compressCSS"); + return yuicompressor.compressCSS(s); + } else if (type == 'js') { + varz.incrementInt("faststatic-yuicompressor-compressJS"); + return yuicompressor.compressJS(s); + } else { + throw Error('Dont know how to compress this filetype: '+type); + } + } + + var fullstr = ""; + fileList.forEach(function(f) { + fullstr += _compress(f.getContents()); + }); + + fullstr = _compress(fullstr); + + var key = stringutils.md5(fullstr) + '.' + type; + var cfcache = _getCache('compressed-files'); + cfcache[key] = fullstr; + return key; +} + |