UNPKG

nsolid-manager

Version:

A manager that sets up and runs N|Solid for you

161 lines (142 loc) 5 kB
'use strict' module.exports = Router const Registry = require('nsolid-registry-etcd') const debug = require('util').debuglog('nsolid') const through2 = require('through2') const Client = require('nsolid-rpcclient') // TBD: a custom endpoint for users to stuff metrics in, maybe a npm.im/statware function Router (options) { if (!(this instanceof Router)) { return new Router(options) } this.registry = Registry(options.etcd_host, {timeout: 5000}) this.denied = options.denied this.broadcast_approved = options.broadcast_approved debug('initialized Router: ' + options.etcd_host) } Router.prototype.list = function list (req, proxySpec, next) { debug('list') let app = proxySpec.app || '*' let toStream = through2() this.registry.list(app, function procList (err, hosts) { if (err) { toStream.emit('error', err) return } debug('host list from registry: ', hosts) // Convert the array into a newline-delimited JSON stream // It is a "fake" stream for now as it is entirely buffered before we stream it for (let i = 0; i < hosts.length; i++) { toStream.write(JSON.stringify(hosts[i]) + '\n') } toStream.end() }) return next(null, toStream) } Router.prototype.proxy = function proxy (req, proxySpec, next) { debug('proxy', proxySpec) let command = proxySpec.command function asyncError (err) { setImmediate(function soon () { next(err) }) } if (command == null) { let err = new TypeError('Missing required command!') return asyncError(err) } if (this.denied.indexOf(command) >= 0) { let err = new Error('Refusing to send denied command ' + command) return asyncError(err) } let id = proxySpec.id let app = proxySpec.app let timeout = proxySpec.timeout if (id == null) { if (app == null) { // switch no app to wildcard for broadcast when no id app = '*' } if (this.broadcast_approved.indexOf(command) < 0) { let err = new Error('Command \'' + command + '\' is not broadcast approved, id is required.') return asyncError(err) } return this._broadcastLookup(command, app, proxySpec.data, next, timeout) } if (app == null) { let err = new Error('`app` and `id` are both required for lookup') return asyncError(err) } return this._singleProxyLookup(command, app, id, proxySpec.data, next, timeout) } Router.prototype._broadcastLookup = function _broadcastLookup (command, app, data, next, timeout) { let self = this this.registry.list(app, function procList (err, hosts) { if (err) { return next(err) } if (hosts.length === 0) { debug('no hosts found to broadcast to') return next(null, null) } // TBD how does etcd handle 1000s of hosts? // speed fast enough? // need to stream? self._broadcast(hosts, command, data, next, timeout) }) } Router.prototype._singleProxyLookup = function _singleProxyLookup (command, app, id, data, next, timeout) { let self = this this.registry.lookup(app, id, function (err, host) { if (err) { return next(err) } if (host == null || host.address == null) { return next(new Error('Unable to locate host at id ' + id)) } self._proxyOneHost(host.address, command, data, next, timeout) }) } Router.prototype._proxyOneHost = function _proxyOneHost (address, command, data, next, timeout) { debug('proxying %s to single endpoint %s', command, address) let req = Client.sendTo(address, command, data, timeout) next(null, req) } Router.prototype._broadcast = function _broadcast (hosts, command, data, next, timeout) { debug('proxying %s to %s endpoints', command, hosts.length) let resultStream = through2() let count = 0 // TBD: this forEach could be problematic for 100s+ of hosts, may need to: // 1. send in batches // 2. add a total timeout hosts.forEach(function proxy (host) { Client.sendToBuffered(host.address, command, data, function finished (err, response, body) { debug('handling response for ' + host.address) // Concatenate in metadata to identify where the reply came from let pid = host.pid || 0 let prefix = '{"meta":{"id":"' + host.id + '","app":"' + host.app + '","pid":' + pid + ',"hostname":"' + host.hostname + '","address":"' + host.address + '"},' resultStream.write(prefix) if (err) { debug(err) resultStream.write('"error":') resultStream.write(JSON.stringify(err.message)) } else if (response.statusCode >= 300) { debug('agent replied with status code ' + response.statusCode) resultStream.write('"error":') resultStream.write(body) } else { resultStream.write('"reply":') resultStream.write(body) } resultStream.write('}\n') if (++count === hosts.length) { resultStream.end() } }, timeout) }) next(null, resultStream) }