Permalink
Browse files

Create CachingMiddleware for zipping and caching.

  • Loading branch information...
1 parent 43f4184 commit bb10f026ca5eb2907d1693b4b79e5a314d1f6b4d @cweider committed Jan 29, 2012
Showing with 190 additions and 89 deletions.
  1. +3 −1 node/server.js
  2. +12 −88 node/utils/Minify.js
  3. +175 −0 node/utils/caching_middleware.js
View
@@ -31,6 +31,7 @@ var async = require('async');
var express = require('express');
var path = require('path');
var minify = require('./utils/Minify');
+var CachingMiddleware = require('./utils/caching_middleware');
var formidable = require('formidable');
var apiHandler;
var exportHandler;
@@ -154,7 +155,8 @@ async.waterfall([
});
//serve minified files
- app.all('/minified/:filename', minify.minifyJS);
+ var assetCache = new CachingMiddleware;
+ app.all('/minified/:filename', assetCache.handle, minify.minifyJS);
//checks for padAccess
function hasPadAccess(req, res, callback)
View
@@ -27,16 +27,12 @@ var cleanCSS = require('clean-css');
var jsp = require("uglify-js").parser;
var pro = require("uglify-js").uglify;
var path = require('path');
-var Buffer = require('buffer').Buffer;
-var zlib = require('zlib');
var RequireKernel = require('require-kernel');
var server = require('../server');
-var os = require('os');
var ROOT_DIR = path.normalize(__dirname + "/../" );
var JS_DIR = ROOT_DIR + '../static/js/';
var CSS_DIR = ROOT_DIR + '../static/css/';
-var CACHE_DIR = ROOT_DIR + '../var/';
var TAR_PATH = path.join(__dirname, 'tar.json');
var tar = JSON.parse(fs.readFileSync(TAR_PATH, 'utf8'));
@@ -110,96 +106,24 @@ function _handle(req, res, jsFilename, jsFiles) {
function respondMinified()
{
var result = undefined;
- var latestModification = new Date(res.getHeader('last-modified'));
-
- async.series([
- function(callback)
- {
- //check the modification time of the minified js
- fs.stat(CACHE_DIR + "/minified_" + jsFilename, function(err, stats)
- {
- if(err && err.code != "ENOENT")
- {
- ERR(err, callback);
- return;
- }
-
- //there is no minfied file or there new changes since this file was generated, so continue generating this file
- if((err && err.code == "ENOENT") || stats.mtime.getTime() < latestModification)
- {
- callback();
- }
- //the minified file is still up to date, stop minifying
- else
- {
- callback("stop");
- }
- });
- },
- //load all js files
- function (callback)
- {
- var values = [];
- tarCode(
- jsFiles
- , function (content) {values.push(content)}
- , function (err) {
- if(ERR(err)) return;
-
- result = values.join('');
- callback();
- });
- },
- //put all together and write it into a file
- function(callback)
- {
- async.parallel([
- //write the results plain in a file
- function(callback)
- {
- fs.writeFile(CACHE_DIR + "minified_" + jsFilename, result, "utf8", callback);
- },
- //write the results compressed in a file
- function(callback)
- {
- zlib.gzip(result, function(err, compressedResult){
- //weird gzip bug that returns 0 instead of null if everything is ok
- err = err === 0 ? null : err;
-
- if(ERR(err, callback)) return;
-
- fs.writeFile(CACHE_DIR + "minified_" + jsFilename + ".gz", compressedResult, callback);
- });
- }
- ],callback);
- }
- ], function(err)
- {
- if(err && err != "stop")
- {
+ var values = [];
+ res.writeHead(200, {});
+ tarCode(
+ jsFiles
+ , function (content) {values.push(content)}
+ , function (err) {
if(ERR(err)) return;
+
+ result = values.join('');
+ res.write(result);
+ res.end();
}
-
- //check if gzip is supported by this browser
- var gzipSupport = req.header('Accept-Encoding', '').indexOf('gzip') != -1;
-
- var pathStr;
- if(gzipSupport && os.type().indexOf("Windows") == -1)
- {
- pathStr = path.normalize(CACHE_DIR + "minified_" + jsFilename + ".gz");
- res.header('Content-Encoding', 'gzip');
- }
- else
- {
- pathStr = path.normalize(CACHE_DIR + "minified_" + jsFilename );
- }
-
- res.sendfile(pathStr, { maxAge: server.maxAge });
- })
+ );
}
//minifying is disabled, so put the files together in one file
function respondRaw()
{
+ res.writeHead(200, {});
tarCode(
jsFiles
, function (content) {res.write(content)}
@@ -0,0 +1,175 @@
+/*
+ * 2011 Peter 'Pita' Martischka (Primary Technology Ltd)
+ *
+ * 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.
+ */
+
+var async = require('async');
+var Buffer = require('buffer').Buffer;
+var fs = require('fs');
+var path = require('path');
+var server = require('../server');
+var zlib = require('zlib');
+var util = require('util');
+
+var ROOT_DIR = path.normalize(__dirname + "/../");
+var CACHE_DIR = ROOT_DIR + '../var/';
+
+var responseCache = {};
+
+/*
+ This caches and compresses 200 and 404 responses to GET and HEAD requests.
+ TODO: Caching and compressing are solved problems, a middleware configuration
+ should replace this.
+*/
+
+function CachingMiddleware() {
+}
+CachingMiddleware.prototype = new function () {
+ function handle(req, res, next) {
+ if (!(req.method == "GET" || req.method == "HEAD")) {
+ return next(undefined, req, res);
+ }
+
+ var old_req = {};
+ var old_res = {};
+
+ var supportsGzip =
+ req.header('Accept-Encoding', '').indexOf('gzip') != -1;
+
+ var path = require('url').parse(req.url).path;
+ var cacheKey = (new Buffer(path)).toString('base64').replace(/[\/\+=]/g, '');
+
+ fs.stat(CACHE_DIR + 'minified_' + cacheKey, function (error, stats) {
+ var modifiedSince = (req.headers['if-modified-since']
+ && new Date(req.headers['if-modified-since']));
+ var lastModifiedCache = stats && stats.mtime;
+ if (lastModifiedCache) {
+ req.headers['if-modified-since'] = lastModifiedCache.toUTCString();
+ } else {
+ delete req.headers['if-modified-since'];
+ }
+
+ // Always issue get to downstream.
+ old_req.method = req.method;
+ req.method = 'GET';
+
+ var expirationDate = new Date(((responseCache[cacheKey] || {}).headers || {})['expires']);
+ if (expirationDate > new Date()) {
+ // Our cached version is still valid.
+ return respond();
+ }
+
+ var _headers = {};
+ old_res.setHeader = res.setHeader;
+ res.setHeader = function (key, value) {
+ _headers[key.toLowerCase()] = value;
+ old_res.setHeader.call(res, key, value);
+ };
+
+ old_res.writeHead = res.writeHead;
+ res.writeHead = function (status, headers) {
+ var lastModified = (res.getHeader('last-modified')
+ && new Date(res.getHeader('last-modified')));
+
+ res.writeHead = old_res.writeHead;
+ if (status == 200 || status == 404) {
+ // Update cache
+ var buffer = '';
+
+ Object.keys(headers).forEach(function (key) {
+ res.setHeader(key, headers[key]);
+ });
+ headers = _headers;
+ responseCache[cacheKey] = {statusCode: status, headers: headers};
+
+ old_res.write = res.write;
+ old_res.end = res.end;
+ res.write = function(data, encoding) {
+ buffer += data.toString(encoding);
+ };
+ res.end = function(data, encoding) {
+ async.parallel([
+ function (callback) {
+ var path = CACHE_DIR + 'minified_' + cacheKey;
+ fs.writeFile(path, buffer, function (error, stats) {
+ callback();
+ });
+ }
+ , function (callback) {
+ var path = CACHE_DIR + 'minified_' + cacheKey + '.gz';
+ zlib.gzip(buffer, function(error, content) {
+ if (error) {
+ callback();
+ } else {
+ fs.writeFile(path, content, function (error, stats) {
+ callback();
+ });
+ }
+ });
+ }
+ ], respond);
+ };
+ } else if (status == 304) {
+ // Nothing new changed from the cached version.
+ old_res.write = res.write;
+ old_res.end = res.end;
+ res.write = function(data, encoding) {};
+ res.end = function(data, encoding) { respond() };
+ } else {
+ res.writeHead(status, headers);
+ }
+ };
+
+ next(undefined, req, res);
+
+ // This handles read/write synchronization as well as its predecessor,
+ // which is to say, not at all.
+ // TODO: Implement locking on write or ditch caching of gzip and use
+ // existing middlewares.
+ function respond() {
+ req.method = old_req.method || req.method;
+ res.write = old_res.write || res.write;
+ res.end = old_res.end || res.end;
+
+ var headers = responseCache[cacheKey].headers;
+ var statusCode = responseCache[cacheKey].statusCode;
+
+ var pathStr = CACHE_DIR + 'minified_' + cacheKey;
+ if (supportsGzip) {
+ pathStr = pathStr + '.gz';
+ headers['content-encoding'] = 'gzip';
+ }
+
+ var lastModified = (headers['last-modified']
+ && new Date(headers['last-modified']));
+
+ if (statusCode == 200 && lastModified <= modifiedSince) {
+ res.writeHead(304, headers);
+ res.end();
+ } else if (req.method == 'GET') {
+ var readStream = fs.createReadStream(pathStr);
+ res.writeHead(statusCode, headers);
+ util.pump(readStream, res);
+ } else {
+ res.writeHead(200, headers);
+ res.end();
+ }
+ }
+ });
+ }
+
+ this.handle = handle;
+}();
+
+module.exports = CachingMiddleware;

0 comments on commit bb10f02

Please sign in to comment.