diff --git a/lib/cmds/blockchain/blockchain.js b/lib/cmds/blockchain/blockchain.js index 745b79fb6..9e881a0e2 100644 --- a/lib/cmds/blockchain/blockchain.js +++ b/lib/cmds/blockchain/blockchain.js @@ -7,6 +7,8 @@ const utils = require('../../utils/utils.js'); const GethCommands = require('./geth_commands.js'); +const {defaultHost, dockerHostSwap} = require('../../utils/host'); + /*eslint complexity: ["error", 36]*/ var Blockchain = function(options) { this.blockchainConfig = options.blockchainConfig; @@ -26,7 +28,7 @@ var Blockchain = function(options) { genesisBlock: this.blockchainConfig.genesisBlock || false, datadir: this.blockchainConfig.datadir || false, mineWhenNeeded: this.blockchainConfig.mineWhenNeeded || false, - rpcHost: this.blockchainConfig.rpcHost || 'localhost', + rpcHost: dockerHostSwap(this.blockchainConfig.rpcHost) || defaultHost, rpcPort: this.blockchainConfig.rpcPort || 8545, rpcCorsDomain: this.blockchainConfig.rpcCorsDomain || false, networkId: this.blockchainConfig.networkId || 1337, @@ -39,7 +41,7 @@ var Blockchain = function(options) { bootnodes: this.blockchainConfig.bootnodes || "", rpcApi: (this.blockchainConfig.rpcApi || ['eth', 'web3', 'net', 'debug']), wsRPC: (this.blockchainConfig.wsRPC === undefined) || this.blockchainConfig.wsRPC, - wsHost: this.blockchainConfig.wsHost || 'localhost', + wsHost: dockerHostSwap(this.blockchainConfig.wsHost) || defaultHost, wsPort: this.blockchainConfig.wsPort || 8546, wsOrigins: this.blockchainConfig.wsOrigins || false, wsApi: (this.blockchainConfig.wsApi || ['eth', 'web3', 'net', 'shh', 'debug']), diff --git a/lib/cmds/simulator.js b/lib/cmds/simulator.js index eeeb8131a..e160b321f 100644 --- a/lib/cmds/simulator.js +++ b/lib/cmds/simulator.js @@ -3,6 +3,7 @@ let shelljs = require('shelljs'); let proxy = require('../core/proxy'); const Ipc = require('../core/ipc'); const constants = require('../constants.json'); +const {defaultHost, dockerHostSwap} = require('../utils/host'); class Simulator { constructor(options) { @@ -16,7 +17,7 @@ class Simulator { const ganache = path.join(__dirname, '../../node_modules/.bin/ganache-cli'); let useProxy = this.blockchainConfig.proxy || false; - let host = (options.host || this.blockchainConfig.rpcHost || 'localhost'); + let host = (dockerHostSwap(options.host || this.blockchainConfig.rpcHost) || defaultHost); let port = (options.port || this.blockchainConfig.rpcPort || 8545); cmds.push("-p " + (port + (useProxy ? constants.blockchain.servicePortOnProxy : 0))); diff --git a/lib/core/config.js b/lib/core/config.js index ac7debe56..925e77fe8 100644 --- a/lib/core/config.js +++ b/lib/core/config.js @@ -5,6 +5,7 @@ const utils = require('../utils/utils.js'); const path = require('path'); const deepEqual = require('deep-equal'); const constants = require('../constants'); +const {canonicalHost, defaultHost} = require('../utils/host'); var Config = function(options) { const self = this; @@ -106,6 +107,9 @@ Config.prototype._updateBlockchainCors = function(){ // remove /ipfs or /bzz: from getUrl if it's there let getUrlParts = storageConfig.upload.getUrl.split('/'); getUrlParts = getUrlParts.slice(0, 3); + let host = canonicalHost(getUrlParts[2].split(':')[0]); + let port = getUrlParts[2].split(':')[1]; + getUrlParts[2] = port ? [host, port].join(':') : host; corsParts.push(getUrlParts.join('/')); } // use our modified getUrl or in case it wasn't specified, use a built url @@ -259,7 +263,7 @@ Config.prototype.loadStorageConfigFile = function() { "upload": { "provider": "ipfs", "protocol": "http", - "host": "localhost", + "host" : defaultHost, "port": 5001, "getUrl": "http://localhost:8080/ipfs/" }, @@ -297,7 +301,9 @@ Config.prototype.loadCommunicationConfigFile = function() { "provider": "whisper", "available_providers": ["whisper"], "connection": { - "host": "localhost", "port": 8546, "type": "ws" + "host": defaultHost, + "port": 8546, + "type": "ws" } } }; @@ -309,7 +315,9 @@ Config.prototype.loadCommunicationConfigFile = function() { Config.prototype.loadWebServerConfigFile = function() { var configObject = { - "enabled": true, "host": "localhost", "port": 8000 + "enabled": true, + "host": defaultHost, + "port": 8000 }; let configFilePath = this._getFileOrOject(this.configDir, 'webserver', 'webserver'); diff --git a/lib/core/proxy.js b/lib/core/proxy.js index 241a3bf82..ce3ed4b18 100644 --- a/lib/core/proxy.js +++ b/lib/core/proxy.js @@ -6,6 +6,8 @@ let commList = {}; let transactions = {}; let receipts = {}; +const {canonicalHost, defaultHost} = require('../utils/host'); + const parseRequest = function(reqBody){ let jsonO; try { @@ -61,7 +63,7 @@ const parseResponse = function(ipc, resBody){ exports.serve = function(ipc, host, port, ws){ let proxy = httpProxy.createProxyServer({ target: { - host, + host: canonicalHost(host), port: port + constants.blockchain.servicePortOnProxy }, ws: ws @@ -120,6 +122,6 @@ exports.serve = function(ipc, host, port, ws){ }); } - server.listen(port); + server.listen(port, defaultHost); return server; }; diff --git a/lib/modules/webserver/index.js b/lib/modules/webserver/index.js index a13fb1b7a..a4dada8a9 100644 --- a/lib/modules/webserver/index.js +++ b/lib/modules/webserver/index.js @@ -1,3 +1,4 @@ +var {canonicalHost} = require('../../utils/host.js'); var utils = require('../../utils/utils.js'); var Server = require('./server.js'); @@ -26,7 +27,7 @@ class WebServer { } setServiceCheck() { - let url = 'http://' + this.host + ':' + this.port; + let url = 'http://' + canonicalHost(this.host) + ':' + this.port; this.events.request("services:register", 'Webserver', function (cb) { utils.checkIsAvailable(url, function (available) { diff --git a/lib/modules/webserver/server.js b/lib/modules/webserver/server.js index 9f1c64edb..8d9747963 100644 --- a/lib/modules/webserver/server.js +++ b/lib/modules/webserver/server.js @@ -1,19 +1,23 @@ let finalhandler = require('finalhandler'); let http = require('http'); let serveStatic = require('serve-static'); +const {canonicalHost, defaultHost, dockerHostSwap} = require('../../utils/host'); require('http-shutdown').extend(); class Server { constructor(options) { this.dist = options.dist || 'dist/'; this.port = options.port || 8000; - this.hostname = options.host || 'localhost'; + this.hostname = dockerHostSwap(options.host) || defaultHost; this.logger = options.logger; } start(callback) { if (this.server && this.server.listening) { - this.logger.warn(__("a webserver is already running at") + " " + ("http://" + this.hostname + ":" + this.port).bold.underline.green); + this.logger.warn(__("a webserver is already running at") + + " " + + ("http://" + canonicalHost(this.hostname) + + ":" + this.port).bold.underline.green); if (callback) { callback(); } @@ -25,7 +29,10 @@ class Server { serve(req, res, finalhandler(req, res)); }).withShutdown(); - this.logger.info(__("webserver available at") + " " + ("http://" + this.hostname + ":" + this.port).bold.underline.green); + this.logger.info(__("webserver available at") + + " " + + ("http://" + canonicalHost(this.hostname) + + ":" + this.port).bold.underline.green); this.server.listen(this.port, this.hostname); if (callback) { callback(); diff --git a/lib/modules/whisper/index.js b/lib/modules/whisper/index.js index 0b688971d..830f1066f 100644 --- a/lib/modules/whisper/index.js +++ b/lib/modules/whisper/index.js @@ -2,6 +2,8 @@ let utils = require('../../utils/utils.js'); let fs = require('../../core/fs.js'); let Web3 = require('web3'); +const {canonicalHost, defaultHost} = require('../../utils/host'); + class Whisper { constructor(embark, _options) { @@ -74,7 +76,7 @@ class Whisper { // todo: make the add code a function as well let config = JSON.stringify({ - server: connection.host || 'localhost', + server: canonicalHost(connection.host || defaultHost), port: connection.port || '8546', type: connection.type || 'ws' }); diff --git a/lib/processes/storageProcesses/storageProcessesLauncher.js b/lib/processes/storageProcesses/storageProcessesLauncher.js index 2ac624a01..6501638a1 100644 --- a/lib/processes/storageProcesses/storageProcessesLauncher.js +++ b/lib/processes/storageProcesses/storageProcessesLauncher.js @@ -4,6 +4,7 @@ const utils = require('../../utils/utils'); const ProcessLauncher = require('../../process/processLauncher'); const constants = require('../../constants'); const StorageUtils = require('./storageUtils'); +const {canonicalHost} = require('../../utils/host'); class StorageProcessesLauncher { constructor(options) { @@ -46,6 +47,9 @@ class StorageProcessesLauncher { // remove /ipfs or /bzz: from getUrl if it's there let getUrlParts = dappConn.getUrl.split('/'); getUrlParts = getUrlParts.slice(0, 3); + let host = canonicalHost(getUrlParts[2].split(':')[0]); + let port = getUrlParts[2].split(':')[1]; + getUrlParts[2] = port ? [host, port].join(':') : host; corsParts.push(getUrlParts.join('/')); } // in case getUrl wasn't specified, use a built url @@ -59,12 +63,12 @@ class StorageProcessesLauncher { if(this.blockchainConfig.enabled) { // add our rpc endpoints to CORS if(this.blockchainConfig.rpcHost && this.blockchainConfig.rpcPort){ - corsParts.push(`http://${this.blockchainConfig.rpcHost}:${this.blockchainConfig.rpcPort}`); + corsParts.push(`http://${canonicalHost(this.blockchainConfig.rpcHost)}:${this.blockchainConfig.rpcPort}`); } // add our ws endpoints to CORS if(this.blockchainConfig.wsRPC && this.blockchainConfig.wsHost && this.blockchainConfig.wsPort){ - corsParts.push(`ws://${this.blockchainConfig.wsHost}:${this.blockchainConfig.wsPort}`); + corsParts.push(`ws://${canonicalHost(this.blockchainConfig.wsHost)}:${this.blockchainConfig.wsPort}`); } } return corsParts; diff --git a/lib/utils/host.js b/lib/utils/host.js new file mode 100644 index 000000000..675881613 --- /dev/null +++ b/lib/utils/host.js @@ -0,0 +1,45 @@ +const isDocker = (() => { + let isDocker; + + const hostname = require('os').hostname(); + const pattern = new RegExp( + '[0-9]+\:[a-z_-]+\:\/docker\/' + hostname + '[0-9a-z]+', 'i' + ); + + try { + isDocker = require('child_process') + .execSync( + 'cat /proc/self/cgroup', + {stdio: ['ignore', 'pipe', 'ignore']} + ) + .toString().match(pattern) !== null; + } catch (e) { + isDocker = false; + } + + return isDocker; +})(); + +const defaultHost = isDocker ? '0.0.0.0' : 'localhost'; + +// when we're runing in Docker, we can expect (generally, in a development +// scenario) that the user would like to connect to the service in the +// container via the **host's** loopback address, so this helper can be used to +// swap 0.0.0.0 for localhost in code/messages that pertain to client-side +function canonicalHost(host) { + return isDocker && host === '0.0.0.0' ? 'localhost' : host; +} + +function dockerHostSwap(host) { + return (isDocker && (host === 'localhost' || host === '127.0.0.1')) ? defaultHost : host; +} + +const defaultCorsHost = canonicalHost(defaultHost); + +module.exports = { + canonicalHost, + defaultCorsHost, + defaultHost, + dockerHostSwap, + isDocker +}; diff --git a/lib/utils/utils.js b/lib/utils/utils.js index 785c3c19d..85ae481cd 100644 --- a/lib/utils/utils.js +++ b/lib/utils/utils.js @@ -1,5 +1,6 @@ let http = require('follow-redirects').http; let https = require('follow-redirects').https; +const {canonicalHost} = require('./host'); function joinPath() { const path = require('path'); @@ -314,7 +315,7 @@ function buildUrl (protocol, host, port){ function buildUrlFromConfig (configObj){ if(!configObj) throw new Error('[utils.buildUrlFromConfig]: config object must cannot be null'); if(!configObj.host) throw new Error('[utils.buildUrlFromConfig]: object must contain a \'host\' property'); - return this.buildUrl(configObj.protocol, configObj.host, configObj.port); + return this.buildUrl(configObj.protocol, canonicalHost(configObj.host), configObj.port); } function compact(array) { diff --git a/test/blockchain.js b/test/blockchain.js index 52bfc4319..36e1f6886 100644 --- a/test/blockchain.js +++ b/test/blockchain.js @@ -1,6 +1,7 @@ /*globals describe, it*/ const Blockchain = require('../lib/cmds/blockchain/blockchain'); const constants = require('../lib/constants.json'); +const {defaultHost} = require('../lib/utils/host'); const assert = require('assert'); @@ -19,7 +20,7 @@ describe('embark.Blockchain', function () { geth_bin: 'geth', datadir: false, mineWhenNeeded: false, - rpcHost: 'localhost', + rpcHost: defaultHost, rpcPort: 8545, rpcApi: ['eth', 'web3', 'net', 'debug'], rpcCorsDomain: false, @@ -33,7 +34,7 @@ describe('embark.Blockchain', function () { account: {}, bootnodes: "", wsApi: ["eth", "web3", "net", "shh", "debug"], - wsHost: "localhost", + wsHost: defaultHost, wsOrigins: false, wsPort: 8546, wsRPC: true, @@ -63,7 +64,7 @@ describe('embark.Blockchain', function () { geth_bin: 'geth', datadir: '/foo/datadir/', mineWhenNeeded: true, - rpcHost: 'someserver', + rpcHost: defaultHost, rpcPort: 12345, rpcApi: ['eth', 'web3', 'net', 'debug'], rpcCorsDomain: true, @@ -77,9 +78,9 @@ describe('embark.Blockchain', function () { account: {}, bootnodes: "", wsApi: ["eth", "web3", "net", "shh", "debug"], - wsHost: "localhost", + wsHost: defaultHost, wsOrigins: false, - wsPort: 8546, + wsPort: 12346, wsRPC: true, targetGasLimit: false, syncMode: undefined,