Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

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