From 5ad578c0f85faa95195441c68eb61cf6588f9772 Mon Sep 17 00:00:00 2001 From: Dave Beck Date: Thu, 20 Oct 2016 08:04:49 -0500 Subject: [PATCH 1/5] Adds the ability to cache data and return cached data when present. --- index.js | 34 ++++++++++++++++++++++++++++------ test/readCache.js | 42 ++++++++++++++++++++++++++++++++++++++++++ test/writeCache.js | 30 ++++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+), 6 deletions(-) create mode 100644 test/readCache.js create mode 100644 test/writeCache.js diff --git a/index.js b/index.js index 00e6997d..a1cd0b26 100644 --- a/index.js +++ b/index.js @@ -18,6 +18,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,6 +30,14 @@ module.exports = function proxy(host, options) { return function handleProxy(req, res, next) { if (!filter(req, res)) { return next(); } + var readPromise = readCache(req); + if (readPromise) { + readPromise.then(function(cachedRes) { + copyResponse(res, cachedRes); + res.send(cachedRes.data); + }); + return; + } forwardPathAsync(req, res) .then(function(path) { @@ -35,6 +45,16 @@ module.exports = function proxy(host, options) { }); }; + // 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 +115,9 @@ module.exports = function proxy(host, options) { rsp.on('end', function() { var rspData = Buffer.concat(chunks, chunkLength(chunks)); + if (writeCache) { + writeCache(req, {statusCode: res.statusCode, headers: res.headers, data: rspData}); + } if (intercept) { rspData = maybeUnzipResponse(rspData, res); @@ -136,12 +159,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 +266,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..02522373 --- /dev/null +++ b/test/readCache.js @@ -0,0 +1,42 @@ +var assert = require('assert'); +var express = require('express'); +var request = require('supertest'); +var proxy = require('../'); + +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',{ + cacheRead: function (req) { + return { + headers: { + 'X-President': 'Obama' + }, + status: '200', + data: { + isCached: 'yes' + } + } + } + })); + request(app) + .get('/get') + .end(function(err, res) { + if (err) { return done(err); } + console.log ('res = ' + JSON.stringify(res)); + assert(res.headers['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..ae91e533 --- /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); + }); + }); +}); From 24dab782e7beef0cc1d47ca457b2e980706b13ea Mon Sep 17 00:00:00 2001 From: Dave Beck Date: Thu, 20 Oct 2016 10:20:39 -0500 Subject: [PATCH 2/5] Fixes minor issues that caused readCache unit test to fail. --- index.js | 20 +++++++++++--------- test/readCache.js | 15 ++++++++------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/index.js b/index.js index a1cd0b26..2117b2c2 100644 --- a/index.js +++ b/index.js @@ -30,19 +30,21 @@ module.exports = function proxy(host, options) { return function handleProxy(req, res, next) { if (!filter(req, res)) { return next(); } - var readPromise = readCache(req); + var readPromise = readCache ? readCache(req) : null; if (readPromise) { readPromise.then(function(cachedRes) { - copyResponse(res, cachedRes); - res.send(cachedRes.data); + copyResponse(cachedRes, res); + if (!res.headersSent) { + res.send(cachedRes.data); + } }); - return; } - - forwardPathAsync(req, res) - .then(function(path) { - proxyWithResolvedPath(req, res, next, path); + else { + forwardPathAsync(req, res) + .then(function(path) { + proxyWithResolvedPath(req, res, next, path); }); + } }; // Copies status and headers from source response to the target response @@ -116,7 +118,7 @@ module.exports = function proxy(host, options) { var rspData = Buffer.concat(chunks, chunkLength(chunks)); if (writeCache) { - writeCache(req, {statusCode: res.statusCode, headers: res.headers, data: rspData}); + writeCache(req, {statusCode: rsp.statusCode, headers: rsp.headers, data: rspData}); } if (intercept) { diff --git a/test/readCache.js b/test/readCache.js index 02522373..2f997ecb 100644 --- a/test/readCache.js +++ b/test/readCache.js @@ -17,24 +17,25 @@ describe('readCache', function() { it('should provide a cached response as though it came from the host', function(done) { app = express(); app.use(proxy('httpbin.org',{ - cacheRead: function (req) { - return { + readCache: function (req) { + return new Promise (function (resolve, reject) { + resolve ({ headers: { - 'X-President': 'Obama' + 'x-president': 'Obama' }, - status: '200', + statusCode: 200, data: { isCached: 'yes' } - } + }); + }); } })); request(app) .get('/get') .end(function(err, res) { if (err) { return done(err); } - console.log ('res = ' + JSON.stringify(res)); - assert(res.headers['X-President'],'Obama'); + assert(res.header['x-president'],'Obama'); assert.equal(res.body.isCached, 'yes'); done(err); }); From 296c1a54420124fce50ea89d9e1f8fb40a09810e Mon Sep 17 00:00:00 2001 From: Dave Beck Date: Thu, 27 Oct 2016 08:11:48 -0500 Subject: [PATCH 3/5] Fixes formatting errors. --- index.js | 6 ++---- test/readCache.js | 18 ++++++++++-------- test/writeCache.js | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/index.js b/index.js index 3a6bdd4f..6d0b2b4e 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'); @@ -38,11 +37,10 @@ module.exports = function proxy(host, options) { res.send(cachedRes.data); } }); - } - else { + } else { forwardPathAsync(req, res) .then(function(path) { - proxyWithResolvedPath(req, res, next, path); + proxyWithResolvedPath(req, res, next, path); }); } }; diff --git a/test/readCache.js b/test/readCache.js index 2f997ecb..5be5a7eb 100644 --- a/test/readCache.js +++ b/test/readCache.js @@ -2,6 +2,8 @@ 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'; @@ -17,21 +19,21 @@ describe('readCache', function() { it('should provide a cached response as though it came from the host', function(done) { app = express(); app.use(proxy('httpbin.org',{ - readCache: function (req) { - return new Promise (function (resolve, reject) { - resolve ({ + readCache: function() { + return new promise.Promise(function(resolve) { + resolve({ headers: { 'x-president': 'Obama' - }, + }, statusCode: 200, data: { isCached: 'yes' - } + } }); }); } - })); - request(app) + })); + request(app) .get('/get') .end(function(err, res) { if (err) { return done(err); } @@ -39,5 +41,5 @@ describe('readCache', function() { assert.equal(res.body.isCached, 'yes'); done(err); }); - }); + }); }); diff --git a/test/writeCache.js b/test/writeCache.js index ae91e533..3814a75d 100644 --- a/test/writeCache.js +++ b/test/writeCache.js @@ -14,7 +14,7 @@ describe('writeCache', function() { 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.headers['Content-Type'], 'application/json'); assert.equal (cacheData.status,'200'); } })); From 02b7ce4679d4f17526dc99130a85b128cc7baeef Mon Sep 17 00:00:00 2001 From: Dave Beck Date: Fri, 28 Oct 2016 07:04:15 -0500 Subject: [PATCH 4/5] Fixes additional formatting issues. --- test/writeCache.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/writeCache.js b/test/writeCache.js index 3814a75d..11fbcbb7 100644 --- a/test/writeCache.js +++ b/test/writeCache.js @@ -12,13 +12,13 @@ describe('writeCache', function() { it('should receive the request and cache data', function(done) { app = express(); app.use(proxy('httpbin.org',{ - cacheWrite: function (req,cacheData) { + 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) + })); + request(app) .get('/get') .end(function(err, res) { if (err) { return done(err); } @@ -26,5 +26,5 @@ describe('writeCache', function() { assert.equal(res.body.url, 'http://httpbin.org/get'); done(err); }); - }); + }); }); From 638a114d62032e1595773b501edfd43a438f6f8a Mon Sep 17 00:00:00 2001 From: Dave Beck Date: Fri, 28 Oct 2016 07:25:55 -0500 Subject: [PATCH 5/5] Adds a clarifying comment. --- index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/index.js b/index.js index 6d0b2b4e..bd902987 100644 --- a/index.js +++ b/index.js @@ -30,6 +30,7 @@ module.exports = function proxy(host, options) { return function handleProxy(req, res, next) { if (!filter(req, res)) { return next(); } var readPromise = readCache ? readCache(req) : null; + // If a cached value exists, use it if (readPromise) { readPromise.then(function(cachedRes) { copyResponse(cachedRes, res);