summaryrefslogtreecommitdiff
path: root/src/node/utils/caching_middleware.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/node/utils/caching_middleware.js')
-rw-r--r--src/node/utils/caching_middleware.js177
1 files changed, 177 insertions, 0 deletions
diff --git a/src/node/utils/caching_middleware.js b/src/node/utils/caching_middleware.js
new file mode 100644
index 00000000..70d5a08c
--- /dev/null
+++ b/src/node/utils/caching_middleware.js
@@ -0,0 +1,177 @@
+/*
+ * 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 zlib = require('zlib');
+var util = require('util');
+var settings = require('./Settings');
+
+var CACHE_DIR = path.normalize(path.join(settings.root, 'var/'));
+CACHE_DIR = path.existsSync(CACHE_DIR) ? CACHE_DIR : undefined;
+
+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") || !CACHE_DIR) {
+ 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 = !error && stats.mtime;
+ if (lastModifiedCache && responseCache[cacheKey]) {
+ 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) {
+ // Update cache
+ var buffer = '';
+
+ Object.keys(headers || {}).forEach(function (key) {
+ res.setHeader(key, headers[key]);
+ });
+ 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();
+ });
+ }
+ });
+ }
+ ], function () {
+ responseCache[cacheKey] = {statusCode: status, headers: headers};
+ 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 && (headers['content-type'] || '').match(/^text\//)) {
+ 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(statusCode, headers);
+ res.end();
+ }
+ }
+ });
+ }
+
+ this.handle = handle;
+}();
+
+module.exports = CachingMiddleware;