2015-08-28 09:57:01 -07:00
|
|
|
'use strict';
|
|
|
|
|
2016-04-15 07:44:56 -07:00
|
|
|
var fs = require('fs');
|
2015-08-28 09:57:01 -07:00
|
|
|
var http = require('http');
|
2015-09-09 13:39:21 -07:00
|
|
|
var https = require('https');
|
2015-08-28 09:57:01 -07:00
|
|
|
var express = require('express');
|
|
|
|
var bodyParser = require('body-parser');
|
|
|
|
var socketio = require('socket.io');
|
2015-08-31 10:38:21 -07:00
|
|
|
var inherits = require('util').inherits;
|
2016-04-15 07:44:56 -07:00
|
|
|
|
|
|
|
var BaseService = require('../service');
|
|
|
|
var bitcore = require('bitcore-lib');
|
|
|
|
var _ = bitcore.deps._;
|
2015-09-03 14:29:28 -07:00
|
|
|
var index = require('../');
|
|
|
|
var log = index.log;
|
2016-04-15 07:44:56 -07:00
|
|
|
|
2015-08-28 09:57:01 -07:00
|
|
|
|
2015-09-22 08:38:14 -07:00
|
|
|
/**
|
2015-10-22 08:47:19 -07:00
|
|
|
* This service represents a hub for combining several services over a single HTTP port. Services
|
2015-09-22 08:38:14 -07:00
|
|
|
* can extend routes by implementing the methods `getRoutePrefix` and `setupRoutes`. Additionally
|
|
|
|
* events that are exposed via the `getPublishEvents` and API methods exposed via `getAPIMethods`
|
|
|
|
* will be available over a socket.io connection.
|
|
|
|
*
|
|
|
|
* @param {Object} options
|
|
|
|
* @param {Node} options.node - A reference to the node
|
|
|
|
* @param {Boolean} options.https - Enable https, will default to node.https settings.
|
|
|
|
* @param {Object} options.httpsOptions - Options passed into https.createServer, defaults to node settings.
|
|
|
|
* @param {String} options.httpsOptions.key - Path to key file
|
|
|
|
* @param {String} options.httpsOptions.cert - Path to cert file
|
|
|
|
* @param {Number} options.port - The port for the service, defaults to node settings.
|
|
|
|
*/
|
2015-08-28 09:57:01 -07:00
|
|
|
var WebService = function(options) {
|
|
|
|
var self = this;
|
|
|
|
this.node = options.node;
|
2015-09-10 08:06:37 -07:00
|
|
|
this.https = options.https || this.node.https;
|
|
|
|
this.httpsOptions = options.httpsOptions || this.node.httpsOptions;
|
2015-08-31 10:05:46 -07:00
|
|
|
this.port = options.port || this.node.port || 3456;
|
2015-08-28 09:57:01 -07:00
|
|
|
|
2016-04-15 07:44:56 -07:00
|
|
|
this.enableSocketRPC = _.isUndefined(options.enableSocketRPC) ?
|
|
|
|
WebService.DEFAULT_SOCKET_RPC : options.enableSocketRPC;
|
|
|
|
|
2015-08-28 09:57:01 -07:00
|
|
|
this.node.on('ready', function() {
|
2015-09-03 14:29:28 -07:00
|
|
|
self.eventNames = self.getEventNames();
|
2015-08-31 10:05:46 -07:00
|
|
|
self.setupAllRoutes();
|
2015-08-28 09:57:01 -07:00
|
|
|
self.server.listen(self.port);
|
2015-08-28 10:54:29 -07:00
|
|
|
self.createMethodsMap();
|
2015-08-28 09:57:01 -07:00
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2015-08-31 10:38:21 -07:00
|
|
|
inherits(WebService, BaseService);
|
|
|
|
|
2015-09-10 08:06:37 -07:00
|
|
|
WebService.dependencies = [];
|
2016-04-15 07:44:56 -07:00
|
|
|
WebService.DEFAULT_SOCKET_RPC = true;
|
2015-08-31 10:38:21 -07:00
|
|
|
|
2015-09-22 08:38:14 -07:00
|
|
|
/**
|
|
|
|
* Called by Node to start the service
|
|
|
|
* @param {Function} callback
|
|
|
|
*/
|
2015-08-28 09:57:01 -07:00
|
|
|
WebService.prototype.start = function(callback) {
|
|
|
|
this.app = express();
|
|
|
|
this.app.use(bodyParser.json());
|
|
|
|
|
2015-09-09 13:39:21 -07:00
|
|
|
if(this.https) {
|
2015-09-10 08:06:37 -07:00
|
|
|
this.transformHttpsOptions();
|
2015-09-09 13:39:21 -07:00
|
|
|
this.server = https.createServer(this.httpsOptions, this.app);
|
|
|
|
} else {
|
|
|
|
this.server = http.createServer(this.app);
|
|
|
|
}
|
2015-08-28 09:57:01 -07:00
|
|
|
|
|
|
|
this.io = socketio.listen(this.server);
|
|
|
|
this.io.on('connection', this.socketHandler.bind(this));
|
|
|
|
|
|
|
|
setImmediate(callback);
|
|
|
|
};
|
|
|
|
|
2015-09-22 08:38:14 -07:00
|
|
|
/**
|
|
|
|
* Called by Node. stop the service
|
|
|
|
* @param {Function} callback
|
|
|
|
*/
|
2015-08-28 09:57:01 -07:00
|
|
|
WebService.prototype.stop = function(callback) {
|
|
|
|
var self = this;
|
|
|
|
|
|
|
|
setImmediate(function() {
|
|
|
|
if(self.server) {
|
|
|
|
self.server.close();
|
|
|
|
}
|
|
|
|
callback();
|
2015-09-04 12:05:32 -07:00
|
|
|
});
|
2015-08-28 09:57:01 -07:00
|
|
|
};
|
|
|
|
|
2015-09-22 08:38:14 -07:00
|
|
|
/**
|
|
|
|
* This function will iterate over all of the available services gathering
|
|
|
|
* all of the exposed HTTP routes.
|
|
|
|
*/
|
2015-08-31 10:05:46 -07:00
|
|
|
WebService.prototype.setupAllRoutes = function() {
|
2015-08-31 10:38:21 -07:00
|
|
|
for(var key in this.node.services) {
|
2015-11-03 07:32:01 -08:00
|
|
|
var subApp = new express();
|
2015-09-03 14:29:28 -07:00
|
|
|
var service = this.node.services[key];
|
|
|
|
|
|
|
|
if(service.getRoutePrefix && service.setupRoutes) {
|
|
|
|
this.app.use('/' + this.node.services[key].getRoutePrefix(), subApp);
|
2015-09-22 08:38:14 -07:00
|
|
|
this.node.services[key].setupRoutes(subApp, express);
|
2015-09-03 14:29:28 -07:00
|
|
|
} else {
|
2015-09-04 12:05:32 -07:00
|
|
|
log.debug('No routes defined for: ' + key);
|
2015-09-03 14:29:28 -07:00
|
|
|
}
|
2015-08-28 09:57:01 -07:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2015-09-22 08:38:14 -07:00
|
|
|
/**
|
2015-10-22 08:47:19 -07:00
|
|
|
* This function will construct an API methods map of all of the
|
2015-09-22 08:38:14 -07:00
|
|
|
* available methods that can be called from enable services.
|
|
|
|
*/
|
2015-08-28 10:54:29 -07:00
|
|
|
WebService.prototype.createMethodsMap = function() {
|
|
|
|
var self = this;
|
|
|
|
var methods = this.node.getAllAPIMethods();
|
|
|
|
this.methodsMap = {};
|
|
|
|
|
|
|
|
methods.forEach(function(data) {
|
|
|
|
var name = data[0];
|
|
|
|
var instance = data[1];
|
|
|
|
var method = data[2];
|
|
|
|
var args = data[3];
|
|
|
|
self.methodsMap[name] = {
|
|
|
|
fn: function() {
|
|
|
|
return method.apply(instance, arguments);
|
|
|
|
},
|
|
|
|
args: args
|
|
|
|
};
|
|
|
|
});
|
2015-09-03 14:29:28 -07:00
|
|
|
};
|
|
|
|
|
2015-09-22 08:38:14 -07:00
|
|
|
/**
|
|
|
|
* This function will gather all of the available events exposed from
|
|
|
|
* the enabled services.
|
|
|
|
*/
|
2015-09-03 14:29:28 -07:00
|
|
|
WebService.prototype.getEventNames = function() {
|
|
|
|
var events = this.node.getAllPublishEvents();
|
|
|
|
var eventNames = [];
|
|
|
|
|
|
|
|
function addEventName(name) {
|
|
|
|
if(eventNames.indexOf(name) > -1) {
|
|
|
|
throw new Error('Duplicate event ' + name);
|
|
|
|
}
|
|
|
|
eventNames.push(name);
|
2015-09-22 08:38:14 -07:00
|
|
|
}
|
2015-09-03 14:29:28 -07:00
|
|
|
|
|
|
|
events.forEach(function(event) {
|
|
|
|
addEventName(event.name);
|
|
|
|
|
|
|
|
if(event.extraEvents) {
|
|
|
|
event.extraEvents.forEach(function(name) {
|
|
|
|
addEventName(name);
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return eventNames;
|
|
|
|
};
|
2015-08-28 10:54:29 -07:00
|
|
|
|
2015-09-22 08:38:14 -07:00
|
|
|
/**
|
|
|
|
* This function is responsible for managing a socket.io connection, including
|
|
|
|
* instantiating a new Bus, subscribing/unsubscribing and handling RPC commands.
|
|
|
|
* @param {Socket} socket - A socket.io socket instance
|
|
|
|
*/
|
2015-08-28 09:57:01 -07:00
|
|
|
WebService.prototype.socketHandler = function(socket) {
|
|
|
|
var bus = this.node.openBus();
|
|
|
|
|
2016-04-15 07:44:56 -07:00
|
|
|
if (this.enableSocketRPC) {
|
|
|
|
socket.on('message', this.socketMessageHandler.bind(this));
|
|
|
|
}
|
2015-08-28 09:57:01 -07:00
|
|
|
|
|
|
|
socket.on('subscribe', function(name, params) {
|
|
|
|
bus.subscribe(name, params);
|
|
|
|
});
|
|
|
|
|
|
|
|
socket.on('unsubscribe', function(name, params) {
|
|
|
|
bus.unsubscribe(name, params);
|
|
|
|
});
|
|
|
|
|
2015-09-03 14:29:28 -07:00
|
|
|
this.eventNames.forEach(function(eventName) {
|
2015-09-03 13:07:35 -07:00
|
|
|
bus.on(eventName, function() {
|
2015-08-28 09:57:01 -07:00
|
|
|
if(socket.connected) {
|
|
|
|
var results = [];
|
|
|
|
|
|
|
|
for(var i = 0; i < arguments.length; i++) {
|
|
|
|
results.push(arguments[i]);
|
|
|
|
}
|
|
|
|
|
2015-09-03 13:07:35 -07:00
|
|
|
var params = [eventName].concat(results);
|
2015-08-28 09:57:01 -07:00
|
|
|
socket.emit.apply(socket, params);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
socket.on('disconnect', function() {
|
|
|
|
bus.close();
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2015-09-22 08:38:14 -07:00
|
|
|
/**
|
|
|
|
* This method will handle incoming RPC messages to a socket.io connection,
|
|
|
|
* call the appropriate method, and respond with the result.
|
|
|
|
* @param {Object} message - The socket.io "message" object
|
|
|
|
* @param {Function} socketCallback
|
|
|
|
*/
|
2015-08-28 09:57:01 -07:00
|
|
|
WebService.prototype.socketMessageHandler = function(message, socketCallback) {
|
|
|
|
if (this.methodsMap[message.method]) {
|
|
|
|
var params = message.params;
|
|
|
|
|
|
|
|
if(!params || !params.length) {
|
|
|
|
params = [];
|
|
|
|
}
|
|
|
|
|
|
|
|
if(params.length !== this.methodsMap[message.method].args) {
|
|
|
|
return socketCallback({
|
|
|
|
error: {
|
2015-08-28 10:54:29 -07:00
|
|
|
message: 'Expected ' + this.methodsMap[message.method].args + ' parameter(s)'
|
2015-08-28 09:57:01 -07:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
var callback = function(err, result) {
|
|
|
|
var response = {};
|
|
|
|
if(err) {
|
|
|
|
response.error = {
|
|
|
|
message: err.toString()
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
if(result) {
|
|
|
|
response.result = result;
|
|
|
|
}
|
|
|
|
|
|
|
|
socketCallback(response);
|
|
|
|
};
|
|
|
|
|
|
|
|
params = params.concat(callback);
|
|
|
|
this.methodsMap[message.method].fn.apply(this, params);
|
|
|
|
} else {
|
|
|
|
socketCallback({
|
|
|
|
error: {
|
|
|
|
message: 'Method Not Found'
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2015-09-22 08:38:14 -07:00
|
|
|
/**
|
|
|
|
* This method will read `key` and `cert` from disk based on `httpsOptions` and
|
|
|
|
* replace the options with the files.
|
|
|
|
*/
|
2015-09-10 08:06:37 -07:00
|
|
|
WebService.prototype.transformHttpsOptions = function() {
|
|
|
|
if(!this.httpsOptions || !this.httpsOptions.key || !this.httpsOptions.cert) {
|
2015-09-09 13:39:21 -07:00
|
|
|
throw new Error('Missing https options');
|
|
|
|
}
|
|
|
|
|
2015-09-10 08:06:37 -07:00
|
|
|
this.httpsOptions = {
|
|
|
|
key: fs.readFileSync(this.httpsOptions.key),
|
|
|
|
cert: fs.readFileSync(this.httpsOptions.cert)
|
|
|
|
};
|
2015-09-09 13:39:21 -07:00
|
|
|
};
|
|
|
|
|
2015-09-04 12:05:32 -07:00
|
|
|
module.exports = WebService;
|