Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

313 lines (222 sloc) 9.287 kb

dnode

DNode is an asynchronous object-oriented RPC system for node.js that lets you call remote functions.

Just write a server:

var dnode = require('dnode');

var server = dnode({
    zing : function (n, cb) { cb(n * 100) }
});
server.listen(7070);

Run it...

$ node server.js

Then you can whip up a client that calls the server's zing function!

var dnode = require('dnode');

dnode.connect(7070, function (remote) {
    remote.zing(33, function (n) {
        console.log('n = ' + n);
    });
});

$ node client.js
n = 3300
^C

When you throw an object at dnode, a recursive traversal scrubs out all of the function objects nested in your data structure and a secondary data structure is sent along with remote requests that creates shim functions that create RPC calls back to the side where the functions were originally defined.

When you call a remote function, the same recursive traversal trick happens to the arguments you pass along, so you can pass callbacks to your remote functions that actually call you back over the wire when the remote side calls the shim function on the other end.

Basically, dnode lets you call remote functions as if they were defined locally without using eval() or Function.prototype.toString(). Awesome!

The only catch is that because the function calls are traveling down the high-latency network, the return values of your functions are ignored. Use continuation-passing style instead!

More features:

  • symmetric design: both sides of the connection can host up methods for the other side to call

  • use tcp streams or websockets courtesy of socket.io! (see below, just throw a webserver at listen())

methods

dnode(wrapper)

If wrapper is an object, serve this object up to the other side every time.

If wrapper is a function, use it to build a new object for each new client. The result of new wrapper(remote, conn) will be used, where remote is an empty object that will be filled with the other side's methods once the initial protocol phase finishes and where conn is the connection object.

Both client and server can call dnode() with a wrapper. dnode.connect() and dnode.listen() are shortcut that set wrapper to {}.

.connect(...)

Connect to a remote dnode service. Pass in a port, host, block, or options object in any order. The block function if present will be executed with the remote object and the connection object once the remote object is ready.

You can reconnect when the connection is refused or drops by passing in a reconnect option as the number of milliseconds to wait between reconnection attempts.

Returns this so you can chain multiple connections.

.listen(...)

Listen for incoming dnode clients. Pass in a port, host, block, or options object in any order. The block function if present will be executed with the remote object and the connection object once the remote object is ready for each client.

If you pass a webserver (http.Server, https.Server, connect, express) to listen(), socket.io will be bound to the webserver and the dnode browser source will be hosted at options.mount || "/dnode.js".

Returns this so you can chain multiple listeners.

.use(middleware)

You can write your own dnode middleware with .use(). The middleware function you pass will be called just like the constructor function that dnode() takes. You can modify this, remote, and conn objects after the instance computed with the dnode() constructor executes but before the methods are sent over the wire.

Returns this so you can chain middlewares.

objects

connection

The connection object (conn) is an EventEmitter.

  • conn.id is a random hex string that uniquely identifies clients

  • conn.end() closes the connection and won't reconnect

  • conn emits 'ready' when the remote object has been fully populated from the methods exchange

  • conn emits 'remote' at the same time as 'ready', except with the remote object as an argument

  • conn emits 'end' when the connection drops

  • conn emits 'connect' when the connection is established

  • conn re-emits error events from the stream object

  • conn emits 'refused', 'drop', and 'reconnect' when reconnect is enabled

more examples!

bidirectional

Both sides of the connection in dnode can host up methods that the other side can call.

Here's an example with some back-and-forth between hosted methods on each side of the connection:

examples/bidirectional/server.js:

var dnode = require('dnode');

dnode(function (client) {
    // Poll the client's own temperature() in celsius and convert that value to
    // fahrenheit in the supplied callback
    this.clientTempF = function (cb) {
        client.temperature(function (degC) {
            var degF = Math.round(degC * 9 / 5 + 32);
            cb(degF);
        });
    }; 
}).listen(6060);

examples/bidirectional/client.js:

var dnode = require('dnode');

var client = dnode({
    // Compute the client's temperature and stuff that value into the callback

    temperature : function (cb) {
        var degC = Math.round(20 + Math.random() * 10 - 5);
        console.log(degC + '° C');
        cb(degC);
    }
});

client.connect(6060, function (remote, conn) {
    // Call the server's conversion routine, which polls the client's
    // temperature in celsius degrees and converts to fahrenheit

    remote.clientTempF(function (degF) {
        console.log(degF + '° F');
        conn.end(); // all done!
    });
});

dnode in the browser!

You can make dnode connections in the browser too! Just pass your webserver to .listen() and you're good to go!

This one uses the built-in node http server:

examples/web-http/server.js:

var http = require('http');
var fs = require('fs');
var dnode = require('dnode');

var index = fs.readFileSync(__dirname + '/index.html');

var server = http.createServer(function (req, res) {
    if (req.url === '/') {
        res.writeHead(200, { 'Content-Type' : 'text/html' });
        res.end(index);
    }
    else {
        res.writeHead(404, { 'Content-Type' : 'text/html' });
        res.end('not found');
    }
});

dnode(function (client) {
    this.cat = function (cb) {
        cb('meow');
    };
}).listen(server);

server.listen(6857);
console.log('http://localhost:6857/');

And then just whip up a quick index.html. The browser-side source is mounted at /dnode.js automatically!

examples/web-http/index.html:

<html>
<head>
<script src="/dnode.js" type="text/javascript"></script>
<script type="text/javascript">
    window.onload = function () {
        DNode.connect(function (remote) {
            remote.cat(function (says) {
                document.getElementById('says').innerHTML = says;
            });
        });
    };
</script>
</head>
<body>
The cat says <span id="says">?</span>.
</body>
</html>

more examples

Check out the examples directory of this distribution.

You'll find examples for using dnode with connect, express, https, and authentication.

There's a chat server too!

installation

Using npm:

npm install dnode

Or check out the repository and link your development copy:

git clone https://github.com/substack/dnode.git
cd dnode && npm link

The dnode dependencies are listed in the package.json. If you install with npm they will be fetched automatically.

read more

protocol

DNode uses a newline-terminated JSON protocol documented in the dnode-protocol readme.

dnode in other languages

These libraries implement the dnode protocol too so you can make RPC calls between scripts written in different languages.

There's a python one in the works too at

Jump to Line
Something went wrong with that request. Please try again.