From 4ad104d85d813c2016f4abcafb46dfb5f0bba23b Mon Sep 17 00:00:00 2001 From: Grant Herman Date: Mon, 11 Mar 2019 09:06:49 -0700 Subject: [PATCH] feat(issue-1852): support multiple API and Gateway addresses (#1903) resolves #1852 License: MIT Signed-off-by: Grant Herman grantlouisherman041@gmail.com --- src/http/index.js | 58 ++++++++++++++++++++--------- test/cli/daemon.js | 62 ++++++++++++++++++++++++++++++- test/gateway/index.js | 2 +- test/http-api/inject/bitswap.js | 2 +- test/http-api/inject/block.js | 2 +- test/http-api/inject/bootstrap.js | 2 +- test/http-api/inject/config.js | 2 +- test/http-api/inject/dht.js | 2 +- test/http-api/inject/dns.js | 2 +- test/http-api/inject/files.js | 2 +- test/http-api/inject/id.js | 2 +- test/http-api/inject/name.js | 2 +- test/http-api/inject/object.js | 2 +- test/http-api/inject/pin.js | 2 +- test/http-api/inject/ping.js | 2 +- test/http-api/inject/pubsub.js | 2 +- test/http-api/inject/resolve.js | 2 +- test/http-api/inject/version.js | 2 +- 18 files changed, 117 insertions(+), 35 deletions(-) diff --git a/src/http/index.js b/src/http/index.js index 59968fc2ae..0692f4785c 100644 --- a/src/http/index.js +++ b/src/http/index.js @@ -29,6 +29,22 @@ function hapiInfoToMultiaddr (info) { return toMultiaddr(uri) } +function serverCreator (serverAddrs, createServer, ipfs) { + serverAddrs = serverAddrs || [] + // just in case the address is just string + serverAddrs = Array.isArray(serverAddrs) ? serverAddrs : [serverAddrs] + + const processServer = async address => { + const addrParts = address.split('/') + const server = await createServer(addrParts[2], addrParts[4], ipfs) + await server.start() + server.info.ma = hapiInfoToMultiaddr(server.info) + return server + } + + return Promise.all(serverAddrs.map(processServer)) +} + class HttpApi { constructor (options) { this._options = options || {} @@ -88,25 +104,28 @@ class HttpApi { this._ipfs = ipfs const config = await ipfs.config.get() + config.Addresses = config.Addresses || {} - const apiAddr = config.Addresses.API.split('/') - const apiServer = await this._createApiServer(apiAddr[2], apiAddr[4], ipfs) - await apiServer.start() - apiServer.info.ma = hapiInfoToMultiaddr(apiServer.info) - this._apiServer = apiServer + const apiAddrs = config.Addresses.API + this._apiServers = await serverCreator(apiAddrs, this._createApiServer, ipfs) // for the CLI to know the where abouts of the API - await promisify(ipfs._repo.apiAddr.set)(apiServer.info.ma) + if (this._apiServers.length) { + await promisify(ipfs._repo.apiAddr.set)(this._apiServers[0].info.ma) + } - const gatewayAddr = config.Addresses.Gateway.split('/') - const gatewayServer = await this._createGatewayServer(gatewayAddr[2], gatewayAddr[4], ipfs) - await gatewayServer.start() - gatewayServer.info.ma = hapiInfoToMultiaddr(gatewayServer.info) - this._gatewayServer = gatewayServer + const gatewayAddrs = config.Addresses.Gateway + this._gatewayServers = await serverCreator(gatewayAddrs, this._createGatewayServer, ipfs) - ipfs._print('API listening on %s', apiServer.info.ma) - ipfs._print('Gateway (read only) listening on %s', gatewayServer.info.ma) - ipfs._print('Web UI available at %s', toUri(apiServer.info.ma) + '/webui') + this._apiServers.forEach(apiServer => { + ipfs._print('API listening on %s', apiServer.info.ma) + }) + this._gatewayServers.forEach(gatewayServer => { + ipfs._print('Gateway (read only) listening on %s', gatewayServer.info.ma) + }) + this._apiServers.forEach(apiServer => { + ipfs._print('Web UI available at %s', toUri(apiServer.info.ma) + '/webui') + }) this._log('started') return this } @@ -176,15 +195,18 @@ class HttpApi { } get apiAddr () { - if (!this._apiServer) throw new Error('API address unavailable - server is not started') - return multiaddr('/ip4/127.0.0.1/tcp/' + this._apiServer.info.port) + if (!this._apiServers || !this._apiServers.length) { + throw new Error('API address unavailable - server is not started') + } + return multiaddr('/ip4/127.0.0.1/tcp/' + this._apiServers[0].info.port) } async stop () { this._log('stopping') + const stopServers = servers => Promise.all((servers || []).map(s => s.stop())) await Promise.all([ - this._apiServer && this._apiServer.stop(), - this._gatewayServer && this._gatewayServer.stop(), + stopServers(this._apiServers), + stopServers(this._gatewayServers), this._ipfs && this._ipfs.stop() ]) this._log('stopped') diff --git a/test/cli/daemon.js b/test/cli/daemon.js index a9aaccacf0..5e9efe08d8 100644 --- a/test/cli/daemon.js +++ b/test/cli/daemon.js @@ -26,7 +26,7 @@ const checkLock = (repo, cb) => { cb() } -function testSignal (ipfs, sig) { +function testSignal (ipfs, sig, config) { return ipfs('init').then(() => { return ipfs('config', 'Addresses', JSON.stringify({ API: '/ip4/127.0.0.1/tcp/0', @@ -91,6 +91,66 @@ describe('daemon', () => { }).catch(err => done(err)) }) + it('should allow bind to multiple addresses for API and Gateway', async function () { + this.timeout(20 * 1000) + + const apiAddrs = [ + '/ip4/127.0.0.1/tcp/55001', + '/ip4/127.0.0.1/tcp/55002' + ] + + const gatewayAddrs = [ + '/ip4/127.0.0.1/tcp/64080', + '/ip4/127.0.0.1/tcp/64081' + ] + + await ipfs('init') + await ipfs('config', 'Addresses.API', JSON.stringify(apiAddrs), '--json') + await ipfs('config', 'Addresses.Gateway', JSON.stringify(gatewayAddrs), '--json') + + const out = await new Promise(resolve => { + const res = ipfs('daemon') + let out = '' + + res.stdout.on('data', function onData (data) { + out += data + if (out.includes('Daemon is ready')) { + res.stdout.removeListener('data', onData) + res.kill() + resolve(out) + } + }) + }) + + apiAddrs.forEach(addr => expect(out).to.include(`API listening on ${addr}`)) + gatewayAddrs.forEach(addr => expect(out).to.include(`Gateway (read only) listening on ${addr}`)) + }) + + it('should allow no bind addresses for API and Gateway', async function () { + this.timeout(20 * 1000) + + await ipfs('init') + await ipfs('config', 'Addresses.API', '[]', '--json') + await ipfs('config', 'Addresses.Gateway', '[]', '--json') + + const out = await new Promise(resolve => { + const res = ipfs('daemon') + let out = '' + + res.stdout.on('data', function onData (data) { + out += data + if (out.includes('Daemon is ready')) { + res.stdout.removeListener('data', onData) + res.kill() + resolve(out) + } + }) + }) + + expect(out).to.not.include('API listening on') + expect(out).to.not.include('Gateway (read only) listening on') + }) + skipOnWindows('should handle SIGINT gracefully', function (done) { this.timeout(100 * 1000) diff --git a/test/gateway/index.js b/test/gateway/index.js index 0f9ac1812b..c5f9bd0be6 100644 --- a/test/gateway/index.js +++ b/test/gateway/index.js @@ -60,7 +60,7 @@ describe('HTTP Gateway', function () { await http.api.start() - gateway = http.api._gatewayServer + gateway = http.api._gatewayServers[0] // QmbQD7EMEL1zeebwBsWEfA3ndgSS6F7S6iTuwuqasPgVRi await http.api._ipfs.add([ diff --git a/test/http-api/inject/bitswap.js b/test/http-api/inject/bitswap.js index c8a1d0568c..9ac273c947 100644 --- a/test/http-api/inject/bitswap.js +++ b/test/http-api/inject/bitswap.js @@ -12,7 +12,7 @@ module.exports = (http) => { let api before(() => { - api = http.api._apiServer + api = http.api._apiServers[0] }) before(async function () { diff --git a/test/http-api/inject/block.js b/test/http-api/inject/block.js index 1cf8eb52e4..a121fc315d 100644 --- a/test/http-api/inject/block.js +++ b/test/http-api/inject/block.js @@ -13,7 +13,7 @@ module.exports = (http) => { let api before(() => { - api = http.api._apiServer + api = http.api._apiServers[0] }) describe('/block/put', () => { diff --git a/test/http-api/inject/bootstrap.js b/test/http-api/inject/bootstrap.js index 7708390cfe..5015b3d40f 100644 --- a/test/http-api/inject/bootstrap.js +++ b/test/http-api/inject/bootstrap.js @@ -11,7 +11,7 @@ module.exports = (http) => { let api before(() => { - api = http.api._apiServer + api = http.api._apiServers[0] return api.inject({ method: 'GET', url: '/api/v0/bootstrap/add/default' diff --git a/test/http-api/inject/config.js b/test/http-api/inject/config.js index 3e647505ff..2388ed4264 100644 --- a/test/http-api/inject/config.js +++ b/test/http-api/inject/config.js @@ -17,7 +17,7 @@ module.exports = (http) => { before(() => { updatedConfig = () => JSON.parse(fs.readFileSync(configPath, 'utf8')) - api = http.api._apiServer + api = http.api._apiServers[0] }) after(() => { diff --git a/test/http-api/inject/dht.js b/test/http-api/inject/dht.js index f614ee50dc..04cb501227 100644 --- a/test/http-api/inject/dht.js +++ b/test/http-api/inject/dht.js @@ -12,7 +12,7 @@ module.exports = (http) => { let api before(() => { - api = http.api._apiServer + api = http.api._apiServers[0] }) describe('/findpeer', () => { diff --git a/test/http-api/inject/dns.js b/test/http-api/inject/dns.js index 163f2dfd4f..010797f57a 100644 --- a/test/http-api/inject/dns.js +++ b/test/http-api/inject/dns.js @@ -8,7 +8,7 @@ module.exports = (http) => { let api before(() => { - api = http.api._apiServer + api = http.api._apiServers[0] }) it('resolve ipfs.io dns', async () => { diff --git a/test/http-api/inject/files.js b/test/http-api/inject/files.js index 06d2e02597..8ff819dfce 100644 --- a/test/http-api/inject/files.js +++ b/test/http-api/inject/files.js @@ -13,7 +13,7 @@ module.exports = (http) => { let api before(() => { - api = http.api._apiServer + api = http.api._apiServers[0] }) describe('/add', () => { diff --git a/test/http-api/inject/id.js b/test/http-api/inject/id.js index eb646be893..ff8922f312 100644 --- a/test/http-api/inject/id.js +++ b/test/http-api/inject/id.js @@ -8,7 +8,7 @@ module.exports = (http) => { let api before(() => { - api = http.api._apiServer + api = http.api._apiServers[0] }) it('get the id', async () => { diff --git a/test/http-api/inject/name.js b/test/http-api/inject/name.js index 95a51eb5c2..699ed0a07b 100644 --- a/test/http-api/inject/name.js +++ b/test/http-api/inject/name.js @@ -16,7 +16,7 @@ module.exports = (http) => { let api before(() => { - api = http.api._apiServer + api = http.api._apiServers[0] }) it('should publish a record', async function () { diff --git a/test/http-api/inject/object.js b/test/http-api/inject/object.js index e329e98c64..952cf1ab7d 100644 --- a/test/http-api/inject/object.js +++ b/test/http-api/inject/object.js @@ -17,7 +17,7 @@ module.exports = (http) => { let api before('api', () => { - api = http.api._apiServer + api = http.api._apiServers[0] }) describe('/new', () => { diff --git a/test/http-api/inject/pin.js b/test/http-api/inject/pin.js index d70547a876..db1ec00b6c 100644 --- a/test/http-api/inject/pin.js +++ b/test/http-api/inject/pin.js @@ -37,7 +37,7 @@ module.exports = (http) => { let api before(() => { - api = http.api._apiServer + api = http.api._apiServers[0] }) describe('rm', () => { diff --git a/test/http-api/inject/ping.js b/test/http-api/inject/ping.js index 0ed460bba3..3ed712c483 100644 --- a/test/http-api/inject/ping.js +++ b/test/http-api/inject/ping.js @@ -13,7 +13,7 @@ module.exports = (http) => { let api before(() => { - api = http.api._apiServer + api = http.api._apiServers[0] }) it('returns 400 if both n and count are provided', async () => { diff --git a/test/http-api/inject/pubsub.js b/test/http-api/inject/pubsub.js index e91df8b3a3..3e88d40434 100644 --- a/test/http-api/inject/pubsub.js +++ b/test/http-api/inject/pubsub.js @@ -16,7 +16,7 @@ module.exports = (http) => { const topicNotSubscribed = 'somethingRandom' before(() => { - api = http.api._apiServer + api = http.api._apiServers[0] }) describe('/sub', () => { diff --git a/test/http-api/inject/resolve.js b/test/http-api/inject/resolve.js index edb7eb94ab..1d70206e8a 100644 --- a/test/http-api/inject/resolve.js +++ b/test/http-api/inject/resolve.js @@ -12,7 +12,7 @@ module.exports = (http) => { let api before(() => { - api = http.api._apiServer + api = http.api._apiServers[0] }) it('should resolve a path and return a base2 encoded CID', async () => { diff --git a/test/http-api/inject/version.js b/test/http-api/inject/version.js index 2ab6b18bf9..9a83821ebe 100644 --- a/test/http-api/inject/version.js +++ b/test/http-api/inject/version.js @@ -9,7 +9,7 @@ module.exports = (http) => { let api before(() => { - api = http.api._apiServer + api = http.api._apiServers[0] }) it('get the version', async () => {