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);
}
};
net.createServer(function (socket) {
var agent = new Agent(api);
agent.connect(socket, function (err, api) {
if (err) return console.error(err.stack);
console.log("A new client connected");
});
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 () {
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;
var agent = new Agent(require('./process-shared-api'));
var child = spawn(process.execPath, [__dirname + "/process-child.js"]);
child.stderr.pipe(process.stderr);
var transport = new Transport(child.stdout, child.stdin);
agent.connect(transport, function (err, api) {
if (err) throw err;
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;
console.log = console.error;
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;
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.