Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 310 lines (243 sloc) 10.657 kb
05ad7c4 @substack more examples in the readme
authored
1 DNode
2 =====
3
0f6fe5f @substack updated readme
authored
4 DNode is a node.js library for asynchronous, bidirectional remote method
5 invocation across the network. Transports for network sockets and
20ab3bf @substack list missing an opening newline
authored
6 websocket-style socket.io connections are available.
0f6fe5f @substack updated readme
authored
7
8 A DNode server listens for incoming connections and offers up an object to
dc8da61 @substack some stuff about continuation passing style
authored
9 clients that connect. Clients can call any of the methods that the server hosts
10 and clients can offer their own methods for the server to call.
0f6fe5f @substack updated readme
authored
11
6f17f01 @substack updated readme with new 0.0.3 browser deploy style and simonw quote
authored
12 DNode uses continuation-passing-style to make return values available: the
13 server calls a function supplied to it by the client as an argument. These
14 functions execute on the client side with the arguments provided by the server.
15 Functions may be nested arbitrarily deeply in a method's arguments and can be
16 called multiple times by the server.
17
18 Or [as Simon Willison puts it](http://simonwillison.net/2010/Jul/11/dnode/)
19 (awesomely):
20
21 > Mind-bendingly clever. DNode lets you expose a JavaScript function so that it
22 > can be called from another machine using a simple JSON-based network protocol.
23 > That’s relatively straight-forward... but DNode is designed for asynchronous
24 > environments, and so also lets you pass callback functions which will be
25 > translated in to references and used to make remote method invocations back to
26 > your original client. And to top it off, there’s a browser client library so
27 > you can perform the same trick over a WebSocket between a browser and a
28 > server.
05ad7c4 @substack more examples in the readme
authored
29
fcacd00 @substack installation instructions
authored
30 Installation
31 ============
32
33 Using npm:
2a9e1e7 @substack socket.io instructions for installation
authored
34
fcacd00 @substack installation instructions
authored
35 npm install dnode
36
37 Or check out the repository and link your development copy:
2a9e1e7 @substack socket.io instructions for installation
authored
38
fcacd00 @substack installation instructions
authored
39 git clone http://github.com/substack/dnode.git
40 cd dnode
41 npm link .
2a9e1e7 @substack socket.io instructions for installation
authored
42 git clone http://github.com/LearnBoost/Socket.IO.git lib/vendor/web/Socket.IO
fcacd00 @substack installation instructions
authored
43
a36c118 @substack socket.io dependency mentioned in the readme and in package.json
authored
44 DNode depends on
90b9bf2 @substack fixed link and version 0.0.2
authored
45 [socket.io](http://github.com/LearnBoost/Socket.IO-node),
31f3413 @substack fix broken links in the readme
authored
46 [traverse](http://github.com/substack/js-traverse),
47 and [bufferlist](http://github.com/substack/node-bufferlist),
a36c118 @substack socket.io dependency mentioned in the readme and in package.json
authored
48 which are all on npm and will be automatically fetched by `npm install dnode`.
49 You can also fetch them from github too:
2a9e1e7 @substack socket.io instructions for installation
authored
50
a36c118 @substack socket.io dependency mentioned in the readme and in package.json
authored
51 git clone http://github.com/LearnBoost/Socket.IO-node.git
2d36d00 @substack dependency info in the readme
authored
52 git clone http://github.com/substack/js-traverse.git
53 git clone http://github.com/substack/node-bufferlist.git
54
2a9e1e7 @substack socket.io instructions for installation
authored
55 For the require('dnode/web') stuff to work, you'll need Socket.IO (different
56 from Socket.IO-node), which is what the second `git clone` in the checkout
57 instructions is all about. Submodules are hard.
58
05ad7c4 @substack more examples in the readme
authored
59 Examples
60 ========
61
62 Client and Server
63 -----------------
979efc1 @substack very basic .listen() part with a README
authored
64
2a9e1e7 @substack socket.io instructions for installation
authored
65 Server:
66
fe7baee @substack updated readme for new preferred style too
authored
67 var DNode = require('dnode');
979efc1 @substack very basic .listen() part with a README
authored
68 DNode({
0f6fe5f @substack updated readme
authored
69 timesTen : function (n,f) { f(n * 10) }
979efc1 @substack very basic .listen() part with a README
authored
70 }).listen(6060);
2a9e1e7 @substack socket.io instructions for installation
authored
71
72 Client:
73
74 var DNode = require('dnode');
75 var sys = require('sys');
979efc1 @substack very basic .listen() part with a README
authored
76
5cb33f8 @substack switched the order of dnode and remote in .connect(function (dnode,re…
authored
77 DNode.connect(6060, function (remote) {
05ad7c4 @substack more examples in the readme
authored
78 remote.timesTen(5, function (res) {
79 sys.puts(res); // 50, computation executed on the server
80 });
81 });
82
0f6fe5f @substack updated readme
authored
83 Synchronous Function Example
05ad7c4 @substack more examples in the readme
authored
84 -----------------------------
85
dc8da61 @substack some stuff about continuation passing style
authored
86 The DNode.sync() function adds a callback as the last argument to a function for
87 functions that return explicitly. This callback is called with the return value.
88
2a9e1e7 @substack socket.io instructions for installation
authored
89 Server:
90
fe7baee @substack updated readme for new preferred style too
authored
91 var DNode = require('dnode');
05ad7c4 @substack more examples in the readme
authored
92 DNode({
0f6fe5f @substack updated readme
authored
93 timesTen : DNode.sync(function (n) {
94 return n * 10;
05ad7c4 @substack more examples in the readme
authored
95 })
96 }).listen(6060);
97
98 This code is functionally equivalent to the server code in the previous example.
99
100 Bidirectional Communication Example
101 -----------------------------------
102
103 DNode clients are only clients in the sense that they initiate the connection.
104 Clients can provide methods for the remote server to call just as the remote
105 server provides methods for the client to call. The server can get at the
106 client's methods by passing a constructor to DNode() that will be passed the
107 client handle as the first argument.
c8a0d8f @substack bidirectional example does something worthwhile now
authored
108
2a9e1e7 @substack socket.io instructions for installation
authored
109 Server:
110
fe7baee @substack updated readme for new preferred style too
authored
111 var DNode = require('dnode');
05ad7c4 @substack more examples in the readme
authored
112 DNode(function (client) {
c8a0d8f @substack bidirectional example does something worthwhile now
authored
113 // Poll the client's own temperature() in celsius and convert that value to
114 // fahrenheit in the supplied callback
115 this.clientTempF = function (cb) {
116 client.temperature(function (degC) {
117 var degF = Math.round(degC * 9 / 5 + 32);
118 cb(degF);
05ad7c4 @substack more examples in the readme
authored
119 });
0f6fe5f @substack updated readme
authored
120 };
05ad7c4 @substack more examples in the readme
authored
121 }).listen(6060);
c8a0d8f @substack bidirectional example does something worthwhile now
authored
122
2a9e1e7 @substack socket.io instructions for installation
authored
123 Client:
c8a0d8f @substack bidirectional example does something worthwhile now
authored
124
05ad7c4 @substack more examples in the readme
authored
125 DNode({
c8a0d8f @substack bidirectional example does something worthwhile now
authored
126 // Compute the client's temperature and stuff that value into the callback
127 temperature : function (cb) {
128 var degC = Math.round(20 + Math.random() * 10 - 5);
129 console.log(degC + '° C');
130 cb(degC);
131 }
5cb33f8 @substack switched the order of dnode and remote in .connect(function (dnode,re…
authored
132 }).connect(6060, function (remote) {
c8a0d8f @substack bidirectional example does something worthwhile now
authored
133 // Call the server's conversion routine, which polls the client's
134 // temperature in celsius degrees and converts to fahrenheit
135 remote.clientTempF(function (degF) {
136 console.log(degF + '° F');
05ad7c4 @substack more examples in the readme
authored
137 });
979efc1 @substack very basic .listen() part with a README
authored
138 });
05ad7c4 @substack more examples in the readme
authored
139
140 Bidirectional Browser Example
141 -----------------------------
142
6f17f01 @substack updated readme with new 0.0.3 browser deploy style and simonw quote
authored
143 DNode's browser-based interface works just like the node.js version.
144 To make DNode easier to deploy, all the necessary browser-side code
145 including [Socket.io](http://github.com/LearnBoost/Socket.IO)
146 is available by calling `require('dnode/web').source()` on the server-side.
05ad7c4 @substack more examples in the readme
authored
147
6f17f01 @substack updated readme with new 0.0.3 browser deploy style and simonw quote
authored
148 Here's a complete web example:
05ad7c4 @substack more examples in the readme
authored
149
150 ### web.html
151
0f6fe5f @substack updated readme
authored
152 <script type="text/javascript" src="/dnode.js"></script>
05ad7c4 @substack more examples in the readme
authored
153 <script type="text/javascript">
154 DNode({
0f6fe5f @substack updated readme
authored
155 name : function (f) { f('Mr. Spock') },
5cb33f8 @substack switched the order of dnode and remote in .connect(function (dnode,re…
authored
156 }).connect(function (remote) {
05ad7c4 @substack more examples in the readme
authored
157 remote.timesTen(10, function (n) {
158 document.getElementById("result").innerHTML = String(n);
159 });
160 remote.whoAmI(function (name) {
161 document.getElementById("name").innerHTML = name;
162 });
163 });
164 </script>
0f6fe5f @substack updated readme
authored
165
05ad7c4 @substack more examples in the readme
authored
166 <p>timesTen(10) == <span id="result">?</span></p>
167 <p>My name is <span id="name">?</span>.</p>
168
169 ### web.js
0f6fe5f @substack updated readme
authored
170
fe7baee @substack updated readme for new preferred style too
authored
171 var DNode = require('dnode');
05ad7c4 @substack more examples in the readme
authored
172 var sys = require('sys');
173 var fs = require('fs');
174 var http = require('http');
6f17f01 @substack updated readme with new 0.0.3 browser deploy style and simonw quote
authored
175
176 // load the html page and the client-side javascript into memory
05ad7c4 @substack more examples in the readme
authored
177 var html = fs.readFileSync(__dirname + '/web.html');
6f17f01 @substack updated readme with new 0.0.3 browser deploy style and simonw quote
authored
178 var js = require('dnode/web').source();
179
180 // simple http server to serve pages and for socket.io transport
05ad7c4 @substack more examples in the readme
authored
181 var httpServer = http.createServer(function (req,res) {
0f6fe5f @substack updated readme
authored
182 if (req.url == '/dnode.js') {
05ad7c4 @substack more examples in the readme
authored
183 res.writeHead(200, { 'Content-Type' : 'text/javascript' });
0f6fe5f @substack updated readme
authored
184 res.end(js);
05ad7c4 @substack more examples in the readme
authored
185 }
186 else {
187 res.writeHead(200, { 'Content-Type' : 'text/html' });
188 res.end(html);
189 }
190 });
191 httpServer.listen(6061);
6f17f01 @substack updated readme with new 0.0.3 browser deploy style and simonw quote
authored
192
193 // share an object with DNode over socket.io on top of the http server
05ad7c4 @substack more examples in the readme
authored
194 DNode(function (client) {
0f6fe5f @substack updated readme
authored
195 this.timesTen = function (n,f) { f(n * 10) };
196 this.whoAmI = function (reply) {
05ad7c4 @substack more examples in the readme
authored
197 client.name(function (name) {
0f6fe5f @substack updated readme
authored
198 reply(name
05ad7c4 @substack more examples in the readme
authored
199 .replace(/Mr\.?/,'Mister')
200 .replace(/Ms\.?/,'Miss')
201 .replace(/Mrs\.?/,'Misses')
202 );
203 })
0f6fe5f @substack updated readme
authored
204 };
05ad7c4 @substack more examples in the readme
authored
205 }).listen({
206 protocol : 'socket.io',
207 server : httpServer,
208 transports : 'websocket xhr-multipart xhr-polling htmlfile'.split(/\s+/),
0f6fe5f @substack updated readme
authored
209 });
05ad7c4 @substack more examples in the readme
authored
210
211 Also note that .listen() returns "this", so you can bind multiple listeners to
212 the same DNode instance by chaining .listen() calls. This is useful when
213 socket.io clients need to access the same service as regular node.js network
214 sockets.
215
7646c59 @substack new message protocol description
authored
216 Conventions
217 ===========
218
219 For the most part, when a method supplies a single return value, the callback
220 function should be the method's last argument, like blocks in ruby.
0f6fe5f @substack updated readme
authored
221 Incidentally, this module was inspired by ruby's DRb.
7646c59 @substack new message protocol description
authored
222
05ad7c4 @substack more examples in the readme
authored
223 Protocol
224 ========
225
7646c59 @substack new message protocol description
authored
226 DNode uses newline-terminated JSON messages. Each side of the connection may
227 request that a method be invoked on the other side.
05ad7c4 @substack more examples in the readme
authored
228
0f6fe5f @substack updated readme
authored
229 Data Fields
230 -----------
7646c59 @substack new message protocol description
authored
231
232 All messages have this format:
20ab3bf @substack list missing an opening newline
authored
233
7646c59 @substack new message protocol description
authored
234 * method :: String or Integer
05ad7c4 @substack more examples in the readme
authored
235 * arguments :: Array
7646c59 @substack new message protocol description
authored
236 * callbacks :: Object
7e213dc @substack unicode test, note about dnode-perl
authored
237 * links :: Array
7646c59 @substack new message protocol description
authored
238
239 When the method field is a string, it refers to a named method at the remote.
240 When the method field is an integer, it refers to an anonymous function
241 declared in the callbacks field of a previous request.
05ad7c4 @substack more examples in the readme
authored
242
7646c59 @substack new message protocol description
authored
243 The arguments field contains the data to supply the remote method or callback.
f79411b @substack updated callback format in the readme
authored
244 The callbacks field maps an integral callback ID to an Array of elements
245 representing the callback's path in the arguments structure. For instance,
246 an arguments array before transformation of
247 [ 50, 3, { "b" : function () {}, "c" : 4 }, function () {} ]
248 could result in a callback field of
249 { 103 : [ 2, "b" ], 104 : [ 3 ] }
250 if the functions were assigned IDs of 103 and 104 from left to right
251 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
252 "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
253 field so its path is just [ 3 ].
254
255 The contents of the arguments array at a callback location is not used, so it
256 may contain any value or may be left undefined.
05ad7c4 @substack more examples in the readme
authored
257
7e213dc @substack unicode test, note about dnode-perl
authored
258 The Array and Object fields can be omitted, in which case they default to [] and
259 {}.
260
0f6fe5f @substack updated readme
authored
261 Methods
262 -------
263
264 After the connection is established, each side should send a message with the
0ad7253 @substack updated readme for new methods protocol
authored
265 method field set to "methods". The arguments fields should contain an array with
266 a single element: the object that should be wrapped. The callbacks field is
267 populated from the arguments array given the procedure above.
05ad7c4 @substack more examples in the readme
authored
268
0ad7253 @substack updated readme for new methods protocol
authored
269 Example of this initial methods message:
0f6fe5f @substack updated readme
authored
270 {
271 "method" : "methods",
0ad7253 @substack updated readme for new methods protocol
authored
272 "arguments" : [ { "timesTen" : "[Function]", "moo" : "[Function]" } ],
273 "callbacks" : { "0" : ["0","timesTen"], "1" : ["0","moo"] }
0f6fe5f @substack updated readme
authored
274 }
05ad7c4 @substack more examples in the readme
authored
275
0ad7253 @substack updated readme for new methods protocol
authored
276 Note that the string "[Function]" is just a placeholder and its value is
277 unimportant.
278
279 After methods are exchanged, each side may request methods from the other based
280 on named keys or numeric callback IDs.
de90265 @substack updated readme with protocol linking
authored
281
282 Links
283 -----
284
285 An optional field, "links" supports representing cyclic data structures over
286 JSON. The "links" field is an array of hashes with "from" and "to" keys set. The
287 values of the "from" and "two" keys are array encoding paths through the data
288 structure from the root, as in the "callbacks" field.
289
290 Example of a method call with cyclic references:
291 {
292 "method" : 12,
293 "arguments" : [ { "a" : 5, "b" : [ { "c" : 5 } ] ],
294 "callbacks" : {},
295 "links" : [ { "from" : [ 0 ], "to" : [ 0, "b", 1 ] } ]
296 }
297 This example creates a link to the first argument within the first argument's
298 "b" key's second element.
299
300 Note that links need not necessarily be cyclic, they can just more efficiently
301 encode duplicate data, for instance.
7e213dc @substack unicode test, note about dnode-perl
authored
302
303 Other Languages
304 ===============
305
306 These libraries implement the DNode protocol too so you can make RPC calls
307 between scripts written in different languages.
308
309 * [dnode-perl](http://github.com/substack/dnode-perl) (experimental)
Something went wrong with that request. Please try again.