Skip to content
Newer
Older
100644 361 lines (276 sloc) 11.8 KB
cd50a59 @substack pared down the readme
authored
1 dnode
05ad7c4 @substack more examples in the readme
authored
2 =====
3
e565c1c @substack updated the intro and put some function usage before the examples sec…
authored
4 DNode is an object-oriented RPC system for node.js.
0f6fe5f @substack updated readme
authored
5
e565c1c @substack updated the intro and put some function usage before the examples sec…
authored
6 With dnode you call remote methods from a server's exposed functions.
7 Any functions you pass along to remote functions as arguments are automatically
8 wrapped so that the remote end can call you back even if those functions are
9 deeply nested in an object.
0f6fe5f @substack updated readme
authored
10
e565c1c @substack updated the intro and put some function usage before the examples sec…
authored
11 If you've used drb from ruby it's a similar idea.
12 Both drb and dnode are bidirectional so each side of the connection can call
13 methods exposed on the other side.
14 Unlike drb, all dnode methods are asynchronous, so to get the results of remote
15 operations, you supply callbacks.
6f17f01 @substack updated readme with new 0.0.3 browser deploy style and simonw quote
authored
16
e565c1c @substack updated the intro and put some function usage before the examples sec…
authored
17 Also unlike drb, remote methods can return objects with methods of their own
18 which are automatically and recursively wrapped. Since you're not stuck with the
19 methods that the server exposes directly at the dnode object entry-point, you
20 can expose dynamic interfaces with rich object hierarchies to remote clients.
6f17f01 @substack updated readme with new 0.0.3 browser deploy style and simonw quote
authored
21
e565c1c @substack updated the intro and put some function usage before the examples sec…
authored
22 This trick works over plain old tcp sockets or over websockets
23 courtesy of [socket.io](http://github.com/LearnBoost/Socket.IO-node).
24
25 The only catch is that everything is asynchronous, so you've got to write your
26 methods in
cd50a59 @substack pared down the readme
authored
27 [continuation passing style](http://en.wikipedia.org/wiki/Continuation-passing_style).
e565c1c @substack updated the intro and put some function usage before the examples sec…
authored
28 Instead of using `return` like this:
cd50a59 @substack pared down the readme
authored
29 function foo () { return 555 }
30 you call a function passed in as an argument
31 function foo (cb) { cb(555) }
e565c1c @substack updated the intro and put some function usage before the examples sec…
authored
32
33 Using CPS means that you can pass in multiple callbacks embedded arbitrarily
34 in the argument lists to remote functions or no callbacks at all. There are no
35 implicit return callbacks to fret over.
36
37 dnode(object or constructor)
38 ============================
39
40 Just pass `dnode()` an object with functions and attributes you want to expose
41 or a constructor function that creates a new object with functions and
42 attributes for each client. The constructor function will be passed a reference to
43 the remote object and a connection object.
44 `dnode()` returns an object with `listen()` and `connect()` functions described
45 below.
46
47 connection
48 ----------
49
50 The connection object emits 'ready'
51 when the remote object has been fully populated.
52 The connect object has an `id` attribute that uniquely identifies clients.
53
54 connect(port)
55 -------------
56
57 Connect to a remote dnode service. Pass in a port, host, block, or options
58 object in any order. The block function if present will be executed with the
59 remote object and the connection object once the remote object is ready.
60
164f85e @substack note about reconnect
authored
61 To reconnect when the connection drops, specify `reconnect` in the options
62 object as a millisecond delay between reconnection attempts. This is
63 experimental.
64
e565c1c @substack updated the intro and put some function usage before the examples sec…
authored
65 listen(port)
66 ------------
67
68 Listen for incoming dnode clients. Pass in a port, host, block, or options
69 object in any order. The block function if present will be executed with the
70 remote object and the connection object once the remote object is ready for each
71 client.
2d36d00 @substack dependency info in the readme
authored
72
f2d386d @substack bump to 0.5 and readme notes about .use()
authored
73 use(middleware)
74 ---------------
75
76 You can write your own dnode middleware with `.use()`. The `middleware` function
77 you pass will be called just like the constructor function that `dnode()` takes.
78 You can modify `this`, `remote`, and `conn` objects after the instance computed
79 with the `dnode()` constructor executes but before the methods are sent over the
80 wire.
81
05ad7c4 @substack more examples in the readme
authored
82 Examples
83 ========
84
cd50a59 @substack pared down the readme
authored
85 Silly simple thing
86 ------------------
979efc1 @substack very basic .listen() part with a README
authored
87
2a9e1e7 @substack socket.io instructions for installation
authored
88 Server:
89
cd50a59 @substack pared down the readme
authored
90 var dnode = require('dnode');
6b00223 @substack blarg caps fixes
authored
91
cd50a59 @substack pared down the readme
authored
92 dnode({
93 decify : function (n,f) { f(n * 10) }
979efc1 @substack very basic .listen() part with a README
authored
94 }).listen(6060);
2a9e1e7 @substack socket.io instructions for installation
authored
95
96 Client:
97
cd50a59 @substack pared down the readme
authored
98 var dnode = require('dnode');
979efc1 @substack very basic .listen() part with a README
authored
99
6b00223 @substack blarg caps fixes
authored
100 dnode.connect(6060, function (remote) {
cd50a59 @substack pared down the readme
authored
101 remote.decify(5, function (n) {
102 console.log(n); // prints 50, woo!
05ad7c4 @substack more examples in the readme
authored
103 });
104 });
105
cd50a59 @substack pared down the readme
authored
106 Bidirectional
107 -------------
05ad7c4 @substack more examples in the readme
authored
108
cd50a59 @substack pared down the readme
authored
109 Clients and servers aren't special in dnode.
110 Each side of the link can provide methods to the other side.
c8a0d8f @substack bidirectional example does something worthwhile now
authored
111
2a9e1e7 @substack socket.io instructions for installation
authored
112 Server:
113
cd50a59 @substack pared down the readme
authored
114 var dnode = require('dnode');
115 dnode(function (client) {
c8a0d8f @substack bidirectional example does something worthwhile now
authored
116 // Poll the client's own temperature() in celsius and convert that value to
117 // fahrenheit in the supplied callback
118 this.clientTempF = function (cb) {
119 client.temperature(function (degC) {
120 var degF = Math.round(degC * 9 / 5 + 32);
121 cb(degF);
05ad7c4 @substack more examples in the readme
authored
122 });
0f6fe5f @substack updated readme
authored
123 };
05ad7c4 @substack more examples in the readme
authored
124 }).listen(6060);
c8a0d8f @substack bidirectional example does something worthwhile now
authored
125
2a9e1e7 @substack socket.io instructions for installation
authored
126 Client:
c8a0d8f @substack bidirectional example does something worthwhile now
authored
127
cd50a59 @substack pared down the readme
authored
128 var dnode = require('dnode');
129 dnode({
c8a0d8f @substack bidirectional example does something worthwhile now
authored
130 // Compute the client's temperature and stuff that value into the callback
131 temperature : function (cb) {
132 var degC = Math.round(20 + Math.random() * 10 - 5);
133 console.log(degC + '° C');
134 cb(degC);
135 }
5cb33f8 @substack switched the order of dnode and remote in .connect(function (dnode,re…
authored
136 }).connect(6060, function (remote) {
c8a0d8f @substack bidirectional example does something worthwhile now
authored
137 // Call the server's conversion routine, which polls the client's
138 // temperature in celsius degrees and converts to fahrenheit
139 remote.clientTempF(function (degF) {
140 console.log(degF + '° F');
05ad7c4 @substack more examples in the readme
authored
141 });
979efc1 @substack very basic .listen() part with a README
authored
142 });
05ad7c4 @substack more examples in the readme
authored
143
cd50a59 @substack pared down the readme
authored
144 In the Browser
145 --------------
05ad7c4 @substack more examples in the readme
authored
146
cd50a59 @substack pared down the readme
authored
147 You can make dnode connections in the browser too!
f6b3373 @substack note about /dnode.js in the browser example
authored
148
cd50a59 @substack pared down the readme
authored
149 web.js:
05ad7c4 @substack more examples in the readme
authored
150
7e1b24b @substack simplified readme a little and using connect for the web example
authored
151 var connect = require('connect');
cd50a59 @substack pared down the readme
authored
152 var server = connect.createServer();
153 server.use(connect.staticProvider(__dirname));
154
6b00223 @substack blarg caps fixes
authored
155 var dnode = require('dnode');
156 dnode(function (client) {
7e1b24b @substack simplified readme a little and using connect for the web example
authored
157 this.cat = function (cb) {
158 cb('meow');
0f6fe5f @substack updated readme
authored
159 };
7e1b24b @substack simplified readme a little and using connect for the web example
authored
160 }).listen(server);
cd50a59 @substack pared down the readme
authored
161
162 server.listen(6857);
163 console.log('http://localhost:6857/');
7e1b24b @substack simplified readme a little and using connect for the web example
authored
164
cd50a59 @substack pared down the readme
authored
165 index.html:
7e1b24b @substack simplified readme a little and using connect for the web example
authored
166
167 <html>
168 <head>
169 <script src="/dnode.js" type="text/javascript"></script>
170 <script type="text/javascript">
171 window.onload = function () {
172 DNode.connect(function (remote) {
173 remote.cat(function (says) {
174 document.getElementById('says').innerHTML = says;
175 });
176 });
177 };
178 </script>
179 </head>
180 <body>
181 The cat says <span id="says">?</span>.
182 </body>
183 </html>
05ad7c4 @substack more examples in the readme
authored
184
185 Also note that .listen() returns "this", so you can bind multiple listeners to
6b00223 @substack blarg caps fixes
authored
186 the same dnode instance by chaining .listen() calls. This is useful when
05ad7c4 @substack more examples in the readme
authored
187 socket.io clients need to access the same service as regular node.js network
188 sockets.
189
cd50a59 @substack pared down the readme
authored
190 Installation
191 ============
192
193 Using npm:
194
195 npm install dnode
196
197 Or check out the repository and link your development copy:
198
199 git clone http://github.com/substack/dnode.git
200 cd dnode && npm link
201
6b00223 @substack blarg caps fixes
authored
202 dnode depends on
cd50a59 @substack pared down the readme
authored
203 [socket.io](http://github.com/LearnBoost/Socket.IO-node),
204 [traverse](http://github.com/substack/js-traverse),
205 and [lazy](http://github.com/pkrumins/node-lazy),
206 which are all on npm and will be automatically fetched when you `npm install
207 dnode` or `npm link` in the project directory.
208
209 You can also fetch them from github too:
210
211 git clone http://github.com/LearnBoost/Socket.IO-node.git
212 git clone http://github.com/substack/js-traverse.git
213 git clone http://github.com/pkrumins/node-lazy.git
214
2bab437 @substack blog links
authored
215 Read More
216 =========
217
218 [Roll your own PubSub with DNode](http://substack.net/posts/9bac3e/Roll-your-own-PubSub-with-DNode)
219
220 [DNode: Asynchronous Remote Method Invocation for Node.js and the Browser](http://substack.net/posts/85e1bd/DNode-Asynchronous-Remote-Method-Invocation-for-Node-js-and-the-Browser)
221
7646c59 @substack new message protocol description
authored
222 Conventions
223 ===========
224
225 For the most part, when a method supplies a single return value, the callback
226 function should be the method's last argument, like blocks in ruby.
0f6fe5f @substack updated readme
authored
227 Incidentally, this module was inspired by ruby's DRb.
7646c59 @substack new message protocol description
authored
228
3ffeee3 @substack updated error handling in the readme, dnode-ruby link
authored
229 Error Handling
230 ==============
6b00223 @substack blarg caps fixes
authored
231 dnode emits `localError` events through the connection object when an exception
3ffeee3 @substack updated error handling in the readme, dnode-ruby link
authored
232 is thrown on the local side and `remoteError` when the remote side throws an
3184e00 @substack updated readme for the new error printing behavior
authored
233 uncaught exception. It doesn't emit `error` because that would crash the service
234 and that's probably not what you want.
3ffeee3 @substack updated error handling in the readme, dnode-ruby link
authored
235
3184e00 @substack updated readme for the new error printing behavior
authored
236 var client = Dnode({ /* ... */ }).connect(port);
237 client.on('localError', function (err) {
238 console.log('Local Error: ' + err);
239 });
240 client.on('remoteError', function (err) {
241 console.log('Remote Error: ' + err);
242 });
3ffeee3 @substack updated error handling in the readme, dnode-ruby link
authored
243
244 The stack trace is obscured for remoteErrors to avoid leaking sensitive
245 information.
246
3184e00 @substack updated readme for the new error printing behavior
authored
247 By default on nextTick a `localError` is registered that prints a stack trace if
248 no listeners have been bound.
249
05ad7c4 @substack more examples in the readme
authored
250 Protocol
251 ========
252
6b00223 @substack blarg caps fixes
authored
253 dnode uses newline-terminated JSON messages. Each side of the connection may
7646c59 @substack new message protocol description
authored
254 request that a method be invoked on the other side.
05ad7c4 @substack more examples in the readme
authored
255
0f6fe5f @substack updated readme
authored
256 Data Fields
257 -----------
7646c59 @substack new message protocol description
authored
258
259 All messages have this format:
20ab3bf @substack list missing an opening newline
authored
260
7646c59 @substack new message protocol description
authored
261 * method :: String or Integer
05ad7c4 @substack more examples in the readme
authored
262 * arguments :: Array
7646c59 @substack new message protocol description
authored
263 * callbacks :: Object
7e213dc @substack unicode test, note about dnode-perl
authored
264 * links :: Array
7646c59 @substack new message protocol description
authored
265
266 When the method field is a string, it refers to a named method at the remote.
267 When the method field is an integer, it refers to an anonymous function
268 declared in the callbacks field of a previous request.
05ad7c4 @substack more examples in the readme
authored
269
7646c59 @substack new message protocol description
authored
270 The arguments field contains the data to supply the remote method or callback.
f79411b @substack updated callback format in the readme
authored
271 The callbacks field maps an integral callback ID to an Array of elements
272 representing the callback's path in the arguments structure. For instance,
273 an arguments array before transformation of
274 [ 50, 3, { "b" : function () {}, "c" : 4 }, function () {} ]
275 could result in a callback field of
276 { 103 : [ 2, "b" ], 104 : [ 3 ] }
277 if the functions were assigned IDs of 103 and 104 from left to right
278 respectively. Function 103 is in the object at element index 2 and at the key
6f17f01 @substack updated readme with new 0.0.3 browser deploy style and simonw quote
authored
279 "b", so its path is [ 2, "b" ]. Function 104 is just at index 3 in the argument
f79411b @substack updated callback format in the readme
authored
280 field so its path is just [ 3 ].
281
282 The contents of the arguments array at a callback location is not used, so it
283 may contain any value or may be left undefined.
05ad7c4 @substack more examples in the readme
authored
284
7e213dc @substack unicode test, note about dnode-perl
authored
285 The Array and Object fields can be omitted, in which case they default to [] and
286 {}.
287
0f6fe5f @substack updated readme
authored
288 Methods
289 -------
290
291 After the connection is established, each side should send a message with the
0ad7253 @substack updated readme for new methods protocol
authored
292 method field set to "methods". The arguments fields should contain an array with
293 a single element: the object that should be wrapped. The callbacks field is
294 populated from the arguments array given the procedure above.
05ad7c4 @substack more examples in the readme
authored
295
0ad7253 @substack updated readme for new methods protocol
authored
296 Example of this initial methods message:
0f6fe5f @substack updated readme
authored
297 {
298 "method" : "methods",
0ad7253 @substack updated readme for new methods protocol
authored
299 "arguments" : [ { "timesTen" : "[Function]", "moo" : "[Function]" } ],
300 "callbacks" : { "0" : ["0","timesTen"], "1" : ["0","moo"] }
0f6fe5f @substack updated readme
authored
301 }
05ad7c4 @substack more examples in the readme
authored
302
0ad7253 @substack updated readme for new methods protocol
authored
303 Note that the string "[Function]" is just a placeholder and its value is
304 unimportant.
305
306 After methods are exchanged, each side may request methods from the other based
307 on named keys or numeric callback IDs.
de90265 @substack updated readme with protocol linking
authored
308
309 Links
310 -----
311
312 An optional field, "links" supports representing cyclic data structures over
313 JSON. The "links" field is an array of hashes with "from" and "to" keys set. The
314 values of the "from" and "two" keys are array encoding paths through the data
315 structure from the root, as in the "callbacks" field.
316
317 Example of a method call with cyclic references:
318 {
319 "method" : 12,
49e7c26 @substack fixed the readme links section and added an example
authored
320 "arguments" : [ { "a" : 5, "b" : [ { "c" : 5 } ] } ],
de90265 @substack updated readme with protocol linking
authored
321 "callbacks" : {},
322 "links" : [ { "from" : [ 0 ], "to" : [ 0, "b", 1 ] } ]
323 }
324 This example creates a link to the first argument within the first argument's
49e7c26 @substack fixed the readme links section and added an example
authored
325 "b" key's second element. The previous data structure could be generated from
326 the following javascript where `fn` comes from the remote:
327
328 var data = { a : 5, b : [ { c : 5 } ] };
329 data.b.push(data);
330 fn(data);
de90265 @substack updated readme with protocol linking
authored
331
332 Note that links need not necessarily be cyclic, they can just more efficiently
333 encode duplicate data, for instance.
7e213dc @substack unicode test, note about dnode-perl
authored
334
335 Other Languages
336 ===============
337
6b00223 @substack blarg caps fixes
authored
338 These libraries implement the dnode protocol too so you can make RPC calls
7e213dc @substack unicode test, note about dnode-perl
authored
339 between scripts written in different languages.
340
cd50a59 @substack pared down the readme
authored
341 * [dnode-perl](http://github.com/substack/dnode-perl)
342 * [dnode-ruby](http://github.com/substack/dnode-ruby)
343
344 There's a python one in the works too at
345 * [dnode-python](https://github.com/jesusabdullah/dnode-python)
346 but it's not finished yet.
347
348 Press!
349 ======
350
351 [Simon Willison](http://simonwillison.net/2010/Jul/11/dnode/) says:
352
353 > Mind-bendingly clever. DNode lets you expose a JavaScript function so that it
354 > can be called from another machine using a simple JSON-based network protocol.
355 > That’s relatively straight-forward... but DNode is designed for asynchronous
356 > environments, and so also lets you pass callback functions which will be
357 > translated in to references and used to make remote method invocations back to
358 > your original client. And to top it off, there’s a browser client library so
359 > you can perform the same trick over a WebSocket between a browser and a
360 > server.
Something went wrong with that request. Please try again.