UNPKG

amino-service

Version:

Decentralized service registry plugin for amino 1.x

150 lines (132 loc) 4.67 kB
var EventEmitter = require('events').EventEmitter , amino = require('amino') , inherits = require('util').inherits , os = require('os') , dns = require('dns') , mefirst = require('mefirst') function Service (amino, name, server, options) { var self = this; EventEmitter.call(this); this.setMaxListeners(0); this.closing = false; this.amino = amino; this.options = amino.utils.copy(options); if (typeof this.options.listen === 'undefined') { this.options.listen = true; } this.server = server; this.spec = new amino.Spec(name); // Get a host/port by configuration and/or discovery. // Run first so existing listeners get access to spec.port. mefirst(server, 'listening', function () { self.spec.port = server.address().port; self.publishSpec(); }); // Attempt to get my address. self.ipAddress(function (err, address) { if (err || !address) { self.emit('error', err || new Error("could not autodetect host! Try setting service.options.host manually.")); return; } self.spec.host = address; if (self.options.listen) { server.listen(self.options.port || 0); } }); server.once('close', this.onClose.bind(this)); // Unpublish our spec if process closes. self.processListeners = { SIGINT: this.onTerminate('SIGINT'), SIGTERM: this.onTerminate('SIGTERM'), uncaughtException: this.onTerminate('uncaughtException') }; Object.keys(this.processListeners).forEach(function (sig) { process.once(sig, self.processListeners[sig]); }); } inherits(Service, EventEmitter); module.exports = Service; // Publish our spec. Service.prototype.publishSpec = function () { var self = this; this.responder = function responder (id) { self.amino.publish('_get:' + self.spec.service + ':' + id, self.spec); }; self.amino.subscribe('_get:' + this.spec.service, this.responder); self.amino.publish('_spec:' + this.spec.service, this.spec); self.emit('listening'); }; // Create a handler for process termination on a certain signal. Service.prototype.onTerminate = function (sig) { var self = this; return function (err) { // Close the server when process is terminated. self.close(function () { var otherListeners = process.listeners(sig); // node 0.8 domains: // if domains are enabled, there will be one listener named 'uncaughtHandler'. // if that listener excecuted first, it will see our listener and think // we'll be the one to handle the exception. so we need to throw the err. if (!otherListeners.length || (otherListeners.length === 1 && otherListeners[0].name === 'uncaughtHandler')) { if (sig === 'uncaughtException') { throw err; } process.exit(); } }); }; }; // Get my IP address. Service.prototype.ipAddress = function (done) { var self = this; if (process.env.NODE_ENV === 'test') { // this is necessary to test on travis-ci because of some network security // changes on their end recently relating to accessing 10.x addresses... self.options.host = '127.0.0.1'; } if (!self.options.host) { dns.lookup(os.hostname(), function (err, address, fam) { if (!err) { self.options.host = address; } done(err, self.options.host); }); } else { done(null, self.options.host); } }; // Close the server and the service. Service.prototype.close = function (cb) { if (cb) this.once('close', cb); // Closing a server will throw an exception if the server is already closed. // Ignore that. try { this.server.close() } catch (e) {}; this.onClose(); }; // Close the service, unpublishing spec and shutting down the server (if we // started one) Service.prototype.onClose = function () { var self = this; if (this.closing) { return; } this.closing = true; if (this.responder) { self.amino.unsubscribe('_get:' + this.spec.service, this.responder); delete this.responder; self.amino.publish('_drop:' + this.spec.service, this.spec); self.amino.publish('_close:' + this.spec.service, this.spec); // If the server is manually closed, we don't want an event listener leak. Object.keys(this.processListeners).forEach(function (k) { // Guard against already removed listeners. Unlike ordinary events // trying to remove a signal listener from process when there are no // listeners for that signal will cause an error to be thrown: // https://github.com/joyent/node/blob/v0.10.28/src/node.js#L779 try { process.removeListener(k, self.processListeners[k]); } catch (e) {} }); } this.emit('close'); };