diff --git a/.travis.yml b/.travis.yml index f1d0f13..2d26206 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,3 @@ language: node_js node_js: - - 0.4 - 0.6 diff --git a/README.markdown b/README.markdown index 51959fe..174bd97 100644 --- a/README.markdown +++ b/README.markdown @@ -3,293 +3,292 @@ dnode ![dnode: freestyle rpc](http://substack.net/images/dnode.png) -DNode is an asynchronous object-oriented RPC system for node.js that lets you +dnode is an asynchronous rpc system for node.js that lets you call remote functions. -[![build status](https://secure.travis-ci.org/substack/dnode.png)](http://travis-ci.org/substack/dnode) +You can pass callbacks to remote functions, and the remote end can call +the functions you passed in with callbacks of its own and so on. +It's callbacks all the way down! -It works over network sockets and even in the browser with -[socket.io](https://github.com/LearnBoost/Socket.IO). +[![build status](https://secure.travis-ci.org/substack/dnode.png)](http://travis-ci.org/substack/dnode) -Plus, there are dnode implementations for -[perl](http://github.com/substack/dnode-perl), -[ruby](http://github.com/substack/dnode-ruby), -[php](https://github.com/bergie/dnode-php), -and -[java](https://github.com/aslakhellesoy/dnode-java), -so you can glue -together all your backend processes. +example +======= -dnode between two node.js processes ------------------------------------ +listen and connect +------------------ -Just write a server.js: +server: -```javascript +``` js var dnode = require('dnode'); - var server = dnode({ - zing : function (n, cb) { cb(n * 100) } + transform : function (s, cb) { + cb(s.replace(/[aeiou]{2,}/, 'oo').toUpperCase()) + } }); -server.listen(5050); -```` - -Run it... - - $ node server.js +server.listen(5004); +``` -Then you can whip up a client.js that calls the server's `zing` function! +client: -```javascript +``` js var dnode = require('dnode'); -dnode.connect(5050, function (remote) { - remote.zing(66, function (n) { - console.log('n = ' + n); +var d = dnode.connect(5004); +d.on('remote', function (remote) { + remote.transform('beep', function (s) { + console.log('beep => ' + s); + d.end(); }); }); -```` +``` -*** +output: - $ node client.js - n = 6600 - ^C +``` +$ node server.js & +[1] 27574 +$ node client.js +beep => BOOP +``` -dnode on the browser --------------------- - -We can retrofit the previous example to run in the browser. - -Just write a server.js: +streaming +--------- -````javascript -var express = require('express'); -var app = express.createServer(); -app.use(express.static(__dirname)); +The `.connect()` and `.listen()` calls in the previous example are just +convenience methods for piping to and from readable/writable streams. +Here's the previous example with the streams set up explicitly: -app.listen(8080); -console.log('http://localhost:8080/'); - -// then just pass the server app handle to .listen()! +server: +``` js var dnode = require('dnode'); -var server = dnode({ - zing : function (n, cb) { cb(n * 100) } -}); -server.listen(app); -```` - -and whip up an index.html: - -````html - - - - - - +var net = require('net'); -n = ? +var server = net.createServer(function (c) { + var d = dnode({ + transform : function (s, cb) { + cb(s.replace(/[aeiou]{2,}/, 'oo').toUpperCase()) + } + }); + c.pipe(d).pipe(c); +}); - - -```` +server.listen(5004); +``` -then just run the server.js: +client: - $ node server.js - http://localhost:8080/ +``` js +var dnode = require('dnode'); +var net = require('net'); -and navigate to http://localhost:8080: +var d = dnode(); +d.on('remote', function (remote) { + remote.transform('beep', function (s) { + console.log('beep => ' + s); + d.end(); + }); +}); -![dnode in the browser](http://substack.net/images/dnode-slides/browser.png) +var c = net.connect(5004); +c.pipe(d).pipe(c); +``` -Awesome it works! +output: -The dnode browser source automatically gets hosted at `/dnode.js` and it also -works with -[browserify](https://github.com/substack/node-browserify) -[out of the box](https://github.com/substack/dnode/tree/master/examples/web-browserify). +``` +$ node server.js & +[1] 27586 +$ node client.js +beep => BOOP +``` -how it works ------------- +dnode in the browser +-------------------- -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. +Since dnode instances are just readable/writable streams, you can use them with +any streaming transport, including in the browser! -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. +This example uses the streaming interface provided by +[shoe](https://github.com/substack/shoe), which is just a thin wrapper on top of +[sockjs](http://sockjs.org/) that provides websockets with fallbacks. -Basically, dnode lets you call remote functions as if they were defined locally -without using `eval()` or `Function.prototype.toString()`. Awesome! +First whip up a server: -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](http://en.wikipedia.org/wiki/Continuation-passing_style) instead! +``` js +var http = require('http'); +var shoe = require('shoe'); +var ecstatic = require('ecstatic')(__dirname + '/static'); +var dnode = require('dnode'); -More features: +var server = http.createServer(ecstatic); +server.listen(9999); -* symmetric design: both sides of the connection can host up methods for the - other side to call +var sock = shoe(function (stream) { + var d = dnode({ + transform : function (s, cb) { + var res = s.replace(/[aeiou]{2,}/, 'oo').toUpperCase(); + cb(res); + } + }); + d.pipe(stream).pipe(d); +}); +sock.install(server, '/dnode'); +``` -* use TCP streams, UNIX domain sockets, or websockets courtesy of socket.io! - (see below, just throw a webserver at `listen()`) +Then write some browser code: -methods -======= +``` js +var domready = require('domready'); +var shoe = require('shoe'); +var dnode = require('dnode'); -dnode(wrapper) --------------- +domready(function () { + var result = document.getElementById('result'); + var stream = shoe('/dnode'); + + var d = dnode(); + d.on('remote', function (remote) { + remote.transform('beep', function (s) { + result.textContent = 'beep => ' + s; + }); + }); + d.pipe(stream).pipe(d); +}); +``` -If `wrapper` is an object, serve this object up to the other side every time. +Install the dependencies for this example then compile the browser code with +[browserify](https://github.com/substack/node-browserify): -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. +``` +$ npm install dnode shoe domready ecstatic +$ npm install -g browserify +$ browserify client.js -o static/bundle.js +``` -Both client and server can call `dnode()` with a wrapper. -`dnode.connect()` and `dnode.listen()` are shortcut that set `wrapper` to `{}`. +Now drop a script tag into static/index.html: -.connect(...) -------------- +``` html + +
+``` -Connect to a remote dnode service. Pass in a port, host, UNIX domain socket -path, 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. +and navigate to http://localhost:9999. +You should see `beep => BOOP` on the page! -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. +Check out the +[complete shoe example](https://github.com/substack/dnode/tree/master/example/shoe). -Returns `this` so you can chain multiple connections. +methods +======= -.listen(...) ------------- +``` js +var dnode = require('dnode') +``` -Listen for incoming dnode clients. Pass in a port, host, UNIX domain socket -path, 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. +var d = dnode(cons, opts={}) +---------------------------- -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"`. +Create a new readable/writable dnode stream object `d`. +All the usual stream methods are at your disposal: pipe(), write(), end(). -You can pass options through to socket.io with the `io` parameter: +If `cons` is a function, it will be called `new cons(remote, d)` to create a new +instance object. Otherwise its value will be used directly. When `cons` is +called as a function, the `remote` ref will be an empty unpopulated object. -````javascript -dnode(...).listen(webserver, { io : { flashPolicyServer : false } }); -```` +By default, dnode uses weakmaps to garbage collect unused callbacks +automatically. This behavior prevents memory leaks in long-running connections. -Returns `this` so you can chain multiple listeners. +You can turn weakmaps off by setting `opts.weak = false`. -.use(middleware) ----------------- +d.connect(...) +-------------- -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. +This method is a shortcut for setting up a pipe between `d` and a new +`net.connect()` stream. -Returns `this` so you can chain middlewares. +The host, port, and callback arguments supplied will be inferred by their +types. -the connection object -===================== +If you pass a callback in as an argument, it will be added as a listener to the +`'remote'` event. -When you pass a constructor function to `dnode()` you'll get a connection -object as the second argument to your constructor. +Returns the `d` object. -The connection object (`conn`) is an EventEmitter. +dnode.connect(...) +------------------ -* conn.id is a random hex string that uniquely identifies clients +Shortcut to create a connection without a constructor. -* conn.end() closes the connection and won't reconnect +d.listen(...) +------------- -* conn emits 'ready' when the remote object has been fully populated from - the methods exchange +This method is a shortcut for setting up a `net.createServer()` and piping +network streams to and from new dnode streams. -* conn emits 'remote' at the same time as 'ready', except with the remote object - as an argument +The host, port, and callback parameters will be inferred from the types of the +arguments. -* conn emits 'end' when the connection drops +Returns a net server object that will also emit `'local'` and `'remote'` events +from the underlying dnode streams.. -* conn emits 'connect' when the connection is established +dnode.listen(...) +----------------- -* conn re-emits error events from the stream object +Shortcut to create a listener without a constructor. -* conn emits 'refused', 'drop', and 'reconnect' when reconnect is enabled +events +====== -more examples -------------- +d.on('remote', cb) +------------------ -Check out -[the examples directory](https://github.com/substack/dnode/tree/master/examples/) -of this distribution. +This event fires with `cb(remote, d)` when the remote side of the connection +has constructed its instance. -You'll find examples for using dnode with -[connect](https://github.com/SenchaLabs/connect), -[express](http://expressjs.com/), -[https](https://github.com/substack/dnode/tree/master/examples/https), -and authentication. +d.on('local', cb) +----------------- -There's a chat server too! +This event fires right after the constructed instance has been created locally +but before it gets sent to the remote end so you can modify the ref object. -installation -============ +This event fires with `cb(ref, d)` where `ref` is the local instance object. -Using [npm](http://npmjs.org): +d.on('fail', cb) +---------------- - npm install dnode +This event fires when the remote end causes errors in the protocol layer. -Or check out the repository and fetch the deps with npm, then build the bundle: +These are non-fatal and can probably be ignored but you could also terminate the +connection here. - git clone https://github.com/substack/dnode.git - cd dnode - npm install --dev - node bin/bundle.js +d.on('error', cb) +----------------- -The dnode dependencies are listed in the -[package.json](https://github.com/substack/dnode/tree/master/package.json). -If you install with npm they will be fetched automatically. +This event fires when local code causes errors in its callbacks. +Not all errors can be caught here since some might be in async functions. -read more -========= +d.on('end', cb) +--------------- -* [slides from my dnode talk at parisoma](http://substack.net/posts/9aabb1) +This event fires when the input stream finishes. -* [Roll your own PubSub with DNode](http://substack.net/posts/9bac3e/Roll-your-own-PubSub-with-DNode) - (Note: EventEmitters are no longer exported directly, use - [browserify](https://github.com/substack/node-browserify) to get them back) +install +======= -* [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) +With [npm](http://npmjs.org) do: -* [Simon Willison's Weblog](http://simonwillison.net/2010/Jul/11/dnode/) +``` +npm install dnode +``` protocol ======== -DNode uses a newline-terminated JSON protocol -[documented in the dnode-protocol -readme](https://github.com/substack/dnode-protocol). +dnode uses a newline-terminated JSON protocol +[documented in the dnode-protocol project](https://github.com/substack/dnode-protocol/blob/master/doc/protocol.markdown#the-protocol). dnode in other languages ======================== @@ -300,12 +299,9 @@ between scripts written in different languages. * [dnode-perl](http://github.com/substack/dnode-perl) * [dnode-ruby](http://github.com/substack/dnode-ruby) * [dnode-php](https://github.com/bergie/dnode-php) +* [dnode-php-sync-client](https://github.com/erasys/dnode-php-sync-client) * [dnode-java](https://github.com/aslakhellesoy/dnode-java) -There's a -[dnode-python](https://github.com/jesusabdullah/dnode-python) -in the works too but it's not finished yet. - shameless plug ============== @@ -313,6 +309,3 @@ Want to make sure your crazy javascript-heavy app still works in other browsers? Give [browserling](http://browserling.com) a spin! Browsers in your browser. Powered by dnode. - -[![build status](https://secure.travis-ci.org/substack/dnode.png)](http://travis-ci.org/substack/dnode) - diff --git a/bin/bundle.js b/bin/bundle.js deleted file mode 100755 index dbd90f5..0000000 --- a/bin/bundle.js +++ /dev/null @@ -1,21 +0,0 @@ -var fs = require('fs'); -var path = require('path'); -var browserify = require('browserify'); - -var src = browserify({ filter : require('uglify-js') }) - .require(__dirname + '/../browser/index.js') - .bundle() -; - -var buf = new Buffer('var DNode = (function () {' - + src + '; return require("/index.js")' -+ '})()'); - -var file = __dirname + '/../browser/bundle.js'; -fs.writeFile(file, buf, function (err) { - if (err) console.error('Error: ' + err) - else { - var bytes = buf.length; - console.log(bytes + ' bytes written'); - } -}); diff --git a/browser.js b/browser.js new file mode 100644 index 0000000..e0e5de7 --- /dev/null +++ b/browser.js @@ -0,0 +1,5 @@ +var dnode = require('./lib/dnode'); + +module.exports = function (cons, opts) { + return new dnode(cons, opts); +}; diff --git a/browser/index.js b/browser/index.js deleted file mode 100644 index 84cac3a..0000000 --- a/browser/index.js +++ /dev/null @@ -1,69 +0,0 @@ -var protocol = require('dnode-protocol'); -var EventEmitter = require('events').EventEmitter; -var io = require('socket.io-client'); -var json = typeof JSON === 'object' ? JSON : require('jsonify'); - -var exports = module.exports = dnode; - -function dnode (wrapper) { - if (!(this instanceof dnode)) return new dnode(wrapper); - this.proto = protocol(wrapper); - this.stack = []; - return this; -} - -dnode.prototype = new EventEmitter; - -dnode.prototype.use = function (middleware) { - this.stack.push(middleware); - return this; -}; - -dnode.prototype.connect = function () { - var self = this; - var params = protocol.parseArgs(arguments); - var client = self.proto.create(); - - var proto = (params.proto || window.location.protocol) - .replace(/:.*/, '') + '://'; - - var sock = client.socketio = io.connect( - proto + (params.host || window.location.host), - params - ); - - client.end = function () { - sock.disconnect(); - }; - - sock.on('connect', function () { - client.start(); - self.emit('connect'); - }); - - sock.on('disconnect', function () { - client.emit('end'); - self.emit('end'); - }); - - sock.on('message', client.parse); - - client.on('request', function (req) { - sock.send(json.stringify(req) + '\n'); - }); - - if (params.block) { - client.on('remote', function () { - params.block.call(client.instance, client.remote, client); - }); - } - - this.stack.forEach(function (middleware) { - middleware.call(client.instance, client.remote, client); - }); -}; - -exports.connect = function () { - var d = exports(); - return d.connect.apply(d, arguments); -}; diff --git a/example/auth/client.js b/example/auth/client.js new file mode 100644 index 0000000..5459a13 --- /dev/null +++ b/example/auth/client.js @@ -0,0 +1,24 @@ +var dnode = require('dnode'); + +if (process.argv.length < 4) { + return console.error('Usage: ./client.js user pass'); +} + +var user = process.argv[2]; +var pass = process.argv[3]; + +var d = dnode.connect(7007); +d.on('remote', function (remote) { + remote.auth(user, pass, function (err, session) { + if (err) { + console.error(err); + return d.end(); + } + + session.quote(function (q) { + console.log('And now for a quote by ' + q.who + ':\n'); + console.log(q.quote + '\n'); + d.end(); + }); + }); +}); diff --git a/examples/auth/quotes.json b/example/auth/quotes.json similarity index 100% rename from examples/auth/quotes.json rename to example/auth/quotes.json diff --git a/example/auth/server.js b/example/auth/server.js new file mode 100644 index 0000000..29b9891 --- /dev/null +++ b/example/auth/server.js @@ -0,0 +1,29 @@ +var dnode = require('dnode'); +var fs = require('fs'); +var net = require('net'); + +var secretQuotes = require('./quotes.json'); +function randomQuote (cb) { + var ix = Math.floor(Math.random() * secretQuotes.length); + cb(secretQuotes[ix]); +} + +var server = net.createServer(function (stream) { + var d = dnode({ auth : auth }); + d.pipe(stream).pipe(d); + + function auth (user, pass, cb) { + if (typeof cb !== 'function') return; + + if (user === 'moo' && pass === 'hax') { + console.log('signed in: ' + user); + d.on('end', function () { + console.log('disconnected: ' + user); + }); + + cb(null, { quote : randomQuote }); + } + else cb('ACCESS DENIED') + } +}); +server.listen(7007); diff --git a/example/shoe/Makefile b/example/shoe/Makefile new file mode 100644 index 0000000..103950e --- /dev/null +++ b/example/shoe/Makefile @@ -0,0 +1,2 @@ +all: + node_modules/.bin/browserify client.js -o static/bundle.js diff --git a/example/shoe/client.js b/example/shoe/client.js new file mode 100644 index 0000000..46d8296 --- /dev/null +++ b/example/shoe/client.js @@ -0,0 +1,16 @@ +var domready = require('domready'); +var shoe = require('shoe'); +var dnode = require('../../'); + +domready(function () { + var result = document.getElementById('result'); + var stream = shoe('/dnode'); + + var d = dnode(); + d.on('remote', function (remote) { + remote.transform('beep', function (s) { + result.textContent = 'beep => ' + s; + }); + }); + d.pipe(stream).pipe(d); +}); diff --git a/example/shoe/package.json b/example/shoe/package.json new file mode 100644 index 0000000..d7c44f2 --- /dev/null +++ b/example/shoe/package.json @@ -0,0 +1,11 @@ +{ + "name" : "dnode-shoe-example", + "version" : "0.0.0", + "dependencies" : { + "shoe" : "~0.0.0", + "ecstatic" : "~0.1.6", + "domready" : "~0.2.11", + "browserify" : "~1.12.3" + }, + "private" : true +} diff --git a/example/shoe/server.js b/example/shoe/server.js new file mode 100644 index 0000000..b415cfa --- /dev/null +++ b/example/shoe/server.js @@ -0,0 +1,18 @@ +var http = require('http'); +var shoe = require('shoe'); +var ecstatic = require('ecstatic')(__dirname + '/static'); +var dnode = require('../../'); + +var server = http.createServer(ecstatic); +server.listen(9999); + +var sock = shoe(function (stream) { + var d = dnode({ + transform : function (s, cb) { + var res = s.replace(/[aeiou]{2,}/, 'oo').toUpperCase(); + cb(res); + } + }); + d.pipe(stream).pipe(d); +}); +sock.install(server, '/dnode'); diff --git a/example/shoe/static/index.html b/example/shoe/static/index.html new file mode 100644 index 0000000..4ad0e26 --- /dev/null +++ b/example/shoe/static/index.html @@ -0,0 +1,2 @@ + +
diff --git a/examples/simple/client.js b/example/simple/client.js similarity index 82% rename from examples/simple/client.js rename to example/simple/client.js index 193fe3a..c399fc2 100644 --- a/examples/simple/client.js +++ b/example/simple/client.js @@ -1,4 +1,4 @@ -var dnode = require('dnode'); +var dnode = require('../../'); dnode.connect(7070, function (remote, conn) { remote.zing(33, function (n) { diff --git a/examples/simple/server.js b/example/simple/server.js similarity index 79% rename from examples/simple/server.js rename to example/simple/server.js index 188afbb..60e8115 100644 --- a/examples/simple/server.js +++ b/example/simple/server.js @@ -1,4 +1,4 @@ -var dnode = require('dnode'); +var dnode = require('../../'); var server = dnode(function (remote, conn) { this.zing = function (n, cb) { cb(n * 100) }; diff --git a/example/stream/connect.js b/example/stream/connect.js new file mode 100644 index 0000000..79b6547 --- /dev/null +++ b/example/stream/connect.js @@ -0,0 +1,13 @@ +var dnode = require('../../'); +var net = require('net'); + +var d = dnode(); +d.on('remote', function (remote) { + remote.transform('beep', function (s) { + console.log('beep => ' + s); + d.end(); + }); +}); + +var c = net.connect(5004); +c.pipe(d).pipe(c); diff --git a/example/stream/listen.js b/example/stream/listen.js new file mode 100644 index 0000000..abedc66 --- /dev/null +++ b/example/stream/listen.js @@ -0,0 +1,13 @@ +var dnode = require('../../'); +var net = require('net'); + +var server = net.createServer(function (c) { + var d = dnode({ + transform : function (s, cb) { + cb(s.replace(/[aeiou]{2,}/, 'oo').toUpperCase()) + } + }); + c.pipe(d).pipe(c); +}); + +server.listen(5004); diff --git a/examples/auth/client.js b/examples/auth/client.js deleted file mode 100755 index ff51fdd..0000000 --- a/examples/auth/client.js +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env node -// Connect to the auth server and display a quote from it. - -var DNode = require('dnode'); -var sys = require('sys'); - -if (process.argv.length < 4) { - sys.puts('Usage: ./client.js user pass'); - process.exit(); -} - -var user = process.argv[2]; -var pass = process.argv[3]; - -DNode.connect(7007, function (remote,conn) { - remote.authenticate(user, pass, function (session) { - if (session) { - sys.puts('Authentication success'); - session.quote(function (q) { - sys.puts('\nAnd now for a quote by ' + q.who + ':\n\n'); - sys.puts(q.quote + '\n\n'); - conn.end(); - }); - } - else { - sys.puts('Authentication failure'); - conn.end(); - } - }); -}); diff --git a/examples/auth/server.js b/examples/auth/server.js deleted file mode 100755 index a4ef38b..0000000 --- a/examples/auth/server.js +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env node -// Serve an object with authentication - -var DNode = require('dnode'); -var sys = require('sys'); -var fs = require('fs'); - -var quotes = JSON.parse( - fs.readFileSync(__dirname + '/quotes.json').toString() -); - -// Serve up a Session object only after the client supplies the valid user/pass -DNode(function (client, connection) { - this.authenticate = function (user, pass, cb) { - if (user == 'moo' && pass == 'hax') { - sys.puts('Sign in as ' + sys.inspect(user) + ' succeeded!'); - cb(new Session({ - user : user, - client : client, - connection : connection, - })); - } - else { - sys.puts('Sign in as ' + sys.inspect(user) + ' failed!'); - cb(null); - } - }; -}).listen(7007); - -// Clients who connect get an instance of this session object: -function Session (params) { - var conn = params.connection; - var user = params.user; - var client = params.client; - - conn.addListener('end', function () { - sys.puts('User ' + sys.inspect(user) + ' disconnected'); - }); - - this.quote = function (f) { - var i = Math.floor(Math.random() * quotes.length); - f(quotes[i]); - }; -} diff --git a/examples/bidirectional/client.js b/examples/bidirectional/client.js deleted file mode 100644 index 5efe66e..0000000 --- a/examples/bidirectional/client.js +++ /dev/null @@ -1,21 +0,0 @@ -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! - }); -}); diff --git a/examples/bidirectional/server.js b/examples/bidirectional/server.js deleted file mode 100644 index 829c49f..0000000 --- a/examples/bidirectional/server.js +++ /dev/null @@ -1,12 +0,0 @@ -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); diff --git a/examples/chat/INSTALL.txt b/examples/chat/INSTALL.txt deleted file mode 100644 index 159ee7d..0000000 --- a/examples/chat/INSTALL.txt +++ /dev/null @@ -1,4 +0,0 @@ -Do this first: - - mkdir node_modules - npm install jquery-browserify diff --git a/examples/chat/chat.css b/examples/chat/chat.css deleted file mode 100644 index c8e7bda..0000000 --- a/examples/chat/chat.css +++ /dev/null @@ -1,67 +0,0 @@ -div#prompt { - width: 600px; - height: 400px; - margin: auto; - display: table-cell; - vertical-align: middle; - text-align: center; - background-color: rgb(30,30,30); - color: white; -} - -div#chat { - width: 600px; - margin: auto; -} - -div#messages { - display: none; - overflow-x: hidden; - overflow-y: scroll; - padding: 0.5em; - width: 600px; - height: 400px; - border-color: black; - border-style: solid; - border-width: 2px; - background-color: rgb(30,30,30); -} - -form#post input { - width: 600px; - padding: 0.5em; - background-color: black; - color: white; - border-width: 0px; -} - -span.who { - font-weight: bold; - margin-right: 1em; - color: rgb(200,200,200); -} - -span.me { - font-weight: bold; - margin-right: 1em; - color: white; -} - -span.msg { - color: white; -} - -div.join { - color: rgb(200,250,150); - font-weight: bold; -} - -div.part { - color: rgb(250,200,150); - font-weight: bold; -} - -div.users { - color: rgb(150,200,250); - font-weight: bold; -} diff --git a/examples/chat/entry.js b/examples/chat/entry.js deleted file mode 100644 index a9c1e07..0000000 --- a/examples/chat/entry.js +++ /dev/null @@ -1,73 +0,0 @@ -var $ = require('jquery-browserify'); -var dnode = require('dnode'); - -$(document).ready(function () { - // Fetch the user's name before the main chat code fires - $('form#name').submit(function (ev) { - ev.preventDefault(); - var name = $('input[name="name"]').val(); - $('#messages').show(); - $('#prompt').hide(); - chat(name); - }); -}); - -function chat (name) { - // Connect to the chat server now that we've got a name - dnode(function () { - this.name = name; - - this.joined = function (who) { - addLine($('
') - .addClass('join') - .text(who + ' has joined') - ); - }; - - this.parted = function (who) { - addLine($('
') - .addClass('part') - .text(who + ' has left') - ); - }; - - this.said = function (who,msg) { - addLine($('
').append( - $('') - .addClass(who == name ? 'me' : 'who') - .text('<' + who + '>') - , - $('').addClass('msg').text(msg) - )); - }; - }).connect(function (remote) { - $('form#post').submit(function (ev) { - ev.preventDefault(); - remote.say(this.elements.msg.value); - this.elements.msg.value = ''; - }); - - // fetch a list of all the connected users - remote.names(function (names) { - addLine($('
') - .addClass('users') - .text('Users: ' + ( - names.map(function (name) { - return '[ ' + name - .replace(/\\/g,'\\\\') - .replace(/\[/g,'\\[') - .replace(/\]/g,'\\]') - + ' ]' - }).join(' ') - || '(no users)' - )) - ); - }); - }); -} - -function addLine(elem) { - var div = $('#messages'); - div.append(elem); - div.animate({ scrollTop: div.attr('scrollHeight') }, 200); -} diff --git a/examples/chat/index.html b/examples/chat/index.html deleted file mode 100644 index 01dea65..0000000 --- a/examples/chat/index.html +++ /dev/null @@ -1,23 +0,0 @@ - - -dnode chat example - - - - -
-
-
- Name: - -
-
- -
- -
- -
-
- - diff --git a/examples/chat/package.json b/examples/chat/package.json deleted file mode 100644 index 00c483a..0000000 --- a/examples/chat/package.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "dependencies" : { - "jquery-browserify" : "*", - "browserify" : "1.0.x", - "express" : "0.7.x" - } -} diff --git a/examples/chat/server.js b/examples/chat/server.js deleted file mode 100755 index 127c46d..0000000 --- a/examples/chat/server.js +++ /dev/null @@ -1,50 +0,0 @@ -// simple dnode chat server with browserify - -var express = require('express'); -var app = express.createServer(); -app.use(express.static(__dirname)); - -var browserify = require('browserify'); -app.use(browserify(__dirname + '/entry.js', { watch : true })); - -app.listen(6061); -console.log('http://localhost:6061/'); - -var EventEmitter = require('events').EventEmitter; -var emitter = new EventEmitter; - -var clients = {}; - -var dnode = require('../../'); -dnode(ChatServer).listen(app); - -function ChatServer (client, con) { - var evNames = [ 'joined', 'said', 'parted' ]; - - con.on('ready', function () { - evNames.forEach(function (name) { - emitter.on(name, client[name]); - }); - emitter.emit('joined', client.name); - - clients[client.name] = client; - }); - - con.on('end', function () { - evNames.forEach(function (name) { - if (typeof client[name] === 'function') { - emitter.removeListener(name, client[name]); - } - }); - emitter.emit('parted', client.name); - delete clients[client.name]; - }); - - this.say = function (msg) { - emitter.emit('said', client.name, msg); - }; - - this.names = function (cb) { - cb(Object.keys(clients)) - }; -} diff --git a/examples/https/index.html b/examples/https/index.html deleted file mode 100644 index 7912b90..0000000 --- a/examples/https/index.html +++ /dev/null @@ -1,16 +0,0 @@ - - - -

timesTen(10) == ?

-

My name is ?.

diff --git a/examples/https/server.js b/examples/https/server.js deleted file mode 100755 index 055cf34..0000000 --- a/examples/https/server.js +++ /dev/null @@ -1,50 +0,0 @@ -var https = require('https'); -var fs = require('fs'); -var dnode = require('dnode'); - -try { - var opts = { - key : fs.readFileSync(__dirname + '/privatekey.pem'), - cert : fs.readFileSync(__dirname + '/certificate.pem'), - }; -} -catch (e) { - console.log('# {privatekey,certificate}.pem missing, do this first:'); - console.log('openssl genrsa -out privatekey.pem 1024'); - console.log('openssl req -new -key privatekey.pem -out certrequest.csr'); - console.log('openssl x509 -req -in certrequest.csr ' - + '-signkey privatekey.pem -out certificate.pem'); - process.exit(); -} - -var index = fs.readFileSync(__dirname + '/index.html'); -var server = https.createServer(opts, function (req, res) { - if (req.url === '/') { - res.writeHead(200, { 'Content-Type' : 'text/html' }); - res.end(index); - } - else if (!res.finished) { - process.nextTick(function () { - if (!res.finished) { - res.writeHead(404, { 'Content-Type' : 'text/html' }); - res.end('Not found!'); - } - }); - } -}); - -dnode(function (client) { - this.timesTen = function (n,f) { f(n * 10) }; - this.whoAmI = function (reply) { - client.name(function (name) { - reply(name - .replace(/Mr\.?/,'Mister') - .replace(/Ms\.?/,'Miss') - .replace(/Mrs\.?/,'Misses') - ); - }) - }; -}).listen(server); -server.listen(8020); - -console.log('https://localhost:8020/'); diff --git a/examples/nested.js b/examples/nested.js deleted file mode 100644 index 22f5b2e..0000000 --- a/examples/nested.js +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env node -var DNode = require('dnode'); -var sys = require('sys'); -var EventEmitter = require('events').EventEmitter; - -// server-side: -var server1 = DNode({ - timesTen : function (n,reply) { reply(n * 10) } -}).listen(6060); - -var server2 = DNode({ - timesTwenty : function (n,reply) { reply(n * 20) } -}).listen(6061); - -var moo = new EventEmitter; - -// client-side: -// Good thing this example is so contrived. Real code doesn't turn out this -// ugly very often on purpose. -server1.on('ready', function () { - server2.on('ready', function () { - DNode.connect(6060, function (remote1) { - DNode.connect(6061, function (remote2) { - moo.addListener('hi', function (x) { - remote1.timesTen(x, function (res) { - sys.puts(res); - remote2.timesTwenty(res, function (res2) { - sys.puts(res2); - }); - }); - }); - remote2.timesTwenty(5, function (n) { - sys.puts(n); // 100 - remote1.timesTen(0.1, function (n) { - sys.puts(n); // 1 - }); - }); - }); - }); - }); -}); - -setTimeout(function() { - moo.emit('hi', 100); -}, 500); - -setTimeout(function () { - // kill the service after a while - server1.end(); - server2.end(); -}, 1000); - -/* - Output: - 100 - 1 - ... after half a second ... - 1000 - 20000 -*/ - diff --git a/examples/perf/client.js b/examples/perf/client.js deleted file mode 100644 index 9898119..0000000 --- a/examples/perf/client.js +++ /dev/null @@ -1,15 +0,0 @@ -var frames = 0, last = 0; -setInterval(function () { - if (last) { - var fps = frames / (Date.now() - last) * 1000; - console.log('fps: ' + fps); - } - - last = Date.now(); - frames = 0; -}, 1000); - -var dnode = require('dnode'); -dnode({ - emit : function (i) { frames ++ } -}).connect(7575); diff --git a/examples/perf/emitter.js b/examples/perf/emitter.js deleted file mode 100644 index 4d716ab..0000000 --- a/examples/perf/emitter.js +++ /dev/null @@ -1,20 +0,0 @@ -var dnode = require('dnode'); -var EventEmitter = require('events').EventEmitter; - -var clients = {}; -setInterval(function () { - Object.keys(clients).forEach(function (id) { - var i = Math.floor(Math.random() * Math.pow(2,32)); - clients[id].emit(i); - }); -}, 1000 / 50); // 50 fps - -dnode(function (client, conn) { - conn.on('ready', function () { - clients[conn.id] = client; - }); - - conn.on('end', function () { - delete clients[conn.id]; - }); -}).listen(7575); diff --git a/examples/saturate/index.html b/examples/saturate/index.html deleted file mode 100644 index dc9be0c..0000000 --- a/examples/saturate/index.html +++ /dev/null @@ -1,83 +0,0 @@ - - - - - - -
- 0 - bytes - transferred -
- -
- ping X milliseconds -
- -
- 0 connections -
- - diff --git a/examples/saturate/saturate.js b/examples/saturate/saturate.js deleted file mode 100755 index 508d201..0000000 --- a/examples/saturate/saturate.js +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env node - -var connect = require('connect'); -var server = connect.createServer( - connect.static(__dirname) -); - -var clients = {}; - -var Hash = require('traverse/hash'); -function publish () { - var args = [].slice.call(arguments); - Hash(clients).forEach( - function (emit) { emit.apply({}, args) } - ); -} - -var DNode = require('dnode'); -DNode(function (client, conn) { - conn.on('ready', function () { - clients[conn.id] = client.emit; - }); - - conn.on('end', function () { - delete clients[conn.id]; - }); -}).listen(server); - -console.log('http://localhost:6061/'); -server.listen(6061); - -setInterval(function () { - var n = Math.floor(Math.random() * 3e4); - var buf = new Buffer(n); - publish('data', buf.toString()); -}, 50); diff --git a/examples/web-browserify/index.html b/examples/web-browserify/index.html deleted file mode 100644 index b1d061a..0000000 --- a/examples/web-browserify/index.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - The cat says ?. - - diff --git a/examples/web-browserify/server.js b/examples/web-browserify/server.js deleted file mode 100755 index 4bd568c..0000000 --- a/examples/web-browserify/server.js +++ /dev/null @@ -1,21 +0,0 @@ -var connect = require('connect'); -var browserify = require('browserify'); -var dnode = require('dnode'); - -var server = connect.createServer(); - -server.use(connect.static(__dirname)); - -server.use(browserify({ - require : 'dnode', - mount : '/browserify.js' -})); - -dnode(function (client) { - this.cat = function (cb) { - cb('meow'); - }; -}).listen(server); - -server.listen(6857); -console.log('http://localhost:6857/'); diff --git a/examples/web-connect/index.html b/examples/web-connect/index.html deleted file mode 100644 index 095e186..0000000 --- a/examples/web-connect/index.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - -The cat says ?. - - diff --git a/examples/web-connect/server.js b/examples/web-connect/server.js deleted file mode 100755 index 8ce6401..0000000 --- a/examples/web-connect/server.js +++ /dev/null @@ -1,14 +0,0 @@ -var connect = require('connect'); -var dnode = require('dnode'); - -var server = connect.createServer(); -server.use(connect.static(__dirname)); - -dnode(function (client) { - this.cat = function (cb) { - cb('meow '); - }; -}).listen(server); - -server.listen(6857); -console.log('http://localhost:6857/'); diff --git a/examples/web-express/index.html b/examples/web-express/index.html deleted file mode 100644 index 095e186..0000000 --- a/examples/web-express/index.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - -The cat says ?. - - diff --git a/examples/web-express/server.js b/examples/web-express/server.js deleted file mode 100755 index cb87c83..0000000 --- a/examples/web-express/server.js +++ /dev/null @@ -1,14 +0,0 @@ -var express = require('express'); -var server = express.createServer(); -var dnode = require('dnode'); - -server.use(express.static(__dirname)); - -dnode(function (client) { - this.cat = function (cb) { - cb('meow'); - }; -}).listen(server); - -server.listen(6857); -console.log('http://localhost:6857/'); diff --git a/examples/web-http/index.html b/examples/web-http/index.html deleted file mode 100644 index 095e186..0000000 --- a/examples/web-http/index.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - -The cat says ?. - - diff --git a/examples/web-http/server.js b/examples/web-http/server.js deleted file mode 100755 index 749989f..0000000 --- a/examples/web-http/server.js +++ /dev/null @@ -1,21 +0,0 @@ -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); - } -}); - -dnode(function (client) { - this.cat = function (cb) { - cb('meow'); - }; -}).listen(server); - -server.listen(6857); -console.log('http://localhost:6857/'); diff --git a/index.js b/index.js index ff87063..34755e3 100644 --- a/index.js +++ b/index.js @@ -1,297 +1,132 @@ +var dnode = require('./lib/dnode'); +var parseArgs = require('./lib/parse_args'); var net = require('net'); -var tls = require('tls'); -var http = require('http'); -var EventEmitter = require('events').EventEmitter; +var util = require('util'); +var weak = require('weak'); -var protocol = require('dnode-protocol'); -var Lazy = require('lazy'); -var SocketIO = require('./lib/stream_socketio'); - -exports = module.exports = dnode; - -function dnode (wrapper) { - if (!(this instanceof dnode)) return new dnode(wrapper); - - this.proto = protocol(wrapper); - this.stack = []; - this.streams = []; - return this; -} +exports = module.exports = function (cons, opts) { + return new D(cons, opts); +}; -dnode.prototype = new EventEmitter; +exports.connect = function () { + var d = new D(); + return d.connect.apply(d, arguments); +}; -dnode.prototype.use = function (middleware) { - this.stack.push(middleware); - return this; +exports.listen = function () { + var d = new D(); + return d.listen.apply(d, arguments); }; -dnode.prototype.connect = function () { - var params = protocol.parseArgs(arguments); - var stream = params.stream; - var client = null; - var self = this; +util.inherits(D, dnode); +function D (cons, opts) { + var self = this; + if (!opts) opts = {}; - if (params.port) { - params.host = params.host || '127.0.0.1'; - if (params.key) { - var options = { - key: params.key, - cert: params.cert, - ca: params.ca, - requestCert: params.requestCert, - rejectUnauthorized: params.rejectUnauthorized - }; - stream = tls.connect(params.port, params.host, options, function() { - attachDnode(); + if (opts.weak !== false && !opts.proto) { + opts.proto = {}; + opts.proto.wrap = function (cb, id) { + return weak(cb, function () { + if (self.proto) self.proto.cull(id); }); - } - else { - stream = net.createConnection(params.port, params.host); - stream.on('connect', function() { - attachDnode(); - }); - } - } - else if (params.path) { - stream = net.createConnection(params.path); - stream.on('connect', function() { - attachDnode(); - }); + }; + opts.proto.unwrap = function (ref, id) { + var cb = weak.get(ref); + return cb || function () {}; + }; } - else { - attachDnode(); - } - - stream.remoteAddress = params.host; - stream.remotePort = params.port; - - var args = arguments; + return dnode.call(self, cons, opts); +} + +D.prototype.connect = function () { + var self = this; + var params = parseArgs(arguments); - if (params.reconnect) { - stream.on('error', (function (err) { - if (err.code === 'ECONNREFUSED' || err.code === 'ENOENT') { - if (client) client.emit('refused'); - - setTimeout((function () { - if (client) client.emit('reconnect'); - dnode.prototype.connect.apply(this, args); - }).bind(this), params.reconnect); - } - else if (client) client.emit('error', err) - else this.emit('error', err) - }).bind(this)); - - stream.once('end', (function () { - if (!params.reconnect) return; - client.emit('drop'); - - setTimeout((function () { - if (!params.reconnect) return; - client.emit('reconnect'); - dnode.prototype.connect.apply(this, args); - }).bind(this), params.reconnect); - }).bind(this)); + var stream; + if (params.path) { + stream = net.connect(params.path); + } + else if (params.port) { + stream = net.connect(params.port, params.host); } else { - stream.on('error', (function (err) { - if (client) client.emit('error', err) - else this.emit('error', err) - }).bind(this)); + throw new Error('no port or unix path given'); } - function attachDnode() { - client = createClient(self.proto, stream); - - client.end = function () { - if (params.reconnect) params.reconnect = 0; - stream.end(); - }; - - self.stack.forEach(function (middleware) { - middleware.call(client.instance, client.remote, client); - }); - - if (params.block) { - client.on('remote', function () { - params.block.call(client.instance, client.remote, client); - }); - } - - process.nextTick(function () { - if (client.listeners('error').length === 0) { - // default error handler to keep everything from crashing - client.on('error', function (err) { - console.error(err && err.stack || err); - }) - } - }); - - client.start(); - }; + if (params.block) self.on('remote', params.block); - this.streams.push(stream); - return this; + stream.on('error', function (err) { + if (err && err.code === 'EPIPE') return; // eat EPIPEs + self.emit('error', err); + }); + + self.id = randomId(); + self.stream = stream; + stream.pipe(self); + self.pipe(stream); + + return self; }; dnode.prototype.listen = function () { var self = this; - var params = protocol.parseArgs(arguments); - var server = params.server; - if (params.port) { - if (params.key) { - var options = { - key: params.key, - cert: params.cert, - ca: params.ca, - requestCert: params.requestCert, - rejectUnauthorized: params.rejectUnauthorized - }; - server = tls.createServer(options); - server.on('error', this.emit.bind(this, 'error')); - if (params.host) { - server.listen( - params.port, params.host, - this.emit.bind(this, 'ready') - ); - } - else { - server.listen( - params.port, - this.emit.bind(this, 'ready') - ); - } - } - else { - server = net.createServer(); - server.on('error', this.emit.bind(this, 'error')); - server.listen( - params.port, params.host, - this.emit.bind(this, 'ready') - ); - } - } - else if (params.path) { - server = net.createServer(); - server.on('error', this.emit.bind(this, 'error')); - server.listen( - params.path, - this.emit.bind(this, 'ready') - ); - } - else if (server && ( - server instanceof http.Server - || server.hasOwnProperty('httpAllowHalfOpen') - || params.webserver - )) { - // a webserver, use socket.io - server = SocketIO( - server || params.webserver, - params.mount === undefined ? '/dnode.js' : params.mount, - params.io || {} - ); - } - - if (!server) { - this.emit('error', new Error('Not sure how to fire up this listener')); - } + // just copy over the opts and cons, the rest will need to be re-created + var cons = self.cons, opts = self.opts; + self.cons = function () {}; + self.end(); - var clients = {}; - var listenFor = server instanceof tls.Server - ? 'secureConnection' - : 'connection' - ; + var params = parseArgs(arguments); - server.on(listenFor, function (stream) { - var client = createClient(self.proto, stream); - clients[client.id] = client; + var server = net.createServer(function (stream) { + var d = new dnode(cons, opts); + do { d.id = randomId() } + while (server.sessions[d.id]); - self.stack.forEach(function (middleware) { - middleware.call(client.instance, client.remote, client); + server.sessions[d.id] = d; + d.on('end', function () { + delete server.sessions[d.id]; }); - if (params.block) { - client.on('remote', function () { - params.block.call(client.instance, client.remote, client); - }); - } + d.on('local', function (ref) { + server.emit('local', ref, d); + }); - client.start(); - }); - - this.server = server; - server.on('close', this.emit.bind(this, 'close')); - - return this; -}; - -function createClient (proto, stream) { - var client = proto.create(); - - process.nextTick(function () { - if (client.listeners('error').length === 0) { - // default error handler to keep everything from crashing - client.on('error', function (err) { - console.error(err && err.stack || err); - }) - } + d.on('remote', function (remote) { + server.emit('remote', remote, d); + }); + + stream.on('error', function (err) { + if (err && err.code === 'EPIPE') return; // eat EPIPEs + d.emit('error', err); + }); + + d.stream = stream; + stream.pipe(d); + d.pipe(stream); }); - client.stream = stream; - client.end = stream.end.bind(stream); - client.destroy = stream.destroy.bind(stream); - - stream.on('end', client.emit.bind(client, 'end')); - stream.on('disconnect', client.emit.bind(client, 'disconnect')); - stream.on('close', client.emit.bind(client, 'close')); + server.sessions = {}; - client.on('request', function (req) { - if (stream.writable) { - stream.write(JSON.stringify(req) + '\n'); - } - else { - client.emit('dropped', req); - } - }); + if (params.port) { + server.listen(params.port, params.host); + } + else if (params.path) { + server.listen(params.path); + } + else { + throw new Error('no port or path provided'); + } - Lazy(stream).lines - .map(String) - .forEach(client.parse) - ; + if (params.block) server.on('listening', params.block); - return client; -} - -dnode.prototype.end = function () { - Object.keys(this.proto.sessions) - .forEach((function (id) { - this.proto.sessions[id].stream.end() - }).bind(this)) - ; - this.emit('end'); + return server; }; -dnode.prototype.close = function () { - var self = this - try { - self.server.close(); +function randomId () { + var s = ''; + for (var i = 0; i < 4; i++) { + s += Math.random().toString(16).slice(2); } - catch (e) { - if (e.message === 'Not running') { - self.server.emit('close'); - } - else { - self.server.emit('error', e); - } - } -}; - -dnode.connect = function () { - var d = dnode(); - return d.connect.apply(d, arguments); -}; - -dnode.listen = function () { - var d = dnode(); - return d.listen.apply(d, arguments); -}; + return s; +} diff --git a/lib/dnode.js b/lib/dnode.js new file mode 100644 index 0000000..55713c9 --- /dev/null +++ b/lib/dnode.js @@ -0,0 +1,152 @@ +var protocol = require('dnode-protocol'); +var Stream = require('stream'); +var json = typeof JSON === 'object' ? JSON : require('jsonify'); + +module.exports = dnode; +dnode.prototype = {}; +(function () { // browsers etc + for (var key in Stream.prototype) { + dnode.prototype[key] = Stream.prototype[key]; + } +})(); + +function dnode (cons, opts) { + Stream.call(this); + var self = this; + + self.opts = opts || {}; + + self.cons = typeof cons === 'function' + ? cons + : function () { return cons || {} } + ; + + self.readable = true; + self.writable = true; + + process.nextTick(function () { + if (self._ended) return; + self.proto = self._createProto(); + self.proto.start(); + + (self._handleQueue || []).forEach(function (row) { + self.handle(row); + }); + }); +} + +dnode.prototype._createProto = function () { + var self = this; + var proto = protocol(function (remote) { + if (self._ended) return; + + var ref = self.cons.call(this, remote, self); + if (typeof ref !== 'object') ref = this; + + self.emit('local', ref, self); + + return ref; + }, self.opts.proto); + + proto.on('remote', function (remote) { + self.emit('remote', remote, self); + self.emit('ready'); // backwards compatability, deprecated + }); + + proto.on('request', function (req) { + if (!self.readable) return; + + if (self.opts.emit === 'object') { + self.emit('data', req); + } + else self.emit('data', json.stringify(req) + '\n'); + }); + + proto.on('fail', function (err) { + // errors that the remote end was responsible for + self.emit('fail', err); + }); + + proto.on('error', function (err) { + // errors that the local code was responsible for + self.emit('error', err); + }); + + return proto; +}; + +dnode.prototype.write = function (buf) { + if (this._ended) return; + var self = this; + var row; + + if (buf && typeof buf === 'object' + && buf.constructor && buf.constructor.name === 'Buffer' + && buf.length + && typeof buf.slice === 'function') { + // treat like a buffer + if (!self._bufs) self._bufs = []; + + // treat like a buffer + for (var i = 0, j = 0; i < buf.length; i++) { + if (buf[i] === 0x0a) { + self._bufs.push(buf.slice(j, i)); + + var line = ''; + for (var k = 0; k < self._bufs.length; k++) { + line += String(self._bufs[k]); + } + + try { row = json.parse(line) } + catch (err) { return self.end() } + + j = i + 1; + + self.handle(row); + self._bufs = []; + } + } + + if (j < buf.length) self._bufs.push(buf.slice(j, buf.length)); + } + else if (buf && typeof buf === 'object') { + // .isBuffer() without the Buffer + // Use self to pipe JSONStream.parse() streams. + self.handle(buf); + } + else { + if (typeof buf !== 'string') buf = String(buf); + if (!self._line) self._line = ''; + + for (var i = 0; i < buf.length; i++) { + if (buf.charCodeAt(i) === 0x0a) { + try { row = json.parse(self._line) } + catch (err) { return self.end() } + + self.handle(row); + self._line = ''; + } + else self._line += buf.charAt(i) + } + } +}; + +dnode.prototype.handle = function (row) { + if (!this.proto) { + if (!this._handleQueue) this._handleQueue = []; + this._handleQueue.push(row); + } + else this.proto.handle(row); +}; + +dnode.prototype.end = function () { + if (this._ended) return; + this._ended = true; + this.writable = false; + this.readable = false; + this.emit('end'); +}; + +dnode.prototype.destroy = function () { + this.end(); +}; diff --git a/lib/parse_args.js b/lib/parse_args.js new file mode 100644 index 0000000..f9a79ff --- /dev/null +++ b/lib/parse_args.js @@ -0,0 +1,35 @@ +module.exports = function (argv) { + var params = {}; + for (var i = 0; i < argv.length; i++) { + var arg = argv[i]; + + if (typeof arg === 'string') { + if (arg.match(/^\d+$/)) { + params.port = parseInt(arg, 10); + } + else if (arg.match('^/')) { + params.path = arg; + } + else { + params.host = arg; + } + } + else if (typeof arg === 'number') { + params.port = arg; + } + else if (typeof arg === 'function') { + params.block = arg; + } + else if (typeof arg === 'object') { + for (var key in arg) { + if (key === 'port') { + params[key] = parseInt(arg[key], 10) + } + else params[key] = arg[key] + } + } + // ignore everything else + }; + + return params; +}; diff --git a/lib/stream_socketio.js b/lib/stream_socketio.js deleted file mode 100644 index 6c24442..0000000 --- a/lib/stream_socketio.js +++ /dev/null @@ -1,112 +0,0 @@ -var EventEmitter = require('events').EventEmitter; -var Stream = require('stream').Stream; -var io = require('socket.io'); -var fs = require('fs'); - -var bundle = (function () { - var cache = null; - var file = __dirname + '/../browser/bundle.js'; - - return function (req, res) { - if (cache) { - var headers = { - 'content-type' : 'text/javascript', - 'last-modified' : cache.modified.toGMTString(), - 'date' : new Date().toGMTString(), - }; - - var ims = req.headers['if-modified-since']; - if (ims) { - var m = new Date(ims); - if (m >= cache.modified) { - res.writeHead(304, headers); - res.end(); - return; - } - } - - res.writeHead(200, headers); - res.end(cache.source); - } - else fs.stat(file, function (err0, stat) { - fs.readFile(file, function (err1, src) { - if (err0 || err1) { - var e = err0 || err1; - console.error(e.message || e); - res.writeHead(500, { 'content-type' : 'text/plain' }); - res.end('an error occured loading the bundle'); - } - else { - cache = { - source : src, - modified : stat.mtime, - }; - bundle(req, res); - } - }); - }); - }; -})(); - -module.exports = function (webserver, mount, ioOptions) { - if (ioOptions['log level'] === undefined) { - ioOptions['log level'] = -1; - } - - var sock = io.listen(webserver, ioOptions); - sock.set('logger', { - error : function () {}, - warn : function () {}, - info : function () {}, - debug : function () {} - }); - - var server = new EventEmitter; - server.socket = sock; - - if (mount && webserver.use) { - webserver.use(function (req, res, next) { - if (req.url.split('?')[0] === mount) { - bundle(req, res); - } - else next() - }); - } - else if (mount) { - if (!webserver._events) webserver._events = {}; - var ev = webserver._events; - - if (!ev.request) ev.request = []; - if (!Array.isArray(ev.request)) ev.request = [ ev.request ]; - - ev.request.push(function (req, res) { - if (!res.finished && req.url.split('?')[0] === mount) { - bundle(req, res); - } - }); - } - - sock.sockets.on('connection', function (client) { - var stream = new Stream; - - stream.socketio = client; - stream.readable = true; - stream.writable = true; - - stream.write = client.send.bind(client); - stream.end = stream.destroy = client.disconnect.bind(client); - - client.on('message', stream.emit.bind(stream, 'data')); - client.on('error', stream.emit.bind(stream, 'error')); - - client.on('disconnect', function () { - stream.writable = false; - stream.readable = false; - stream.emit('end'); - }); - - server.emit('connection', stream); - }); - - return server; -}; diff --git a/package.json b/package.json index 73863a4..1513609 100644 --- a/package.json +++ b/package.json @@ -1,17 +1,11 @@ { "name" : "dnode", - "version" : "0.9.9", - "description" : "freestyle RPC", + "version" : "1.0.1", + "description" : "freestyle rpc", "main" : "./index.js", "keywords" : [ - "message passing", "rpc", - "rmi", - "drb", - "remote", - "communication", - "websockets", - "socket.io" + "callbacks" ], "repository" : { "type" : "git", @@ -22,24 +16,20 @@ "examples" : "./examples" }, "dependencies" : { - "socket.io" : "0.8.6", - "socket.io-client" : "https://github.com/substack/socket.io-client/tarball/master", - "lazy" : ">=1.0.5 <1.1", - "dnode-protocol" : "~0.1.2", - "jsonify" : "0.0.x" + "dnode-protocol" : "~0.2.2", + "jsonify" : "~0.0.0", + "weak" : "~0.2.1" }, "devDependencies" : { - "tap" : "~0.2.4", - "browserify" : "~1.10.7", - "uglify-js" : "~1.2.5", - "express" : "~2.5.9" + "tap" : "~0.2.5", + "browserify" : "~1.13.0" }, "scripts" : { "test" : "tap test/*.js" }, - "browserify" : "browser/index.js", + "browserify" : "browser.js", "engine" : { - "node" : ">=0.4.0" + "node" : ">=0.6.0" }, "author" : { "name" : "James Halliday", diff --git a/perf/memory/client.js b/perf/memory/client.js new file mode 100644 index 0000000..c4b167a --- /dev/null +++ b/perf/memory/client.js @@ -0,0 +1,12 @@ +var dnode = require('../../'); +var d = dnode(); + +var d = dnode.connect(7070); +var ix = 0; +d.on('remote', function fn (remote) { + remote.zing(33, function () { + ix++; + if (ix % 100 === 0) console.log(ix); + fn(remote); + }); +}); diff --git a/perf/memory/server.js b/perf/memory/server.js new file mode 100644 index 0000000..7012176 --- /dev/null +++ b/perf/memory/server.js @@ -0,0 +1,16 @@ +var dnode = require('../../'); +var net = require('net'); + +var server = net.createServer(function (stream) { + var d = dnode(function (remote) { + this.zing = function (n, cb) { cb(n * 100) }; + }); + d.pipe(stream).pipe(d); +}); +server.listen(7070); + +setInterval(function () { + var mem = process.memoryUsage(); + var m = mem.heapTotal / 1024 / 1024; + console.log(Math.round(m * 100) / 100 + ' MB'); +}, 1000); diff --git a/test/_id.js b/test/_id.js index 74b44d3..c8c8397 100644 --- a/test/_id.js +++ b/test/_id.js @@ -7,7 +7,7 @@ test('_id', function (t) { var server = dnode({ _id : 1337 }).listen(port); - server.on('ready', function () { + server.on('listening', function () { dnode.connect(port, function (remote, conn) { t.equal(remote._id, 1337); conn.end(); diff --git a/test/args.js b/test/args.js new file mode 100644 index 0000000..0b498b1 --- /dev/null +++ b/test/args.js @@ -0,0 +1,57 @@ +var test; +try { test = require('tap').test; } +catch (e) { test = require('testling') } + +var parseArgs = require('../lib/parse_args'); + +function argv () { return arguments } + +test('args', function (t) { + t.deepEqual( + parseArgs(argv('moo.com', 555)), + { host : 'moo.com', port : 555 } + ); + + t.deepEqual( + parseArgs(argv('7777')), + { port : 7777 } + ); + + t.deepEqual( + parseArgs(argv({ + host : 'moosy.moo.com', + port : 5050 + })), + { host : 'moosy.moo.com', port : 5050 } + ); + + t.deepEqual( + parseArgs(argv('meow.cats.com', { port : '1234' })), + { host : 'meow.cats.com', port : 1234 } + ); + + t.deepEqual( + typeof parseArgs(argv('789')).port, + 'number' + ); + + t.deepEqual( + parseArgs(argv( + { host : 'woof.dogs.com' }, { port : 4050 } + )), + { host : 'woof.dogs.com', port : 4050 } + ); + + t.deepEqual( + parseArgs(argv( + undefined, + { host : 'woof.dogs.com' }, + undefined, + { port : 4050 }, + undefined + )), + { host : 'woof.dogs.com', port : 4050 } + ); + + t.end(); +}); diff --git a/test/bidirectional.js b/test/bidirectional.js index 2d52a59..737041a 100644 --- a/test/bidirectional.js +++ b/test/bidirectional.js @@ -18,7 +18,7 @@ test('bidirectional', function (t) { }; }).listen(port); - server.on('ready', function () { + server.on('listening', function () { dnode({ x : function (f) { counts.x ++; diff --git a/test/broadcast.js b/test/broadcast.js index 6fa66f5..c2f6731 100644 --- a/test/broadcast.js +++ b/test/broadcast.js @@ -1,13 +1,14 @@ var dnode = require('../'); var EventEmitter = require('events').EventEmitter; var test = require('tap').test; +var destroy = require('./lib/destroy'); test('broadcast', function (t) { t.plan(3); var port = Math.floor(Math.random() * 40000 + 10000); var em = new EventEmitter; - var server = dnode(function (client,conn) { + var server = dnode(function (client, conn) { conn.on('ready', function () { em.on('message', client.message); }); @@ -23,7 +24,7 @@ test('broadcast', function (t) { var recv = { 0 : [], 1 : [], 2 : [] }; - server.on('ready', function () { + server.on('listening', function () { dnode({ name : '#0', message : function (msg) { recv[0].push(msg) }, @@ -53,8 +54,8 @@ test('broadcast', function (t) { }); setTimeout(function () { - server.end(); - server.close(); + destroy(server); + t.deepEqual( recv[0], [ '#0 says: hello!', '#1 says: hey', '#2 says: wowsy' ], diff --git a/test/bundle.js b/test/bundle.js deleted file mode 100644 index ce23b3d..0000000 --- a/test/bundle.js +++ /dev/null @@ -1,77 +0,0 @@ -var test = require('tap').test; -var dnode = require('../'); -var http = require('http'); -var express = require('express'); - -var path = require('path'); -if (!path.existsSync(__dirname + '/../browser/bundle.js')) { - require('../bin/bundle.js'); -} - -test('checkCookieHTTP', function (t) { - t.plan(3); - var port = Math.floor(1e4 + (Math.random() * 5e4 - 1e4)); - - var web = http.createServer(function (req, res) { - res.setHeader('set-cookie', [ 'foo=bar' ]); - - if (req.url === '/') { - res.setStatus(200); - res.setHeader('content-type', 'text/html'); - res.end('pow'); - } - }); - var server = dnode().listen(web); - - web.listen(port, function () { - var req = { - host : 'localhost', - port : port, - path : '/dnode.js', - }; - http.get(req, function (res) { - t.equal(res.statusCode, 200); - t.equal(res.headers['content-type'], 'text/javascript'); - t.deepEqual(res.headers['set-cookie'], [ 'foo=bar' ]); - - web.close(); - server.end(); - }); - }); -}); - -test('checkCookieExpress', function (t) { - t.plan(3); - var port = Math.floor(1e4 + (Math.random() * 5e4 - 1e4)); - - var app = express.createServer(); - app.use(function (req, res, next) { - res.setHeader('set-cookie', [ 'foo=bar' ]); - next(); - }); - - app.get('/', function (req, res) { - res.setStatus(200); - res.setHeader('content-type', 'text/html'); - res.end('pow'); - }); - - var server = dnode().listen(app); - - app.listen(port, function () { - var req = { - host : 'localhost', - port : port, - path : '/dnode.js', - }; - http.get(req, function (res) { - t.equal(res.statusCode, 200); - t.deepEqual(res.headers['set-cookie'], [ 'foo=bar' ]); - t.equal(res.headers['content-type'], 'text/javascript'); - - app.close(); - server.end(); - t.end(); - }); - }); -}); diff --git a/test/circular.js b/test/circular.js index fe68c97..447049d 100644 --- a/test/circular.js +++ b/test/circular.js @@ -17,7 +17,7 @@ test('circular refs', function (t) { }, }).listen(port); - server.on('ready', function () { + server.on('listening', function () { dnode.connect(port, function (remote, conn) { var obj = { a : 1, b : 2 }; obj.c = obj; diff --git a/test/double.js b/test/double.js index cf04079..7b1b253 100644 --- a/test/double.js +++ b/test/double.js @@ -15,7 +15,7 @@ test('double', function (t) { } }).listen(port); - server.on('ready', function () { + server.on('listening', function () { dnode.connect(port, function (remote, conn) { remote.z( function (x,f) { f(x * 2) }, diff --git a/test/emit.js b/test/emit.js index 66f64c7..d436baa 100644 --- a/test/emit.js +++ b/test/emit.js @@ -29,7 +29,7 @@ test('emit events', function (t) { }; }).listen(port); - server.on('ready', function () { + server.on('listening', function () { setTimeout(function () { var iv = setInterval(function () { publish('data', Math.floor(Math.random() * 100)); diff --git a/test/error.js b/test/error.js deleted file mode 100644 index 81de727..0000000 --- a/test/error.js +++ /dev/null @@ -1,102 +0,0 @@ -var dnode = require('../'); -var sys = require('sys'); -var test = require('tap').test; - -test('errors', function (t) { - t.plan(4); - var port = Math.floor(Math.random() * 40000 + 10000); - var errors = { server : [], client : [] }; - - var server = dnode(function (remote, conn) { - conn.on('error', function (err) { - errors.server.push(err); - }); - - this.one = function () { - throw 'string throw' - }; - - this.two = function () { - undefined.name - }; - - this.three = function () { - remote.pow(); - }; - }).listen(port); - - var ts = setTimeout(function () { - t.fail('server never ended'); - }, 5000); - - server.on('end', function () { - clearTimeout(ts); - t.deepEqual(errors.server[0], 'string throw'); - - try { undefined.name } - catch (refErr) { - process.nextTick(function () { - t.equal(refErr.message, errors.server[1].message); - t.equal(refErr.type, errors.server[1].type); - t.equal(errors.server.length, 2); - t.end(); - }); - } - }); - - server.on('ready', function () { - var client = dnode(function (client, conn) { - conn.on('error', function (err) { - errors.client.push(err); - }); - - conn.on('end', function () { - t.deepEqual(errors.client, [ 'Local error' ]); - }); - - this.pow = function () { - throw 'Local error'; - }; - }).connect(port, function (remote, conn) { - remote.one(); - remote.two(); - remote.three(); - - setTimeout(function () { - conn.end(); - server.end(); - server.close(); - }, 500); - }); - }); -}); - -test('refused', function (t) { - t.plan(2); - - var port = Math.floor(Math.random() * 40000 + 10000); - var client = dnode.connect(port, function (remote, conn) { - assert.fail('should have been refused, very unlikely'); - }); - - client.on('error', function (err) { - t.equal(err.code, 'ECONNREFUSED'); - t.equal(err.syscall, 'connect'); - t.end(); - }); -}); - -test('close same server twice shouldn\'t throw errors', function(t) { - var port = Math.floor(Math.random() * 40000 + 10000); - var server = dnode(); - server.on('ready', function() { - server.once('close', function() { - server.once('close', function() { - t.end(); - }) - server.close(); - }) - server.close(); - }); - server.listen(port); -}) diff --git a/test/lib/destroy.js b/test/lib/destroy.js new file mode 100644 index 0000000..780250f --- /dev/null +++ b/test/lib/destroy.js @@ -0,0 +1,6 @@ +module.exports = function (server) { + server.close(); + Object.keys(server.sessions).forEach(function (id) { + server.sessions[id].end(); + }); +}; diff --git a/test/middleware.js b/test/middleware.js index e93b12c..63073af 100644 --- a/test/middleware.js +++ b/test/middleware.js @@ -21,7 +21,7 @@ test('middleware', function (t) { t.ok(!conn.zing); t.ok(!client.moo); - conn.on('ready', (function () { + conn.on('remote', (function () { clearTimeout(tr); t.ok(conn.zing); t.ok(this.moo); @@ -30,18 +30,18 @@ test('middleware', function (t) { this.baz = 42; }).listen(port); - server.use(function (client, conn) { + server.on('local', function (client, conn) { conn.zing = true; }); - server.use(function (client, conn) { - this.moo = true; - conn.on('ready', function () { + server.on('local', function (client, conn) { + client.moo = true; + conn.on('remote', function () { clearTimeout(tc); }); }); - server.on('ready', function () { + server.on('listening', function () { dnode.connect(port, function (remote, conn) { clearTimeout(tf); t.ok(remote.baz); diff --git a/test/nested.js b/test/nested.js index f58d438..4e0babf 100644 --- a/test/nested.js +++ b/test/nested.js @@ -18,8 +18,8 @@ test('nested', function (t) { var moo = new EventEmitter; // Don't worry, real code does't look like this: - server1.on('ready', function () { - server2.on('ready', function () { + server1.on('listening', function () { + server2.on('listening', function () { dnode.connect(port, function (remote1, conn1) { dnode.connect(port + 1, function (remote2, conn2) { moo.on('hi', function (x) { diff --git a/test/null.js b/test/null.js index fd97bac..fa4ed97 100644 --- a/test/null.js +++ b/test/null.js @@ -18,7 +18,7 @@ test('null', function (t) { }, }).listen(port.toString()); // test for stringified ports too why not - server.on('ready', function () { + server.on('listening', function () { dnode.connect(port, function (remote, conn) { t.ok(conn.id); t.equal(conn.stream.remoteAddress, '127.0.0.1'); diff --git a/test/obj.js b/test/obj.js index a89ab10..5e9cc78 100644 --- a/test/obj.js +++ b/test/obj.js @@ -11,7 +11,7 @@ test('object ref tests', function (t) { getObject : function (f) { f(obj) }, }).listen(port); - server.on('ready', function () { + server.on('listening', function () { dnode.connect(port, function (remote, conn) { remote.getObject(function (rObj) { t.equal(rObj.a, 1); diff --git a/test/recon.js b/test/recon.js deleted file mode 100644 index 1e50388..0000000 --- a/test/recon.js +++ /dev/null @@ -1,44 +0,0 @@ -var dnode = require('../'); -var test = require('tap').test; - -test('recon', function (t) { - t.plan(4); - var port = Math.floor(Math.random() * 40000 + 10000); - - var scounts = { connect : 0, ready : 0 }; - var ccounts = { connect : 0, ready : 0 }; - - var server = dnode(function (remote, conn) { - scounts.connect ++; - conn.on('ready', function () { - scounts.ready ++; - }); - }).listen(port); - - dnode(function (remote, conn) { - ccounts.connect ++; - conn.on('ready', function () { - ccounts.ready ++; - setTimeout(function () { - if (ccounts.ready >= 4) { - conn.end(); - } - else { - conn.stream.end(); - } - }, 25); - }); - - }).connect(port, { reconnect : 100 }); - - setTimeout(function () { - t.ok(scounts.connect >= 2); - t.ok(scounts.ready >= 2); - - t.equal(ccounts.connect, scounts.connect); - t.equal(ccounts.ready, scounts.ready); - - server.close(); - t.end(); - }, 1000); -}); diff --git a/test/refs.js b/test/refs.js index 2641377..f31c5cc 100644 --- a/test/refs.js +++ b/test/refs.js @@ -10,7 +10,7 @@ test('refs', function (t) { b : 2, }).listen(port); - server.on('ready', function () { + server.on('listening', function () { dnode.connect(port, function (remote, conn) { conn.end(); server.close(); diff --git a/test/self-referential.js b/test/self-referential.js index 6f3b0f9..6e685ac 100644 --- a/test/self-referential.js +++ b/test/self-referential.js @@ -20,7 +20,7 @@ test('self-referential', function (t) { }, }).listen(port); - server.on('ready', function () { + server.on('listening', function () { dnode.connect(port, function (remote, conn) { t.equal(conn.stream.remoteAddress, '127.0.0.1'); var args = [1,2,3] diff --git a/test/simple.js b/test/simple.js index 58eb090..c83b987 100644 --- a/test/simple.js +++ b/test/simple.js @@ -17,7 +17,7 @@ test('simple', function (t) { }, }).listen(port.toString()); // test for stringified ports too why not - server.on('ready', function () { + server.on('listening', function () { dnode.connect(port, function (remote, conn) { t.ok(conn.id); t.equal(conn.stream.remoteAddress, '127.0.0.1'); diff --git a/test/single.js b/test/single.js index 00c6c17..5f9ffb8 100644 --- a/test/single.js +++ b/test/single.js @@ -1,5 +1,6 @@ var dnode = require('../') var test = require('tap').test; +var util = require('util'); test('simple', function (t) { t.plan(3); @@ -11,11 +12,11 @@ test('simple', function (t) { reply(n.number * 10); }, print : function (n,reply) { - reply(sys.inspect(n)); + reply(util.inspect(n)); }, }).listen(port); - server.on('ready', function () { + server.on('listening', function () { dnode.connect(port, function (remote, conn) { t.equal(conn.stream.remoteAddress, '127.0.0.1'); var args = { diff --git a/test/stream.js b/test/stream.js index 74a7815..de4c8b8 100644 --- a/test/stream.js +++ b/test/stream.js @@ -3,27 +3,31 @@ var net = require('net'); var test = require('tap').test; test('stream', function (t) { - t.plan(1); + t.plan(2); var port = Math.floor(Math.random() * 40000 + 10000); - var server = dnode({ - meow : function f (g) { g('cats') } + var server = net.createServer(function (stream) { + var d = dnode({ + meow : function f (g) { g('cats') } + }); + d.on('remote', function (remote) { + t.equal(remote.x, 5); + }); + stream.pipe(d).pipe(stream); }); + server.listen(port); - var netServer = net.createServer(); - server.listen(netServer); - - var times = 0; - netServer.listen(port, function () { - var netClient = net.createConnection(port); - dnode.connect(netClient, function (remote) { + server.on('listening', function () { + var d = dnode({ x : 5 }); + d.on('remote', function (remote) { remote.meow(function (cats) { t.equal(cats, 'cats'); - - netClient.end(); - netServer.close(); - t.end(); + server.close(); + d.end(); }); }); + + var stream = net.connect(port); + d.pipe(stream).pipe(d); }); }); diff --git a/test/tls.js b/test/tls.js index f44b343..1e06a3b 100644 --- a/test/tls.js +++ b/test/tls.js @@ -1,97 +1,82 @@ -// (A) dnode.listen(tls.createServer(options),cb) -// (B) dnode.listen(port,options,cb) -// (C) dnode.connect(tls.connect(port,options),cb) -// (D) dnode.connect(port,options,cb) -// -// (A) and (C) are examples for using servers/streams -// (B) and (D) are examples for using dnode to create server and client - var test = require('tap').test; var dnode = require('../'); var tls = require('tls'); var fs = require('fs'); -var ports = [3001,3002]; -var keyA = fs.readFileSync(__dirname+'/keys/agent1-key.pem'); -var certA = fs.readFileSync(__dirname+'/keys/agent1-cert.pem'); -var keyB = fs.readFileSync(__dirname+'/keys/agent1-key.pem'); -var certB = fs.readFileSync(__dirname+'/keys/agent1-cert.pem'); -var keyC = fs.readFileSync(__dirname+'/keys/agent2-key.pem'); -var certC = fs.readFileSync(__dirname+'/keys/agent2-cert.pem'); -var keyD = fs.readFileSync(__dirname+'/keys/agent2-key.pem'); -var certD = fs.readFileSync(__dirname+'/keys/agent2-cert.pem'); -var optionsA = { key:keyA, cert:certA, ca:[certC,certD] - , requestCert:true, rejectUnauthorized:true }; -var optionsB = { key:keyB, cert:certB, ca:[certC,certD] - , requestCert:true, rejectUnauthorized:true }; -var optionsC = { key:keyC, cert:certC }; -var optionsD = { key:keyD, cert:certD }; -var A = dnode({name:function(cb){cb('A')}}); -var B = dnode({name:function(cb){cb('B')}}); -var C = dnode({name:function(cb){cb('C')}}); -var D = dnode({name:function(cb){cb('D')}}); -test('tls', function (t) { - t.plan(4); - - var AC = setTimeout(function () { t.fail(); }, 500); - var AD = setTimeout(function () { t.fail(); }, 500); - var BC = setTimeout(function () { t.fail(); }, 500); - var BD = setTimeout(function () { t.fail(); }, 500); - var CA = setTimeout(function () { t.fail(); }, 500); - var CB = setTimeout(function () { t.fail(); }, 500); - var DA = setTimeout(function () { t.fail(); }, 500); - var DB = setTimeout(function () { t.fail(); }, 500); +var keys = { + A : fs.readFileSync(__dirname + '/keys/agent1-key.pem'), + B : fs.readFileSync(__dirname+'/keys/agent1-key.pem'), + C : fs.readFileSync(__dirname+'/keys/agent2-key.pem'), + D : fs.readFileSync(__dirname+'/keys/agent2-key.pem'), +}; - var tlsServer = tls.createServer(optionsA); +var certs = { + A : fs.readFileSync(__dirname+'/keys/agent1-cert.pem'), + B : fs.readFileSync(__dirname+'/keys/agent1-cert.pem'), + C : fs.readFileSync(__dirname+'/keys/agent2-cert.pem'), + D : fs.readFileSync(__dirname+'/keys/agent2-cert.pem'), +}; - A.listen(tlsServer, function (client, con) { - client.name(function(data){ - if (data == 'C') clearTimeout(CA); - if (data == 'D') clearTimeout(DA); - }); - }); - tlsServer.listen(ports[0], function () { - var tlsStream = tls.connect(ports[0], optionsC, function () { - C.connect(tlsStream, function(remote, con){ - remote.name(function (data) { - t.equal(data,'A'); - con.end(); - }); - }); - }); - D.connect(ports[0], optionsD, function (remote, con) { - remote.name(function (data) { - t.equal(data,'A'); - con.end(); +var options = { + A : { + key : keys.A, + cert : certs.A, + ca : [ certs.C, certs.D ], + requestCert : true, + //rejectUnauthorized : true, + }, + B : { + key : keys.B, + cert : certs.B, + ca : [ certs.C, certs.D ], + requestCert : true, + rejectUnauthorized : true, + }, + C : { key : keys.C, cert : certs.C }, +}; + +test('tls A', function (t) { + t.plan(4); + + var names = [ 'B', 'C' ]; + + var port = Math.floor(Math.random() * 40000 + 10000); + + var server = tls.createServer(options.A, function (stream) { + var A = dnode({ name : function (cb) { cb('A') } }); + A.on('remote', function (remote) { + remote.name(function (name) { + var ix = names.indexOf(name); + t.ok(ix >= 0); + names.splice(ix, 1); + A.end(); }); }); + A.pipe(stream).pipe(A); }); - - B.listen(ports[1], optionsB, function (client, con) { - client.name(function(data){ - if (data == 'C') clearTimeout(CB); - if (data == 'D') clearTimeout(DB); - }) - }).on('ready',function(){ - var tlsStream = tls.connect(ports[1], optionsC, function () { - C.connect(tlsStream, function(remote, con){ - remote.name(function (data) { - t.equal(data,'B'); - con.end(); - }); + server.listen(port); + + server.on('listening', function () { + var bs = tls.connect(port, options.B); + var B = dnode({ name : function (cb) { cb('B') } }); + B.on('remote', function (remote) { + remote.name(function (name) { + t.equal(name, 'A'); }); }); - D.connect(ports[1], optionsD, function (remote, con) { - remote.name(function (data) { - t.equal(data,'B'); - con.end(); + B.pipe(bs).pipe(B); + + var C = dnode({ name : function (cb) { cb('C') } }); + var cs = tls.connect(port, options.C); + C.on('remote', function (remote) { + remote.name(function (name) { + t.equal(name, 'A'); }); }); + C.pipe(cs).pipe(C); + }); + + t.on('end', function () { + server.close(); }); - - setTimeout(function () { - tlsServer.close(); - B.close(); - t.end(); - }, 500); }); diff --git a/test/unicode.js b/test/unicode.js index e99e73f..0171568 100644 --- a/test/unicode.js +++ b/test/unicode.js @@ -11,7 +11,7 @@ test('unicode', function (t) { } }).listen(port); - server.on('ready', function () { + server.on('listening', function () { dnode.connect(port, function (remote, conn) { t.equal(conn.stream.remoteAddress, '127.0.0.1'); remote.unicodes(function (str) { diff --git a/test/unix.js b/test/unix.js index 854d2d1..ecd91a0 100644 --- a/test/unix.js +++ b/test/unix.js @@ -10,7 +10,7 @@ test('unix', function (t) { var server = dnode({ f : function (cb) { cb(1337) } }).listen(sfile); - server.on('ready', function () { + server.on('listening', function () { dnode.connect(sfile, function (remote, conn) { remote.f(function (x) { t.equal(x, 1337);