diff --git a/index.js b/index.js index 1e65781c..bd902987 100644 --- a/index.js +++ b/index.js @@ -1,5 +1,4 @@ 'use strict'; - var assert = require('assert'); var url = require('url'); var http = require('http'); @@ -18,6 +17,8 @@ module.exports = function proxy(host, options) { /** * Function :: intercept(targetResponse, data, res, req, function(err, json, sent)); */ + var readCache = options.readCache || defaultReadCache; + var writeCache = options.writeCache; var intercept = options.intercept; var decorateRequest = options.decorateRequest; var forwardPath = options.forwardPath || defaultForwardPath; @@ -28,13 +29,33 @@ module.exports = function proxy(host, options) { return function handleProxy(req, res, next) { if (!filter(req, res)) { return next(); } - - forwardPathAsync(req, res) - .then(function(path) { + var readPromise = readCache ? readCache(req) : null; + // If a cached value exists, use it + if (readPromise) { + readPromise.then(function(cachedRes) { + copyResponse(cachedRes, res); + if (!res.headersSent) { + res.send(cachedRes.data); + } + }); + } else { + forwardPathAsync(req, res) + .then(function(path) { proxyWithResolvedPath(req, res, next, path); }); + } }; + // Copies status and headers from source response to the target response + function copyResponse(source, target) { + target.status(source.statusCode); + Object.keys(source.headers) + .filter(function(item) { return item !== 'transfer-encoding'; }) + .forEach(function(item) { + target.set(item, source.headers[item]); + }); + } + function proxyWithResolvedPath(req, res, next, path) { parsedHost = parsedHost || parseHost(host, req, options); @@ -95,6 +116,9 @@ module.exports = function proxy(host, options) { rsp.on('end', function() { var rspData = Buffer.concat(chunks, chunkLength(chunks)); + if (writeCache) { + writeCache(req, {statusCode: rsp.statusCode, headers: rsp.headers, data: rspData}); + } if (intercept) { rspData = maybeUnzipResponse(rspData, res); @@ -136,12 +160,7 @@ module.exports = function proxy(host, options) { }); if (!res.headersSent) { - res.status(rsp.statusCode); - Object.keys(rsp.headers) - .filter(function(item) { return item !== 'transfer-encoding'; }) - .forEach(function(item) { - res.set(item, rsp.headers[item]); - }); + copyResponse(rsp, res); } }); @@ -248,6 +267,10 @@ function defaultForwardPath(req) { return url.parse(req.url).path; } +function defaultReadCache() { + return null; // Default is always a cache-miss. +} + function bodyEncoding(options) { diff --git a/test/readCache.js b/test/readCache.js new file mode 100644 index 00000000..5be5a7eb --- /dev/null +++ b/test/readCache.js @@ -0,0 +1,45 @@ +var assert = require('assert'); +var express = require('express'); +var request = require('supertest'); +var proxy = require('../'); +var promise = require('es6-promise'); + + +describe('readCache', function() { + 'use strict'; + this.timeout(10000); + + var app; + + beforeEach(function() { + app = express(); + app.use(proxy('httpbin.org')); + }); + + it('should provide a cached response as though it came from the host', function(done) { + app = express(); + app.use(proxy('httpbin.org',{ + readCache: function() { + return new promise.Promise(function(resolve) { + resolve({ + headers: { + 'x-president': 'Obama' + }, + statusCode: 200, + data: { + isCached: 'yes' + } + }); + }); + } + })); + request(app) + .get('/get') + .end(function(err, res) { + if (err) { return done(err); } + assert(res.header['x-president'],'Obama'); + assert.equal(res.body.isCached, 'yes'); + done(err); + }); + }); +}); diff --git a/test/writeCache.js b/test/writeCache.js new file mode 100644 index 00000000..11fbcbb7 --- /dev/null +++ b/test/writeCache.js @@ -0,0 +1,30 @@ +var assert = require('assert'); +var express = require('express'); +var request = require('supertest'); +var proxy = require('../'); + +describe('writeCache', function() { + 'use strict'; + this.timeout(10000); + + var app; + + it('should receive the request and cache data', function(done) { + app = express(); + app.use(proxy('httpbin.org',{ + cacheWrite: function(req,cacheData) { + assert.equal (cacheData.data.url,'http://httpbin.org/get'); + assert.equal (cacheData.headers['Content-Type'], 'application/json'); + assert.equal (cacheData.status,'200'); + } + })); + request(app) + .get('/get') + .end(function(err, res) { + if (err) { return done(err); } + assert(/node-superagent/.test(res.body.headers['User-Agent'])); + assert.equal(res.body.url, 'http://httpbin.org/get'); + done(err); + }); + }); +});