aboutsummaryrefslogtreecommitdiffstats
path: root/trunk/infrastructure/framework-src/modules/faststatic.js
diff options
context:
space:
mode:
Diffstat (limited to 'trunk/infrastructure/framework-src/modules/faststatic.js')
-rw-r--r--trunk/infrastructure/framework-src/modules/faststatic.js318
1 files changed, 318 insertions, 0 deletions
diff --git a/trunk/infrastructure/framework-src/modules/faststatic.js b/trunk/infrastructure/framework-src/modules/faststatic.js
new file mode 100644
index 0000000..5cca676
--- /dev/null
+++ b/trunk/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;
+}
+