emperor v0.0.0-alpha.0
Emperor
TL;DR
Emperor is a one of a kind network controller allowing you to build a centrally controlled network of node instances. Scroll down to the end to see a cookbook on some common recipes to use
I highly suggest you check this pdf to see a trivial use case of Emperor
https://www.dropbox.com/s/iwqb48g7zuacg9k/Emperor.pdf?dl=1
- NOTE Some changes have occurred to the API since major updates, but it should still be easy to glean use cases from this document
Installation
npm install --save emperor
//The Master controller on a server somewhere
var emperor=require('emperor').Master;
var emp= new emperor();
emp.open();
//A potential slave on a server or client somewhere
var Slave=require('emperor').Slave;
//$SlaveDriver will be automatically injected into this context
var ctx =global;
var rpc=
{
namespace:
{
hello:function(name)
{
ctx.$SlaveDriver.respond('Hi '+name+' I'm Horus');
}
}
}
var worker = new Slave({id:'Horus',dynamic:true,ctx:ctx});
worker.open();
e.s.Horus.rpc.namespace.hello();
API
Emperor.Master(opt) Constructor
Creates a new Master controller.opt object flags are:
{
port:Integer
debug:Boolean,//Shows more output
out:Stream //Stream to output to , defaults to process.stdout
in:Stream //Stream to provide input, defaults to process.stdin,
ctx:Object// Context for the REPL instance.Defaults to global
noREPL: Boolean//If you are using your own repl already you can set this to true so it doesn't overwrite your current REPL
//You can still interact with the master instance using the .e property
}
If using the standard process.input/output, you should be greeted by a REPL shell to control Emperor. NOTE that in the REPL shell, tab is your friend to prevent too much writing e is the shorthand to reference the MasterInstance, so you shouldn't have multiple MasterInstances running. s is the property to access the masterInstances slaves, so you will frequently be doing
e.s
In your REPL shell.You identify a slave by name
e.s.<SLAVE NAME GOES HERE>
This will access the slave wrapper which provides a number of functions, for our example we will call our slave Conan.We assume Conan has dynamic set to true.Note that all of the functions in the Slave wrapper are chainable
//e.s.Conan.dyn(evalString) Dynamic functions.Used to evaluate string expressions on the slave side
e.s.Conan.dyn('global.a=1;');
// Conan>1
//In the dynamic context, we can directly interact with the $SlaveDriver as its automatically injected
e.s.Conan.dyn('$SlaveDriver.respond("Greetings")');
//Conan>Greetings
//e.s.Conan.addRpc(funcName,func); Add an RPC function on Conan
//In the RPC context, we cannot reference the $SlaveDriver instance directly, so we append $SlaveDriver to the
//end of our argument list. Then when it is executed, it will automatically inject the $SlaveDriver instance slave side
e.s.Conan.addRpc('namespace.test',function(someArg,$SlaveDriver)
{
$SlaveDriver.respond('urgh '+someArg);
});
//e.s.Conan.rpc Object containing callable rpc functions
e.s.Conan.rpc.someNamespace.test('potatoes');
//Conan>"urgh potatoes"
//e.s.Conan.data An object to store data in if needed. This will be cleaned on slave exit
//We can also download files directly from our slaves
e.s.Conan.download('someFile.ext');
e.s.Conan.download('how/about/dir/structure.test');
//Once the slave has responded, we can download the file from the offered files object
//e.s.Conan.offeredFiles An object full of offered files by the slave that we can accept,decline,pause,resume and cancel
//Stream the file into the console
e.s.Conan.offeredFiles.someFile_ext.accept(process.stdout);
//Or maybe download it to a a local file
e.s.Conan.offeredFiles.structure_test.accept('./SomeLocalFile');
//Note that once a file has been completely downloaded, it will be removed from the offeredFiles object
//We can also upload to slaves
e.s.Conan.upload('some/local/path','some/remote/path/to/be/saved','optionalAlias',false);
//e.s.Conan.toUpload.optionalAlias is the network file reader that we can interact with to pause and resume uploads.
// If the optional Alias is not set , then the local path is used .
//The last parameter is a boolean that determines whether we should return the reader directly .
//If true the network reader is returned . This is useful when one is not using the REPL to interact with the master and you want to do this programmatically
//e.s.Conan.isDynamic readonly property to tell if the slave allows dynamic functions
//e.s.Conan.allowsDownload readonly property to tell if the slave allows file downloads
//e.s.Conan.allowsUpload readonly property to tell if the slave allows file uploads
//e.s.Conan.info()//Displays connection info
//e.s.Conan.kill Kills the slave and causes it to shutdown
e.s.Conan.kill();
/====================Broadcasting=================//
//e.broadcastUpload(local,remote) Uploads a file to all slaves
e.broadcastUpload('someLocalFile','NameOfFileIWantOnSlave');
//e.broadcastDyn(evalString) Broadcasts dynamic strings
e.broadcastDyn('console.log($SlaveDriver.respond("Hello");');
//e.broadcastRpc(funcname,varargs) Broadcasts A call to an rpc function
e.broadcastRpc('someNamespace.func',1,4,"43");
//e.broadcastAddRpc(name,func) Adds an rpc call to all connected slaves
e.broadcastAddRpc('testFunc',function($SlaveDriver)
{
var http=require('http');
var httpServer=http.createServer();
var port =~~(Math.random()*1000)+2000;
httpServer.listen(port,function()
{
$SlaveDriver.respond("I have an http Server running on port :"+port)
});
);
MasterInstance.open()
Starts running the Emperor server CHAINABLE
MasterInstance.close()
Closes the server
MasterInstance.numSlaves()
Returns the number of slaves we have working for us through
MasterInstance.e
Serves the same purpose as the e property that would normally be on the command line
MasterInstance Events
- open (masterInstance)
- error (err)
- slave (slaveID,newSlave)
- close (this)
- slaveLost (slaveID,slave)
Slave Events
These events can be listened to via on with a slave instance in the e.s slave pool, or via the newSlave instance emitted on the slave event from the master
- newRPC (RPCName,slave)
- fileOffered (fileName,file,slave)
- fileDownloaded (fileAlias,slave)
- fileUploaded (fileAlias,slave)
- unknownrpc (RPCName,slave)
- fileUploadErr (err,slave)
- uploadUnallowed (slave) You have attempted to upload to a slave that does not allow it
- downloadUnallowed (slave) You have attempted to download from a slave that does not allow it
- addRPCUnallowed (slave) You have attempted to add an RPC on a slave that does not allow it
Emperor.Slave(opt) Constructor
Object opt flags are as follows
{
Id:String //An id to identify the slave to the emperor. If none provided , one will be generated
host:String //The address to the emperor server .Defaults to “localhost”
port:Integer //Defaults to 9999
rpc:Object //An object that contains callable RPC methods that the Emperor Master can use
fileInputRoot // String A path to use relative to which files will be saved if the master tries to upload defaults to ./
fileOutputRoot// String A path to use relative to which files will be retrieved if the master tries to download defaults to ./
allowFileUpload//Boolean Whether or not to allow the master to upload files to the slave Defaults false
allowFileDownload//Boolean Whether or not to allow master to download files to the slave Defaults false
ctx:Object //A context for use by Dynamic calls by the server. Defaults to global
dynamic:Boolean //Whether we allow dynamic injection of code by the Emperor Master . Defaults false
}
SlaveInstance.open()
Starts running the slave
SlaveInstance.respond(AnyData)
Responds to the emperor master instance.
SlaveInstance.addRPC(name,func,namespace)
Adds an RPC callable function for the emperor master instance to use
SlaveInstance Events
- error (err)
- invalidID (slaveInstance) We provided an invalid ID, most likely ID is already in use by another Slave
- close (slaveInstance)
- open (slaveInstance)
- death (slaveInstance) Emperor has requested the slave shut down
CookBook
Note that for the purposes of this cookbook, I have neglected to put in any port or host options unless to prove a point
As a hierachal controller
//Top level controller
var emperor=require('emperor').Master;
var TopEmp= new emperor({in:someStream,out:someStream});
TopEmp.open();
//Mid Level controller
var emperor=require('emperor').Master;
var MidEmp= new emperor({in:someStream,out:someStream});
MidEmp.open();
var slave =require('emperor').Slave;
var MidEmpSlave;
var rpc=
{
killSubSlaves:function()
{
for (var slave in MidEmp.s)
slave.kill();
}
//...
}
MidEmpSlave= new slave({id:'something',rpc:rpc});
MidEmpSlave.open();
//Low level slaves
var lowSlave =new slave();
lowSlave.open();
//EG usage
TopEmp.s.something.rpc.killSubSlaves();
As a centralized logging service
var emperor=require('emperor').Master;
//In this example we will not go through REPL
var emp= new emperor({in:someStream,out:someLogStream});
emp.open();
//On Slave side
var Slave=require('emperor').Slave;
//No ID needed, let it generate its own
var worker1 = new Slave({id:'httpServer'});
var worker2 = new Slave({id:'databaseController'});
worker1.open().on('opened',function()
{
worker1.respond('HTTP requests from ...');
//etc
});
worker2.open().on('opened',function()
{
worker2.respond('Database requests from ...');
//etc
});
/*The log file would look something like
httpServer> ....
databaseController> ....
databaseController> ....
httpServer> ....
databaseController> ....
etc
*/
As a file distributor
We shall upload a file on connection, and once its finish, disconnect the slave
var emperor=require('emperor').Master;
//In this example we will not go through REPL
var emp= new emperor({in:someStream,out:someStream});
emp.open().on('slave',function(newSlave)
{
var uploader=newSlave.upload('update_1.patch','patches/update_1.patch');
uploader.on('finish',function()
{
newSlave.kill();
}).on('error',function()
{
console.log('An error occurred uploading the file');
});
});
//On Slave side
var Slave=require('emperor').Slave;
//No ID needed, let it generate its own
var worker = new Slave({ allowFileUpload:true});
worker.open();