diff --git a/lib/blocks.js b/lib/blocks.js index a15b2e7..11806bd 100644 --- a/lib/blocks.js +++ b/lib/blocks.js @@ -3,7 +3,6 @@ var common = require('./common'); var async = require('async'); var bitcore = require('bitcore-lib'); -var BufferUtil = bitcore.util.buffer; var pools = require('../pools.json'); var BN = bitcore.crypto.BN; diff --git a/lib/index.js b/lib/index.js index ed0df24..66c88dd 100644 --- a/lib/index.js +++ b/lib/index.js @@ -15,6 +15,15 @@ var $ = bitcore.util.preconditions; var Transaction = bitcore.Transaction; var EventEmitter = require('events').EventEmitter; +/** + * A service for Bitcore to enable HTTP routes to query information about the blockchain. + * + * @param {Object} options + * @param {Boolean} options.enableCache - This will enable cache-control headers + * @param {Number} options.cacheShortSeconds - The time to cache short lived cache responses. + * @param {Number} options.cacheLongSeconds - The time to cache long lived cache responses. + * @param {String} options.routePrefix - The URL route prefix + */ var InsightAPI = function(options) { BaseService.call(this, options); @@ -25,6 +34,13 @@ var InsightAPI = function(options) { inv: [] }; + if (!_.isUndefined(options.enableCache)) { + $.checkArgument(_.isBoolean(options.enableCache)); + this.enableCache = options.enableCache; + } + this.cacheShortSeconds = options.cacheShortSeconds; + this.cacheLongSeconds = options.cacheLongSeconds; + if (!_.isUndefined(options.routePrefix)) { this.routePrefix = options.routePrefix; } else { @@ -38,6 +54,26 @@ InsightAPI.dependencies = ['address', 'web']; inherits(InsightAPI, BaseService); +InsightAPI.prototype.cache = function(maxAge) { + var self = this; + return function(req, res, next) { + if (self.enableCache) { + res.header('Cache-Control', 'public, max-age=' + maxAge); + } + next(); + }; +}; + +InsightAPI.prototype.cacheShort = function() { + var seconds = this.cacheShortSeconds || 30; // thirty seconds + return this.cache(seconds); +}; + +InsightAPI.prototype.cacheLong = function() { + var seconds = this.cacheLongSeconds || 86400; // one day + return this.cache(seconds); +}; + InsightAPI.prototype.getRoutePrefix = function() { return this.routePrefix; }; @@ -49,6 +85,7 @@ InsightAPI.prototype.start = function(callback) { }; InsightAPI.prototype.setupRoutes = function(app) { + //Enable CORS app.use(function(req, res, next) { res.header('Access-Control-Allow-Origin', '*'); @@ -58,48 +95,47 @@ InsightAPI.prototype.setupRoutes = function(app) { //Block routes var blocks = new BlockController(this.node); - app.get('/blocks', blocks.list.bind(blocks)); + app.get('/blocks', this.cacheShort(), blocks.list.bind(blocks)); - app.get('/block/:blockHash', blocks.show.bind(blocks)); + app.get('/block/:blockHash', this.cacheLong(), blocks.show.bind(blocks)); app.param('blockHash', blocks.block.bind(blocks)); - app.get('/block-index/:height', blocks.blockIndex.bind(blocks)); + app.get('/block-index/:height', this.cacheLong(), blocks.blockIndex.bind(blocks)); app.param('height', blocks.blockIndex.bind(blocks)); - // Transaction routes var transactions = new TxController(this.node); - app.get('/tx/:txid', transactions.show.bind(transactions)); + app.get('/tx/:txid', this.cacheLong(), transactions.show.bind(transactions)); app.param('txid', transactions.transaction.bind(transactions)); - app.get('/txs', transactions.list.bind(transactions)); + app.get('/txs', this.cacheShort(), transactions.list.bind(transactions)); app.post('/tx/send', transactions.send.bind(transactions)); // Raw Routes - app.get('/rawtx/:txid', transactions.showRaw.bind(transactions)); + app.get('/rawtx/:txid', this.cacheLong(), transactions.showRaw.bind(transactions)); app.param('txid', transactions.rawTransaction.bind(transactions)); // Address routes var addresses = new AddressController(this.node); - app.get('/addr/:addr', addresses.checkAddr.bind(addresses), addresses.show.bind(addresses)); - app.get('/addr/:addr/utxo', addresses.checkAddr.bind(addresses), addresses.utxo.bind(addresses)); - app.get('/addrs/:addrs/utxo', addresses.checkAddrs.bind(addresses), addresses.multiutxo.bind(addresses)); - app.post('/addrs/utxo', addresses.checkAddrs.bind(addresses), addresses.multiutxo.bind(addresses)); - app.get('/addrs/:addrs/txs', addresses.checkAddrs.bind(addresses), addresses.multitxs.bind(addresses)); - app.post('/addrs/txs', addresses.checkAddrs.bind(addresses), addresses.multitxs.bind(addresses)); + app.get('/addr/:addr', this.cacheShort(), addresses.checkAddr.bind(addresses), addresses.show.bind(addresses)); + app.get('/addr/:addr/utxo', this.cacheShort(), addresses.checkAddr.bind(addresses), addresses.utxo.bind(addresses)); + app.get('/addrs/:addrs/utxo', this.cacheShort(), addresses.checkAddrs.bind(addresses), addresses.multiutxo.bind(addresses)); + app.post('/addrs/utxo', this.cacheShort(), addresses.checkAddrs.bind(addresses), addresses.multiutxo.bind(addresses)); + app.get('/addrs/:addrs/txs', this.cacheShort(), addresses.checkAddrs.bind(addresses), addresses.multitxs.bind(addresses)); + app.post('/addrs/txs', this.cacheShort(), addresses.checkAddrs.bind(addresses), addresses.multitxs.bind(addresses)); // Address property routes - app.get('/addr/:addr/balance', addresses.checkAddr.bind(addresses), addresses.balance.bind(addresses)); - app.get('/addr/:addr/totalReceived', addresses.checkAddr.bind(addresses), addresses.totalReceived.bind(addresses)); - app.get('/addr/:addr/totalSent', addresses.checkAddr.bind(addresses), addresses.totalSent.bind(addresses)); - app.get('/addr/:addr/unconfirmedBalance', addresses.checkAddr.bind(addresses), addresses.unconfirmedBalance.bind(addresses)); + app.get('/addr/:addr/balance', this.cacheShort(), addresses.checkAddr.bind(addresses), addresses.balance.bind(addresses)); + app.get('/addr/:addr/totalReceived', this.cacheShort(), addresses.checkAddr.bind(addresses), addresses.totalReceived.bind(addresses)); + app.get('/addr/:addr/totalSent', this.cacheShort(), addresses.checkAddr.bind(addresses), addresses.totalSent.bind(addresses)); + app.get('/addr/:addr/unconfirmedBalance', this.cacheShort(), addresses.checkAddr.bind(addresses), addresses.unconfirmedBalance.bind(addresses)); // Status route var status = new StatusController(this.node); - app.get('/status', status.show.bind(status)); - app.get('/sync', status.sync.bind(status)); - app.get('/peer', status.peer.bind(status)); - app.get('/version', status.version.bind(status)); + app.get('/status', this.cacheShort(), status.show.bind(status)); + app.get('/sync', this.cacheShort(), status.sync.bind(status)); + app.get('/peer', this.cacheShort(), status.peer.bind(status)); + app.get('/version', this.cacheShort(), status.version.bind(status)); // Address routes var messages = new MessagesController(this.node); diff --git a/test/index.js b/test/index.js new file mode 100644 index 0000000..2d8e5da --- /dev/null +++ b/test/index.js @@ -0,0 +1,110 @@ +'use strict'; + +var should = require('should'); +var sinon = require('sinon'); +var InsightAPI = require('../lib/index'); + +describe('Index', function() { + describe('#cache', function() { + it('will set cache control header', function(done) { + var index = new InsightAPI({ + enableCache: true + }); + var req = {}; + var res = { + header: sinon.stub() + }; + var middle = index.cache(10); + middle(req, res, function() { + res.header.callCount.should.equal(1); + res.header.args[0][0].should.equal('Cache-Control'); + res.header.args[0][1].should.equal('public, max-age=10'); + done(); + }); + }); + it('will NOT set cache control header', function(done) { + var index = new InsightAPI({ + enableCache: false + }); + var req = {}; + var res = { + header: sinon.stub() + }; + var middle = index.cache(10); + middle(req, res, function() { + res.header.callCount.should.equal(0); + done(); + }); + }); + }); + describe('#cacheShort', function() { + it('will set SHORT cache control header', function(done) { + var index = new InsightAPI({ + enableCache: true, + cacheShortSeconds: 35 + }); + var req = {}; + var res = { + header: sinon.stub() + }; + var middle = index.cacheShort(); + middle(req, res, function() { + res.header.callCount.should.equal(1); + res.header.args[0][0].should.equal('Cache-Control'); + res.header.args[0][1].should.equal('public, max-age=35'); + done(); + }); + }); + it('will set SHORT DEFAULT cache control header', function(done) { + var index = new InsightAPI({ + enableCache: true + }); + var req = {}; + var res = { + header: sinon.stub() + }; + var middle = index.cacheShort(); + middle(req, res, function() { + res.header.callCount.should.equal(1); + res.header.args[0][0].should.equal('Cache-Control'); + res.header.args[0][1].should.equal('public, max-age=30'); + done(); + }); + }); + }); + describe('#cacheLong', function() { + it('will set LONG cache control header', function(done) { + var index = new InsightAPI({ + enableCache: true, + cacheLongSeconds: 86400000 + }); + var req = {}; + var res = { + header: sinon.stub() + }; + var middle = index.cacheLong(); + middle(req, res, function() { + res.header.callCount.should.equal(1); + res.header.args[0][0].should.equal('Cache-Control'); + res.header.args[0][1].should.equal('public, max-age=86400000'); + done(); + }); + }); + it('will set LONG DEFAULT cache control header', function(done) { + var index = new InsightAPI({ + enableCache: true + }); + var req = {}; + var res = { + header: sinon.stub() + }; + var middle = index.cacheLong(); + middle(req, res, function() { + res.header.callCount.should.equal(1); + res.header.args[0][0].should.equal('Cache-Control'); + res.header.args[0][1].should.equal('public, max-age=86400'); + done(); + }); + }); + }); +});