README
Smith
Smith is an RPC agent system for Node.JS used in architect and vfs.
Usage
Smith can be used in any situation where you have a duplex node stream. This can be over tcp, stdio, a pipe, or anything that sends bytes back and forth.
TCP client-server example
In this example, I have a TCP server that serves an add function to any agent clients who want to consume the service.
For the server, we create a small agent and serve it on a listening tcp port.
var net = require('net');
var Agent = require('smith').Agent;
var api = {
add: function (a, b, callback) {
callback(null, a + b);
}
};
// Start a TCP server
net.createServer(function (socket) {
// Create the agent that serves the shared api.
var agent = new Agent(api);
// Connect to the remote agent
agent.connect(socket, function (err, api) {
if (err) return console.error(err.stack);
console.log("A new client connected");
});
// Log when the agent disconnects
agent.on("disconnect", function (err) {
console.error("The client disconnected")
if (err) console.error(err.stack);
});
}).listen(1337, function () {
console.log("Agent server listening on port 1337");
});
Then to consume this TCP service, we can create an agent and connect it to the tcp server.
var net = require('net');
var Agent = require('smith').Agent;
var socket = net.connect(1337, function () {
// Create our client
var agent = new Agent()
agent.connect(socket, function (err, api) {
api.add(4, 5, function (err, result) {
if (err) throw err;
console.log("4 + 5 = %s", result);
agent.disconnect();
});
});
});
For an example of how to reconnect if the connection goes down, see https://github.com/c9/smith/blob/master/samples/tcp-client-autoreconnect.js
STDIO Parent-Child Example
Here we create a node process that spawns a child process, and the two talk to eachother calling functions both directions.
Both share a simple API library.
exports.ping = function (callback) {
callback(null, process.pid + " pong");
}
The parent creates an Agent,spawns the child, and connects.
var spawn = require('child_process').spawn;
var Agent = require('smith').Agent;
var Transport = require('smith').Transport;
// Create an agent instance using the shared API
var agent = new Agent(require('./process-shared-api'));
// Spawn the child process that runs the other half.
var child = spawn(process.execPath, [__dirname + "/process-child.js"]);
// Forward the child's console output
child.stderr.pipe(process.stderr);
var transport = new Transport(child.stdout, child.stdin);
agent.connect(transport, function (err, api) {
if (err) throw err;
// Call the child's API in a loop
function loop() {
api.ping(function (err, message) {
if (err) throw err;
console.log("Child says %s", message);
})
setTimeout(loop, Math.random() * 1000);
}
loop();
});
The child resumes stdin, creates an Agent, and connects.
var Agent = require('smith').Agent;
var Transport = require('smith').Transport;
// Redirect logs to stderr since stdout is used for data
console.log = console.error;
// Start listening on stdin for smith rpc data.
process.stdin.resume();
var agent = new Agent(require('./process-shared-api'));
var transport = new Transport(process.stdin, process.stdout);
agent.connect(transport, function (err, api) {
if (err) throw err;
// Call the parent's API in a loop
function loop() {
api.ping(function (err, message) {
if (err) throw err;
console.log("Got %s from parent", message);
})
setTimeout(loop, Math.random() * 1000);
}
loop();
});
Class: Agent
Agent is the main class used in smith. It represents an agent in your mesh network. It provides a set of service functions exposed as async functions.
new Agent(api)
Create a new Agent instance that serves the functions listed in api
.
agent.api
The functions this agent serves locally to remote agents.
agent.remoteApi
A object containing proxy functions for the api functions in the remote agent. Calling these functions when the remote is offline will result in the last argument being called with a ENOTCONNECTED error (assuming it's a function).
agent.connectionTimeout
If the connection hasn't happened by 10,000 ms, an ETIMEDOUT error will
happen. To change the timeoutvalue, change connectionTimeout
on either the
instance or the prototype. Set to zero to disable.
Event: 'connect'
function (remoteApi) { }
When the rpc handshake is complete, the agent will emit a connect event containing the remoteApi.
Event: 'disconnect'
function () { }
Emitted when the transport dies and the remote becomes offline
Event: 'drain'
When the writable stream in the transport emits drain, it's forwarded here
agent.connect(transport, [callback]))
Start the connection to a new remote agent using transport
. Emits connect
when
ready or error
on failure. Optionally use the callback to get (err, api, agent)
results.
The transport
argument is either a Transport instance or a duplex Stream.
The callback will be called with (err, remoteApi)
.
agent.disconnect(err)
Tell the agent to disconnect from the transport with optional error reason err
.
agent.send(message)
Encode a message and send it on the transport. Used internally to send function calls. Returns false if the kernel buffer is full.
Class: Transport
Transport is a wrapper around a duplex socket to allow two Agent instances to
talk to eachother. A transport will shut down itself if either end of the
socket ends and emit an error
event.
new Transport(input, [output])
Pass in either a duplex Stream instance or two streams (one readable, one writable). This transport object can then be used to connect to another Agent.
Event: 'message'
function (message) { }
Emitted when a message arrives from the remote end of the transport.ts
Event: 'drain'
function () { }
Emitted when the writable stream emits drain. (The write buffer is empty.)
Event: 'disconnect'
function (err) { }
Emitted when the transport dies. If this was caused by an error, it will be emitted here.
transport.send(message)
Send a message to the other end of the transport. Message is JSON
serializable object with the addition of being able to serialize node Buffer
instances and undefined
values. Returns true if the kernel buffer is full
and you should pause your incoming stream.