├── .gitignore ├── index.js ├── .npmignore ├── .travis.yml ├── examples ├── workers │ ├── images │ │ ├── one.jpeg │ │ ├── two.jpeg │ │ └── three.jpeg │ ├── sink.js │ ├── consumer.js │ └── producer.js ├── pubsub │ ├── sub.js │ └── pub.js ├── pushpull │ ├── pull.js │ └── push.js ├── reqrep │ ├── rep.js │ └── req.js ├── emitter │ ├── pub.js │ └── sub.js ├── http │ ├── app.js │ └── server.js └── hwm │ └── index.js ├── test ├── test.bind-event.js ├── run ├── test.socket.close.js ├── test.reqrep.callback.js ├── test.reqrep.json.js ├── test.reqrep.js ├── test.reqrep.vargs.js ├── test.socket.connect.error.js ├── test.bind.ephemeral.js ├── test.reqrep.undefined.js ├── test.unixsocket.js ├── test.socket.accept.epipe.js ├── test.socket.connect.epipe.js ├── test.socket.accept.econnreset.js ├── test.socket.connect.econnreset.js ├── test.reqrep.error-reply.js ├── test.socket.accept.econnrefused.js ├── test.socket.connect.econnrefused.js ├── test.queue.js ├── test.socket.js ├── test.pushpull.json.js ├── test.reqrep.chain.js ├── test.arg-types.js ├── test.emitter.arg-types.js ├── test.pubsub.arg-types.js ├── test.reqrep.ordering.js ├── test.pushpull.multiple-pushers.js ├── test.pushpull.pull-bind.js ├── test.socket.rebind.js ├── test.socket.reconnect.js ├── test.pushpull.js ├── test.pubsub.string-subscriptions.js ├── test.pubsub.subscriptions.js ├── test.pubsub.missed-messages.js ├── test.hwm.js ├── test.pubsub.js ├── test.sub.matches.js ├── test.emitter.js ├── test.emitter.off.js ├── test.pubsub.multiple-subscribers.js ├── test.socket.error-remove-sock.js ├── test.emitter.wildcards.js ├── test.emitter.many-connect.js ├── test.emitter.many.js └── test.pubsub.unsubscribe.js ├── Makefile ├── lib ├── utils.js ├── sockets │ ├── pub-emitter.js │ ├── push.js │ ├── pull.js │ ├── pub.js │ ├── rep.js │ ├── sub-emitter.js │ ├── req.js │ ├── sub.js │ └── sock.js ├── plugins │ ├── round-robin.js │ └── queue.js └── index.js ├── benchmark ├── pub.js └── sub.js ├── package.json ├── LICENSE ├── History.md └── Readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | testing 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = require('./lib'); 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | examples 2 | Readme.md 3 | test 4 | testing 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.11.8" 4 | - "0.12" 5 | - "4" 6 | - "6" 7 | -------------------------------------------------------------------------------- /examples/workers/images/one.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tj/axon/HEAD/examples/workers/images/one.jpeg -------------------------------------------------------------------------------- /examples/workers/images/two.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tj/axon/HEAD/examples/workers/images/two.jpeg -------------------------------------------------------------------------------- /examples/workers/images/three.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tj/axon/HEAD/examples/workers/images/three.jpeg -------------------------------------------------------------------------------- /examples/pubsub/sub.js: -------------------------------------------------------------------------------- 1 | 2 | var axon = require('../..') 3 | , sock = axon.socket('sub'); 4 | 5 | sock.connect(3000); 6 | 7 | sock.on('message', function(msg){ 8 | console.log(msg.toString()); 9 | }); -------------------------------------------------------------------------------- /examples/pushpull/pull.js: -------------------------------------------------------------------------------- 1 | 2 | var axon = require('../..') 3 | , sock = axon.socket('pull'); 4 | 5 | sock.connect(3000); 6 | 7 | sock.on('message', function(msg){ 8 | console.log(msg.toString()); 9 | }); -------------------------------------------------------------------------------- /test/test.bind-event.js: -------------------------------------------------------------------------------- 1 | 2 | var axon = require('..') 3 | , should = require('should') 4 | , req = axon.socket('req') 5 | 6 | req.bind(0); 7 | 8 | req.on('bind', function(){ 9 | req.close(); 10 | }); 11 | -------------------------------------------------------------------------------- /test/run: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | echo 4 | for file in $@; do 5 | printf "\033[90m ${file#test/test.}\033[0m " 6 | node $file && printf "\033[36m✓\033[0m\n" 7 | test $? -eq 0 || exit $? 8 | done 9 | echo 10 | -------------------------------------------------------------------------------- /examples/reqrep/rep.js: -------------------------------------------------------------------------------- 1 | 2 | var axon = require('../..') 3 | , rep = axon.socket('rep'); 4 | 5 | rep.bind(3000); 6 | 7 | rep.on('message', function(msg, reply){ 8 | console.log('requested: %j', msg); 9 | reply({ goodbye: 'world' }); 10 | }); -------------------------------------------------------------------------------- /examples/pubsub/pub.js: -------------------------------------------------------------------------------- 1 | 2 | var axon = require('../..') 3 | , sock = axon.socket('pub'); 4 | 5 | sock.bind(3000); 6 | console.log('pub server started'); 7 | 8 | setInterval(function(){ 9 | console.log('sending'); 10 | sock.send('hello'); 11 | }, 500); 12 | -------------------------------------------------------------------------------- /examples/reqrep/req.js: -------------------------------------------------------------------------------- 1 | 2 | var axon = require('../..') 3 | , req = axon.socket('req'); 4 | 5 | req.connect(3000); 6 | 7 | setInterval(function(){ 8 | req.send({ hello: 'world' }, function(msg){ 9 | console.log('replied: %j', msg); 10 | }); 11 | }, 150); -------------------------------------------------------------------------------- /examples/pushpull/push.js: -------------------------------------------------------------------------------- 1 | 2 | var axon = require('../..') 3 | , sock = axon.socket('push'); 4 | 5 | sock.bind(3000); 6 | console.log('push server started'); 7 | 8 | setInterval(function(){ 9 | process.stdout.write('.'); 10 | sock.send('hello'); 11 | }, 150); 12 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | TESTS = $(wildcard test/test.*.js) 3 | PER_TICK=10 4 | SIZE=1024 5 | DURATION=5000 6 | 7 | bench: 8 | node benchmark/pub --size $(SIZE) --per-tick $(PER_TICK) --duration $(DURATION) & 9 | node benchmark/sub --size $(SIZE) --duration $(DURATION) 10 | 11 | test: 12 | @./test/run $(TESTS) 13 | 14 | .PHONY: test bench 15 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Slice helper. 4 | * 5 | * @api private 6 | * @param {Arguments} args 7 | * @return {Array} 8 | */ 9 | 10 | exports.slice = function(args){ 11 | var len = args.length; 12 | var ret = new Array(len); 13 | 14 | for (var i = 0; i < len; i++) { 15 | ret[i] = args[i]; 16 | } 17 | 18 | return ret; 19 | }; -------------------------------------------------------------------------------- /examples/emitter/pub.js: -------------------------------------------------------------------------------- 1 | 2 | var axon = require('../..') 3 | , sock = axon.socket('pub-emitter'); 4 | 5 | sock.bind(3000); 6 | console.log('bound to 3000'); 7 | 8 | setInterval(function(){ 9 | sock.emit('user:login', 'tobi'); 10 | setTimeout(function(){ 11 | sock.emit('user:logout', 'tobi'); 12 | }, Math.random() * 2000); 13 | }, 1000); -------------------------------------------------------------------------------- /test/test.socket.close.js: -------------------------------------------------------------------------------- 1 | 2 | var axon = require('..') 3 | , assert = require('better-assert'); 4 | 5 | var pull = axon.socket('pull'); 6 | 7 | var closed = false; 8 | 9 | pull.bind(4000, function(){ 10 | pull.close(function(){ 11 | closed = true; 12 | }); 13 | }); 14 | 15 | pull.on('close', function(){ 16 | setTimeout(function(){ 17 | assert(closed); 18 | }, 100); 19 | }); 20 | -------------------------------------------------------------------------------- /test/test.reqrep.callback.js: -------------------------------------------------------------------------------- 1 | 2 | var axon = require('..') 3 | , should = require('should'); 4 | 5 | var req = axon.socket('req') 6 | , rep = axon.socket('rep'); 7 | 8 | req.bind(4000); 9 | rep.connect(4000); 10 | 11 | rep.on('message', function(msg, reply){ 12 | reply('got "' + msg + '"', function(){ 13 | req.close(); 14 | rep.close(); 15 | }); 16 | }); 17 | 18 | req.send('hello'); 19 | -------------------------------------------------------------------------------- /test/test.reqrep.json.js: -------------------------------------------------------------------------------- 1 | 2 | var axon = require('..') 3 | , should = require('should') 4 | , req = axon.socket('req') 5 | , rep = axon.socket('rep'); 6 | 7 | req.bind(4000); 8 | rep.connect(4000); 9 | 10 | rep.on('message', function(obj, reply){ 11 | reply({ name: obj.name }); 12 | }); 13 | 14 | req.send({ name: 'Tobi' }, function(res){ 15 | res.should.eql({ name: 'Tobi' }); 16 | req.close(); 17 | rep.close(); 18 | }); -------------------------------------------------------------------------------- /test/test.reqrep.js: -------------------------------------------------------------------------------- 1 | 2 | var axon = require('..') 3 | , should = require('should'); 4 | 5 | var req = axon.socket('req') 6 | , rep = axon.socket('rep'); 7 | 8 | req.bind(4000); 9 | rep.connect(4000); 10 | 11 | rep.on('message', function(msg, reply){ 12 | reply('got "' + msg + '"'); 13 | }); 14 | 15 | req.send('hello', function(msg){ 16 | msg.toString().should.equal('got "hello"'); 17 | req.close(); 18 | rep.close(); 19 | }); -------------------------------------------------------------------------------- /test/test.reqrep.vargs.js: -------------------------------------------------------------------------------- 1 | 2 | var axon = require('..') 3 | , should = require('should'); 4 | 5 | var req = axon.socket('req') 6 | , rep = axon.socket('rep'); 7 | 8 | req.bind(4000); 9 | rep.connect(4000); 10 | 11 | rep.on('message', function(first, last, reply){ 12 | reply(first + ' ' + last) 13 | }); 14 | 15 | req.send('tobi', 'ferret', function(msg){ 16 | msg.toString().should.equal('tobi ferret'); 17 | req.close(); 18 | rep.close(); 19 | }); -------------------------------------------------------------------------------- /test/test.socket.connect.error.js: -------------------------------------------------------------------------------- 1 | 2 | var axon = require('..') 3 | , assert = require('better-assert'); 4 | 5 | var push = axon.socket('push') 6 | , pull = axon.socket('pull'); 7 | 8 | pull.bind(4000); 9 | push.connect(4000); 10 | 11 | push.on('error', function(err){ 12 | assert('boom' == err.message); 13 | push.close(); 14 | pull.close(); 15 | }); 16 | 17 | push.on('connect', function(){ 18 | push.socks[0]._destroy(new Error('boom')); 19 | }); -------------------------------------------------------------------------------- /test/test.bind.ephemeral.js: -------------------------------------------------------------------------------- 1 | 2 | var axon = require('..') 3 | , should = require('should') 4 | , req = axon.socket('req') 5 | , rep = axon.socket('rep') 6 | 7 | req.bind(0, function(){ 8 | rep.connect(req.address().string); 9 | }); 10 | 11 | rep.on('message', function(msg, reply){ 12 | reply('got "' + msg + '"'); 13 | }); 14 | 15 | req.send('hello', function(msg){ 16 | msg.toString().should.equal('got "hello"'); 17 | req.close(); 18 | rep.close(); 19 | }); -------------------------------------------------------------------------------- /test/test.reqrep.undefined.js: -------------------------------------------------------------------------------- 1 | 2 | var axon = require('..') 3 | , should = require('should') 4 | , req = axon.socket('req') 5 | , rep = axon.socket('rep') 6 | , assert = require('assert'); 7 | 8 | req.bind(4000); 9 | rep.connect(4000); 10 | 11 | rep.on('message', function(obj, reply){ 12 | reply(undefined); 13 | }); 14 | 15 | req.send({ name: 'Tobi' }, function(res){ 16 | assert(null === res, 'expected null'); 17 | req.close(); 18 | rep.close(); 19 | }); -------------------------------------------------------------------------------- /examples/workers/sink.js: -------------------------------------------------------------------------------- 1 | 2 | var axon = require('../..') 3 | , sock = axon.socket('pull') 4 | , fs = require('fs'); 5 | 6 | // bind 7 | 8 | sock.bind(3001); 9 | console.log('sink bound to 3001'); 10 | 11 | // save images 12 | 13 | sock.on('message', function(img){ 14 | var path = '/tmp/' + (Math.random() * 0xffffff | 0) + '.png'; 15 | fs.writeFile(path, img, function(err){ 16 | if (err) throw err; 17 | // ^ dont do this 18 | console.log('saved %s', path); 19 | }); 20 | }); -------------------------------------------------------------------------------- /test/test.unixsocket.js: -------------------------------------------------------------------------------- 1 | 2 | var axon = require('..') 3 | , should = require('should'); 4 | 5 | var req = axon.socket('req') 6 | , rep = axon.socket('rep'); 7 | 8 | var path = 'unix://' + process.cwd() + '/test.sock'; 9 | 10 | req.bind(path); 11 | rep.connect(path); 12 | 13 | rep.on('message', function(msg, reply){ 14 | reply('got "' + msg + '"'); 15 | }); 16 | 17 | req.send('hello', function(msg){ 18 | msg.toString().should.equal('got "hello"'); 19 | req.close(); 20 | rep.close(); 21 | }); -------------------------------------------------------------------------------- /test/test.socket.accept.epipe.js: -------------------------------------------------------------------------------- 1 | 2 | var axon = require('..') 3 | , assert = require('better-assert'); 4 | 5 | var push = axon.socket('push') 6 | , pull = axon.socket('pull'); 7 | 8 | push.bind(4000); 9 | pull.connect(4000); 10 | 11 | push.on('ignored error', function(err){ 12 | assert('EPIPE' == err.code); 13 | push.close(); 14 | pull.close(); 15 | }); 16 | 17 | push.on('connect', function(){ 18 | var err = new Error('faux EPIPE'); 19 | err.code = 'EPIPE'; 20 | push.socks[0]._destroy(err); 21 | }); -------------------------------------------------------------------------------- /test/test.socket.connect.epipe.js: -------------------------------------------------------------------------------- 1 | 2 | var axon = require('..') 3 | , assert = require('better-assert'); 4 | 5 | var push = axon.socket('push') 6 | , pull = axon.socket('pull'); 7 | 8 | pull.bind(4000); 9 | push.connect(4000); 10 | 11 | push.on('ignored error', function(err){ 12 | assert('EPIPE' == err.code); 13 | push.close(); 14 | pull.close(); 15 | }); 16 | 17 | push.on('connect', function(){ 18 | var err = new Error('faux EPIPE'); 19 | err.code = 'EPIPE'; 20 | push.socks[0]._destroy(err); 21 | }); -------------------------------------------------------------------------------- /examples/emitter/sub.js: -------------------------------------------------------------------------------- 1 | 2 | var axon = require('../..') 3 | , sock = axon.socket('sub-emitter'); 4 | 5 | sock.connect(3000); 6 | console.log('connected to 3000'); 7 | 8 | sock.on('user:login', function(user){ 9 | console.log('user %s logged in', user); 10 | }); 11 | 12 | sock.on('user:*', function(action, user){ 13 | console.log('user %s %s', user, action); 14 | }); 15 | 16 | sock.on('*', function(event, user){ 17 | console.log('got event %s %j', event, [].slice.call(arguments, 1)); 18 | console.log(); 19 | }); -------------------------------------------------------------------------------- /test/test.socket.accept.econnreset.js: -------------------------------------------------------------------------------- 1 | 2 | var axon = require('..') 3 | , assert = require('better-assert'); 4 | 5 | var push = axon.socket('push') 6 | , pull = axon.socket('pull'); 7 | 8 | push.bind(4000); 9 | pull.connect(4000); 10 | 11 | push.on('ignored error', function(err){ 12 | assert('ECONNRESET' == err.code); 13 | push.close(); 14 | pull.close(); 15 | }); 16 | 17 | push.on('connect', function(){ 18 | var err = new Error('faux ECONNRESET'); 19 | err.code = 'ECONNRESET'; 20 | push.socks[0]._destroy(err); 21 | }); -------------------------------------------------------------------------------- /test/test.socket.connect.econnreset.js: -------------------------------------------------------------------------------- 1 | 2 | var axon = require('..') 3 | , assert = require('better-assert'); 4 | 5 | var push = axon.socket('push') 6 | , pull = axon.socket('pull'); 7 | 8 | pull.bind(4000); 9 | push.connect(4000); 10 | 11 | push.on('ignored error', function(err){ 12 | assert('ECONNRESET' == err.code); 13 | push.close(); 14 | pull.close(); 15 | }); 16 | 17 | push.on('connect', function(){ 18 | var err = new Error('faux ECONNRESET'); 19 | err.code = 'ECONNRESET'; 20 | push.socks[0]._destroy(err); 21 | }); -------------------------------------------------------------------------------- /test/test.reqrep.error-reply.js: -------------------------------------------------------------------------------- 1 | 2 | var axon = require('..') 3 | , assert = require('better-assert'); 4 | 5 | var req = axon.socket('req') 6 | , rep = axon.socket('rep'); 7 | 8 | rep.bind(4000); 9 | req.connect(4000); 10 | 11 | rep.on('message', function(msg, reply){ 12 | setTimeout(function(){ 13 | assert(reply('ok') === false); 14 | rep.close(); 15 | }, 50); 16 | }); 17 | 18 | 19 | req.on('connect', function(){ 20 | req.send('hi', function(){}); 21 | setTimeout(function(){ 22 | req.close(); 23 | }, 25); 24 | }); -------------------------------------------------------------------------------- /test/test.socket.accept.econnrefused.js: -------------------------------------------------------------------------------- 1 | 2 | var axon = require('..') 3 | , assert = require('better-assert'); 4 | 5 | var push = axon.socket('push') 6 | , pull = axon.socket('pull'); 7 | 8 | push.bind(4000); 9 | pull.connect(4000); 10 | 11 | push.on('ignored error', function(err){ 12 | assert('ECONNREFUSED' == err.code); 13 | push.close(); 14 | pull.close(); 15 | }); 16 | 17 | push.on('connect', function(){ 18 | var err = new Error('faux ECONNREFUSED'); 19 | err.code = 'ECONNREFUSED'; 20 | push.socks[0]._destroy(err); 21 | }); -------------------------------------------------------------------------------- /test/test.socket.connect.econnrefused.js: -------------------------------------------------------------------------------- 1 | 2 | var axon = require('..') 3 | , assert = require('better-assert'); 4 | 5 | var push = axon.socket('push') 6 | , pull = axon.socket('pull'); 7 | 8 | pull.bind(4000); 9 | push.connect(4000); 10 | 11 | push.on('ignored error', function(err){ 12 | assert('ECONNREFUSED' == err.code); 13 | push.close(); 14 | pull.close(); 15 | }); 16 | 17 | push.on('connect', function(){ 18 | var err = new Error('faux ECONNREFUSED'); 19 | err.code = 'ECONNREFUSED'; 20 | push.socks[0]._destroy(err); 21 | }); -------------------------------------------------------------------------------- /test/test.queue.js: -------------------------------------------------------------------------------- 1 | 2 | var ss = require('../') 3 | , should = require('should'); 4 | 5 | var push = ss.socket('push') 6 | , pull = ss.socket('pull'); 7 | 8 | // .queue testing 9 | 10 | var pending = 3; 11 | 12 | push.bind(4000); 13 | push.send('foo'); 14 | push.send('bar'); 15 | push.send('baz'); 16 | 17 | push.queue.should.eql([['foo'], ['bar'], ['baz']]); 18 | 19 | pull.connect(4000); 20 | pull.on('message', function(msg){ 21 | push.queue.should.eql([]); 22 | --pending || (function(){ 23 | push.close(); 24 | pull.close(); 25 | })(); 26 | }); 27 | -------------------------------------------------------------------------------- /test/test.socket.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var axon = require('../') 7 | , assert = require('better-assert'); 8 | 9 | // socket types 10 | 11 | assert(axon.socket('pub') instanceof axon.PubSocket); 12 | assert(axon.socket('sub') instanceof axon.SubSocket); 13 | assert(axon.socket('push') instanceof axon.PushSocket); 14 | assert(axon.socket('pull') instanceof axon.PullSocket); 15 | assert(axon.socket('sub-emitter') instanceof axon.SubEmitterSocket); 16 | assert(axon.socket('pub-emitter') instanceof axon.PubEmitterSocket); 17 | 18 | process.exit(); 19 | -------------------------------------------------------------------------------- /examples/http/app.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var axon = require('../..') 7 | , sock = axon.socket('rep'); 8 | 9 | // try $ curl -d '{"foo":"bar"}' http://localhost:3000/ 10 | 11 | sock.on('message', function(msg, reply){ 12 | console.log('%s %s', msg.url, msg.method); 13 | switch (msg.url) { 14 | case '/': 15 | reply({ 16 | header: { 'content-type': 'text/plain' }, 17 | status: 200, 18 | body: 'thanks!' 19 | }) 20 | break; 21 | } 22 | }); 23 | 24 | sock.connect(4000); 25 | console.log('connected to 4000'); -------------------------------------------------------------------------------- /lib/sockets/pub-emitter.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var PubSocket = require('./pub'); 7 | 8 | /** 9 | * Expose `SubPubEmitterSocket`. 10 | */ 11 | 12 | module.exports = PubEmitterSocket; 13 | 14 | /** 15 | * Initialzie a new `PubEmitterSocket`. 16 | * 17 | * @api private 18 | */ 19 | 20 | function PubEmitterSocket() { 21 | this.sock = new PubSocket; 22 | this.emit = this.sock.send.bind(this.sock); 23 | this.bind = this.sock.bind.bind(this.sock); 24 | this.connect = this.sock.connect.bind(this.sock); 25 | this.close = this.sock.close.bind(this.sock); 26 | } 27 | -------------------------------------------------------------------------------- /examples/workers/consumer.js: -------------------------------------------------------------------------------- 1 | 2 | // $ npm install resize 3 | 4 | var axon = require('../..') 5 | , sock = axon.socket('pull') 6 | , sink = axon.socket('push') 7 | , resize = require('resize') 8 | , fs = require('fs'); 9 | 10 | // connect 11 | 12 | sock.connect(3000); 13 | sink.connect(3001); 14 | console.log('consumer connected to 3000'); 15 | 16 | // resize 17 | 18 | sock.on('message', function(img){ 19 | console.log('resizing %dkb image', img.length / 1024 | 0); 20 | resize(img, 100, 100, function(err, buf){ 21 | if (err) throw err; 22 | // ^ dont do this 23 | sink.send(buf); 24 | }); 25 | }); -------------------------------------------------------------------------------- /test/test.pushpull.json.js: -------------------------------------------------------------------------------- 1 | 2 | var ss = require('../') 3 | , should = require('should'); 4 | 5 | var push = ss.socket('push') 6 | , pull = ss.socket('pull'); 7 | 8 | // basic 1-1 push/pull 9 | 10 | var n = 0; 11 | 12 | push.bind(4000); 13 | 14 | push.send({ path: '/tmp/foo.png' }); 15 | push.send({ path: '/tmp/bar.png' }); 16 | push.send({ path: '/tmp/baz.png' }); 17 | 18 | var strs = ['foo', 'bar', 'baz']; 19 | 20 | pull.connect(4000); 21 | pull.on('message', function(msg){ 22 | msg.should.have.property('path', '/tmp/' + strs[n++] + '.png'); 23 | if (n == 3) { 24 | push.close(); 25 | pull.close(); 26 | } 27 | }); -------------------------------------------------------------------------------- /examples/workers/producer.js: -------------------------------------------------------------------------------- 1 | 2 | var axon = require('../..') 3 | , sock = axon.socket('push') 4 | , fs = require('fs') 5 | , read = fs.readFileSync; 6 | 7 | // images 8 | 9 | var images = [ 10 | read(__dirname + '/images/one.jpeg'), 11 | read(__dirname + '/images/two.jpeg'), 12 | read(__dirname + '/images/three.jpeg'), 13 | ]; 14 | 15 | // bind 16 | 17 | sock.bind(3000); 18 | console.log('producer bound to 3000'); 19 | 20 | // send random images 21 | 22 | setInterval(function(){ 23 | var img = images[Math.random() * images.length | 0]; 24 | console.log('sending %dkb image', img.length / 1024 | 0); 25 | sock.send(img); 26 | }, 200); 27 | -------------------------------------------------------------------------------- /lib/sockets/push.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var roundrobin = require('../plugins/round-robin'); 7 | var queue = require('../plugins/queue'); 8 | var Socket = require('./sock'); 9 | 10 | /** 11 | * Expose `PushSocket`. 12 | */ 13 | 14 | module.exports = PushSocket; 15 | 16 | /** 17 | * Initialize a new `PushSocket`. 18 | * 19 | * @api private 20 | */ 21 | 22 | function PushSocket() { 23 | Socket.call(this); 24 | this.use(queue()); 25 | this.use(roundrobin({ fallback: this.enqueue })); 26 | } 27 | 28 | /** 29 | * Inherits from `Socket.prototype`. 30 | */ 31 | 32 | PushSocket.prototype.__proto__ = Socket.prototype; -------------------------------------------------------------------------------- /lib/sockets/pull.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var Socket = require('./sock'); 7 | 8 | /** 9 | * Expose `PullSocket`. 10 | */ 11 | 12 | module.exports = PullSocket; 13 | 14 | /** 15 | * Initialize a new `PullSocket`. 16 | * 17 | * @api private 18 | */ 19 | 20 | function PullSocket() { 21 | Socket.call(this); 22 | // TODO: selective reception 23 | } 24 | 25 | /** 26 | * Inherits from `Socket.prototype`. 27 | */ 28 | 29 | PullSocket.prototype.__proto__ = Socket.prototype; 30 | 31 | /** 32 | * Pull sockets should not send messages. 33 | */ 34 | 35 | PullSocket.prototype.send = function(){ 36 | throw new Error('pull sockets should not send messages'); 37 | }; 38 | -------------------------------------------------------------------------------- /test/test.reqrep.chain.js: -------------------------------------------------------------------------------- 1 | 2 | var axon = require('..') 3 | , should = require('should'); 4 | 5 | var req = axon.socket('req') 6 | , rep = axon.socket('rep') 7 | , req2 = axon.socket('req') 8 | , rep2 = axon.socket('rep'); 9 | 10 | req.bind(4000); 11 | rep.connect(4000); 12 | 13 | req2.bind(4445); 14 | rep2.connect(4445); 15 | 16 | rep.on('message', function(msg, reply){ 17 | req2.send(msg, function(msg){ 18 | reply('got "' + msg + '"'); 19 | }); 20 | }); 21 | 22 | req.send('hello', function(msg){ 23 | msg.toString().should.equal('got "HELLO"'); 24 | req.close(); 25 | rep.close(); 26 | req2.close(); 27 | rep2.close(); 28 | }); 29 | 30 | rep2.on('message', function(msg, reply){ 31 | reply(msg.toString().toUpperCase()); 32 | }); -------------------------------------------------------------------------------- /test/test.arg-types.js: -------------------------------------------------------------------------------- 1 | 2 | var ss = require('..'); 3 | var should = require('should'); 4 | var assert = require('assert'); 5 | 6 | var push = ss.socket('push'); 7 | var pull = ss.socket('pull'); 8 | 9 | // arg type checks 10 | 11 | var n = 0; 12 | var done; 13 | 14 | push.bind(4000); 15 | push.send('foo', { bar: 'baz' }, ['some', 1], new Buffer('hello')); 16 | 17 | pull.connect(4000); 18 | pull.on('message', function(a, b, c, d){ 19 | assert('string' == typeof a); 20 | b.should.eql({ bar: 'baz' }); 21 | c.should.eql(['some', 1]); 22 | assert(Buffer.isBuffer(d)); 23 | assert('hello' == d.toString()); 24 | 25 | pull.close(); 26 | push.close(); 27 | done = true; 28 | }); 29 | 30 | process.on('exit', function(){ 31 | assert(done); 32 | }); -------------------------------------------------------------------------------- /test/test.emitter.arg-types.js: -------------------------------------------------------------------------------- 1 | 2 | var axon = require('..'); 3 | var should = require('should'); 4 | var assert = require('assert'); 5 | 6 | var pub = axon.socket('pub-emitter'); 7 | var sub = axon.socket('sub-emitter'); 8 | 9 | // arg type checks 10 | 11 | var done = false; 12 | 13 | pub.bind(4000); 14 | 15 | setTimeout(function() { 16 | pub.emit('foo', { bar: 'baz' }, ['some', 1], new Buffer('hello')); 17 | }, 50); 18 | 19 | sub.connect(4000); 20 | sub.on('foo', function(a, b, c){ 21 | assert(this instanceof axon.SubEmitterSocket); 22 | a.should.eql({ bar: 'baz' }); 23 | b.should.eql(['some', 1]); 24 | assert(Buffer.isBuffer(c)); 25 | assert('hello' == c.toString()); 26 | 27 | sub.close(); 28 | pub.close(); 29 | done = true; 30 | }); 31 | 32 | process.on('exit', function(){ 33 | assert(done); 34 | }); 35 | -------------------------------------------------------------------------------- /test/test.pubsub.arg-types.js: -------------------------------------------------------------------------------- 1 | 2 | var axon = require('..'); 3 | var should = require('should'); 4 | var assert = require('assert'); 5 | 6 | var pub = axon.socket('pub'); 7 | var sub = axon.socket('sub'); 8 | 9 | // arg type checks 10 | 11 | var done = false; 12 | 13 | pub.bind(4000); 14 | 15 | setTimeout(function() { 16 | pub.send('foo', { bar: 'baz' }, ['some', 1], new Buffer('hello')); 17 | }, 50); 18 | 19 | sub.connect(4000); 20 | sub.on('message', function(a, b, c, d){ 21 | assert(this instanceof axon.SubSocket); 22 | assert('string' == typeof a); 23 | b.should.eql({ bar: 'baz' }); 24 | c.should.eql(['some', 1]); 25 | assert(Buffer.isBuffer(d)); 26 | assert('hello' == d.toString()); 27 | 28 | sub.close(); 29 | pub.close(); 30 | done = true; 31 | }); 32 | 33 | process.on('exit', function(){ 34 | assert(done); 35 | }); 36 | -------------------------------------------------------------------------------- /test/test.reqrep.ordering.js: -------------------------------------------------------------------------------- 1 | 2 | var axon = require('..') 3 | , should = require('should'); 4 | 5 | var req = axon.socket('req') 6 | , rep = axon.socket('rep'); 7 | 8 | req.bind(4000); 9 | rep.connect(4000); 10 | 11 | var pending = 10 12 | , n = pending 13 | , msgs = []; 14 | 15 | rep.on('message', function(msg, reply){ 16 | reply('got "' + msg + '"'); 17 | }); 18 | 19 | while (n--) { 20 | (function(n){ 21 | n = String(n); 22 | setTimeout(function(){ 23 | req.send(n, function(msg){ 24 | msgs.push(msg.toString()); 25 | --pending || done(); 26 | }); 27 | }, Math.random() * 200 | 0); 28 | })(n); 29 | } 30 | 31 | function done() { 32 | msgs.should.have.length(10); 33 | for (var i = 0; i < 10; ++i) { 34 | msgs.should.containEql('got "' + i + '"'); 35 | } 36 | req.close(); 37 | rep.close(); 38 | } 39 | -------------------------------------------------------------------------------- /test/test.pushpull.multiple-pushers.js: -------------------------------------------------------------------------------- 1 | 2 | var ss = require('../') 3 | , should = require('should'); 4 | 5 | // multiple pushers 6 | 7 | var pusher1 = ss.socket('push'); 8 | var pusher2 = ss.socket('push'); 9 | var pusher3 = ss.socket('push'); 10 | 11 | pusher1.bind(4000); 12 | pusher2.bind(4445); 13 | pusher3.bind(4446); 14 | 15 | pusher1.send('hey'); 16 | pusher2.send('hey'); 17 | pusher3.send('hey'); 18 | 19 | // one puller that connects to many pushers 20 | 21 | var pull = ss.socket('pull'); 22 | 23 | pull.connect(4000); 24 | pull.connect(4445); 25 | pull.connect(4446); 26 | 27 | var msgs = []; 28 | 29 | pull.on('message', function(msg){ 30 | var n = msgs.push(msg.toString()); 31 | if (n == 3) { 32 | msgs.join(' ').should.equal('hey hey hey'); 33 | pusher1.close(); 34 | pusher2.close(); 35 | pusher3.close(); 36 | pull.close(); 37 | } 38 | }); 39 | -------------------------------------------------------------------------------- /benchmark/pub.js: -------------------------------------------------------------------------------- 1 | 2 | var ss = require('..') 3 | , program = require('commander'); 4 | 5 | program 6 | .option('-T, --type ', 'socket type [pub]', 'pub') 7 | .option('-t, --per-tick ', 'messages per tick [1000]', parseInt) 8 | .option('-s, --size ', 'message size in bytes [1024]', parseInt) 9 | .option('-d, --duration ', 'duration of test [5000]', parseInt) 10 | .parse(process.argv) 11 | 12 | var sock = ss.socket(program.type); 13 | sock.bind(3000); 14 | sock.on('disconnect', process.exit); 15 | console.log('pub bound'); 16 | 17 | var perTick = program.perTick || 1000; 18 | var buf = new Buffer(Array(program.size || 1024).join('a')); 19 | console.log('sending %d per tick', perTick); 20 | console.log('sending %d byte messages', buf.length); 21 | 22 | function more() { 23 | for (var i = 0; i < perTick; ++i) sock.send(buf); 24 | setImmediate(more); 25 | } 26 | 27 | more(); 28 | -------------------------------------------------------------------------------- /test/test.pushpull.pull-bind.js: -------------------------------------------------------------------------------- 1 | 2 | var ss = require('../') 3 | , should = require('should'); 4 | 5 | var push = ss.socket('push') 6 | , pull = ss.socket('pull'); 7 | 8 | // basic 1-1 push/pull bind pull 9 | 10 | var n = 0 11 | , closed; 12 | 13 | pull.bind(4000); 14 | push.connect(4000); 15 | push.send('foo'); 16 | push.send('bar'); 17 | 18 | pull.on('message', function(msg){ 19 | msg.should.have.length(3); 20 | msg = msg.toString(); 21 | switch (n++) { 22 | case 0: 23 | msg.should.equal('foo'); 24 | break; 25 | case 1: 26 | msg.should.equal('bar'); 27 | break; 28 | case 2: 29 | msg.should.equal('baz'); 30 | pull.close(); 31 | push.close(); 32 | closed = true; 33 | break; 34 | } 35 | }); 36 | 37 | push.on('connect', function(){ 38 | push.send('baz'); 39 | }); 40 | 41 | process.on('exit', function(){ 42 | should.equal(true, closed); 43 | }); -------------------------------------------------------------------------------- /test/test.socket.rebind.js: -------------------------------------------------------------------------------- 1 | 2 | var ss = require('../') 3 | , should = require('should') 4 | , assert = require('assert'); 5 | 6 | var push = ss.socket('push') 7 | , pull = ss.socket('pull'); 8 | 9 | // basic 1-1 push/pull 10 | 11 | var msgs = [] 12 | , n = 0; 13 | 14 | push.bind(4000); 15 | pull.connect(4000); 16 | 17 | var id = setInterval(function(){ 18 | push.send(String(n++)); 19 | }, 2); 20 | 21 | pull.on('message', function(msg){ 22 | msgs.push(msg.toString()); 23 | 24 | switch (msgs.length) { 25 | case 10: 26 | push.close(); 27 | push.once('close', function(){ 28 | setTimeout(function(){ 29 | push.bind(4000); 30 | }, 50); 31 | }); 32 | break; 33 | case 300: 34 | for (var i = 0; i < 299; ++i) { 35 | msgs[i].should.equal(i.toString()); 36 | } 37 | 38 | clearInterval(id); 39 | 40 | push.close(); 41 | pull.close(); 42 | break; 43 | } 44 | }); -------------------------------------------------------------------------------- /lib/sockets/pub.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var Socket = require('./sock'); 7 | var slice = require('../utils').slice; 8 | 9 | /** 10 | * Expose `PubSocket`. 11 | */ 12 | 13 | module.exports = PubSocket; 14 | 15 | /** 16 | * Initialize a new `PubSocket`. 17 | * 18 | * @api private 19 | */ 20 | 21 | function PubSocket() { 22 | Socket.call(this); 23 | } 24 | 25 | /** 26 | * Inherits from `Socket.prototype`. 27 | */ 28 | 29 | PubSocket.prototype.__proto__ = Socket.prototype; 30 | 31 | /** 32 | * Send `msg` to all established peers. 33 | * 34 | * @param {Mixed} msg 35 | * @api public 36 | */ 37 | 38 | PubSocket.prototype.send = function(msg){ 39 | var socks = this.socks; 40 | var len = socks.length; 41 | var sock; 42 | 43 | var buf = this.pack(arguments); 44 | 45 | for (var i = 0; i < len; i++) { 46 | sock = socks[i]; 47 | if (sock.writable) sock.write(buf); 48 | } 49 | 50 | return this; 51 | }; 52 | -------------------------------------------------------------------------------- /test/test.socket.reconnect.js: -------------------------------------------------------------------------------- 1 | 2 | var ss = require('../') 3 | , should = require('should') 4 | , assert = require('assert'); 5 | 6 | var push = ss.socket('push') 7 | , pull = ss.socket('pull'); 8 | 9 | // basic 1-1 push/pull 10 | 11 | var msgs = [] 12 | , n = 0; 13 | 14 | push.bind(4000); 15 | pull.connect(4000); 16 | 17 | var id = setInterval(function(){ 18 | push.send(String(n++)); 19 | }, 5); 20 | 21 | pull.on('message', function(msg){ 22 | msgs.push(msg.toString()); 23 | 24 | switch (msgs.length) { 25 | case 10: 26 | pull.close(); 27 | pull.once('close', function(){ 28 | setTimeout(function(){ 29 | pull.connect(4000); 30 | }, 50); 31 | }); 32 | break; 33 | case 300: 34 | for (var i = 0; i < 299; ++i) { 35 | msgs[i].should.equal(i.toString()); 36 | } 37 | 38 | clearInterval(id); 39 | 40 | push.close(); 41 | pull.close(); 42 | break; 43 | } 44 | }); -------------------------------------------------------------------------------- /test/test.pushpull.js: -------------------------------------------------------------------------------- 1 | 2 | var ss = require('../') 3 | , should = require('should') 4 | , assert = require('assert'); 5 | 6 | var push = ss.socket('push') 7 | , pull = ss.socket('pull'); 8 | 9 | // basic 1-1 push/pull 10 | 11 | var n = 0 12 | , closed; 13 | 14 | push.bind(4000); 15 | push.send('foo'); 16 | push.send('bar'); 17 | 18 | pull.connect(4000); 19 | pull.on('message', function(msg){ 20 | assert('string' == typeof msg); 21 | msg.should.have.length(3); 22 | msg = msg.toString(); 23 | switch (n++) { 24 | case 0: 25 | msg.should.equal('foo'); 26 | break; 27 | case 1: 28 | msg.should.equal('bar'); 29 | break; 30 | case 2: 31 | msg.should.equal('baz'); 32 | pull.close(); 33 | push.close(); 34 | closed = true; 35 | break; 36 | } 37 | }); 38 | 39 | pull.on('connect', function(){ 40 | push.send('baz'); 41 | }); 42 | 43 | process.on('exit', function(){ 44 | should.equal(true, closed); 45 | }); -------------------------------------------------------------------------------- /test/test.pubsub.string-subscriptions.js: -------------------------------------------------------------------------------- 1 | 2 | var axon = require('..') 3 | , should = require('should') 4 | , pub = axon.socket('pub') 5 | , sub = axon.socket('sub') 6 | 7 | pub.bind(4000); 8 | 9 | sub.subscribe('user:*'); 10 | sub.subscribe('page:view'); 11 | 12 | pub.on('connect', function() { 13 | pub.send('user:login', 'tobi'); 14 | pub.send('user:login', 'loki'); 15 | pub.send('user:logout', 'jane'); 16 | pub.send('unrelated', 'message'); 17 | pub.send('other', 'message'); 18 | pub.send('page:view', '/home'); 19 | }); 20 | 21 | sub.connect(4000); 22 | 23 | var msgs = []; 24 | sub.on('message', function(type, name){ 25 | msgs.push(type, name); 26 | if ('page:view' == type) { 27 | msgs.map(String).should.eql(expected); 28 | pub.close(); 29 | sub.close(); 30 | } 31 | }); 32 | 33 | var expected = [ 34 | 'user:login', 35 | 'tobi', 36 | 'user:login', 37 | 'loki', 38 | 'user:logout', 39 | 'jane', 40 | 'page:view', 41 | '/home' 42 | ]; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "axon", 3 | "description": "High-level messaging & socket patterns implemented in pure js", 4 | "version": "2.0.3", 5 | "author": "TJ Holowaychuk ", 6 | "dependencies": { 7 | "debug": "*", 8 | "configurable": "0.0.1", 9 | "escape-regexp": "0.0.1", 10 | "amp-message": "~0.1.1", 11 | "amp": "~0.3.1" 12 | }, 13 | "devDependencies": { 14 | "better-assert": "~1.0.1", 15 | "commander": "~2.4.0", 16 | "humanize-number": "0.0.1", 17 | "mocha": "~1.21.5", 18 | "should": "~4.1.0" 19 | }, 20 | "keywords": [ 21 | "zmq", 22 | "zeromq", 23 | "pubsub", 24 | "socket", 25 | "emitter", 26 | "ipc", 27 | "rpc" 28 | ], 29 | "engines": { 30 | "node": ">= 0.11.8" 31 | }, 32 | "repository": { 33 | "type": "git", 34 | "url": "https://github.com/visionmedia/axon.git" 35 | }, 36 | "scripts": { 37 | "test": "make test" 38 | }, 39 | "license": "MIT" 40 | } 41 | -------------------------------------------------------------------------------- /test/test.pubsub.subscriptions.js: -------------------------------------------------------------------------------- 1 | 2 | var axon = require('..') 3 | , should = require('should') 4 | , pub = axon.socket('pub') 5 | , sub = axon.socket('sub') 6 | 7 | pub.bind(4000); 8 | 9 | sub.subscribe(/^user:.+$/); 10 | sub.subscribe(/^page:view$/); 11 | 12 | pub.on('connect', function() { 13 | pub.send('user:login', 'tobi'); 14 | pub.send('user:login', 'loki'); 15 | pub.send('user:logout', 'jane'); 16 | pub.send('unrelated', 'message'); 17 | pub.send('other', 'message'); 18 | pub.send('page:view', '/home'); 19 | }); 20 | 21 | sub.connect(4000); 22 | 23 | var msgs = []; 24 | sub.on('message', function(type, name){ 25 | msgs.push(type, name); 26 | if ('page:view' == type) { 27 | msgs.map(String).should.eql(expected); 28 | pub.close(); 29 | sub.close(); 30 | } 31 | }); 32 | 33 | var expected = [ 34 | 'user:login', 35 | 'tobi', 36 | 'user:login', 37 | 'loki', 38 | 'user:logout', 39 | 'jane', 40 | 'page:view', 41 | '/home' 42 | ]; 43 | -------------------------------------------------------------------------------- /examples/http/server.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var axon = require('../..') 7 | , http = require('http') 8 | 9 | // http server 10 | 11 | var server = http.createServer(function(req, res){ 12 | var buf = ''; 13 | 14 | // buffer body for this example 15 | req.on('data', function(chunk){ buf += chunk }); 16 | req.on('end', function(){ 17 | // construct json message 18 | var msg = { 19 | url: req.url, 20 | method: req.method, 21 | header: req.headers, 22 | body: buf 23 | }; 24 | 25 | // distribute between N ./app nodes 26 | sock.send(msg, function(msg){ 27 | res.statusCode = msg.status; 28 | Object.keys(msg.header).forEach(function(field){ 29 | res.setHeader(field, msg.header[field]); 30 | }); 31 | res.end(msg.body); 32 | }); 33 | 34 | }); 35 | }).listen(3000); 36 | 37 | // socket 38 | 39 | var sock = axon.socket('req'); 40 | sock.bind(4000); 41 | 42 | console.log('listening on port 3000'); -------------------------------------------------------------------------------- /test/test.pubsub.missed-messages.js: -------------------------------------------------------------------------------- 1 | // 2 | // var ss = require('../') 3 | // , should = require('should'); 4 | // 5 | // var pub = ss.socket('pub') 6 | // , sub = ss.socket('sub'); 7 | // 8 | // var n = 0; 9 | // 10 | // // test basic 1-1 pub/sub with missed messages 11 | // 12 | // pub.bind(3333, function(){ 13 | // pub.send('foo'); 14 | // pub.send('bar'); 15 | // sub.connect(3333, function(){ 16 | // sub.on('message', function(msg){ 17 | // msg.should.be.an.instanceof(Buffer); 18 | // msg.should.have.length(3); 19 | // msg = msg.toString(); 20 | // switch (n++) { 21 | // case 0: 22 | // msg.should.equal('baz'); 23 | // break; 24 | // case 1: 25 | // msg.should.equal('raz'); 26 | // pub.close(); 27 | // sub.close(); 28 | // break; 29 | // } 30 | // }); 31 | // 32 | // setTimeout(function(){ 33 | // pub.send('baz'); 34 | // pub.send('raz'); 35 | // }, 20); 36 | // }); 37 | // }); 38 | // 39 | -------------------------------------------------------------------------------- /test/test.hwm.js: -------------------------------------------------------------------------------- 1 | 2 | var axon = require('../') 3 | , should = require('should'); 4 | 5 | var push = axon.socket('push') 6 | , pull = axon.socket('pull'); 7 | 8 | push.set('hwm', 5); 9 | push.connect(3333); 10 | 11 | push.send('1'); 12 | push.send('2'); 13 | push.send('3'); 14 | push.send('4'); 15 | push.send('5'); 16 | 17 | // check that messages are dropped 18 | 19 | push.once('drop', function(msg){ 20 | msg.toString().should.equal('6'); 21 | 22 | push.once('drop', function(msg){ 23 | msg.toString().should.equal('7'); 24 | 25 | pull.bind(3333); 26 | push.once('flush', function(buf){ 27 | buf.should.eql([['1'], ['2'], ['3'], ['4'], ['5']]); 28 | push.send('8'); 29 | 30 | var vals = []; 31 | pull.on('message', function(msg){ 32 | vals.push(msg.toString()); 33 | if ('8' == msg.toString()) { 34 | vals.should.eql(['1', '2', '3', '4', '5', '8']); 35 | push.close(); 36 | pull.close(); 37 | } 38 | }); 39 | }); 40 | }); 41 | 42 | push.send('7'); 43 | }); 44 | 45 | push.send('6'); 46 | -------------------------------------------------------------------------------- /test/test.pubsub.js: -------------------------------------------------------------------------------- 1 | 2 | var ss = require('../') 3 | , should = require('should') 4 | , assert = require('assert'); 5 | 6 | var pub = ss.socket('pub') 7 | , sub = ss.socket('sub'); 8 | 9 | var n = 0 10 | , closed; 11 | 12 | // test basic 1-1 pub/sub 13 | 14 | pub.bind(4000, function(){ 15 | sub.connect(4000, function(){ 16 | sub.on('message', function(msg){ 17 | assert('string' == typeof msg); 18 | msg.should.have.length(3); 19 | msg = msg.toString(); 20 | switch (n++) { 21 | case 0: 22 | msg.should.equal('foo'); 23 | break; 24 | case 1: 25 | msg.should.equal('bar'); 26 | break; 27 | case 2: 28 | msg.should.equal('baz'); 29 | pub.close(); 30 | sub.close(); 31 | closed = true; 32 | break; 33 | } 34 | }); 35 | 36 | setTimeout(function(){ 37 | pub.send('foo'); 38 | pub.send('bar'); 39 | pub.send('baz'); 40 | }, 20); 41 | }); 42 | }); 43 | 44 | process.on('exit', function(){ 45 | assert(closed); 46 | }); -------------------------------------------------------------------------------- /lib/plugins/round-robin.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Deps. 4 | */ 5 | 6 | var slice = require('../utils').slice; 7 | 8 | /** 9 | * Round-robin plugin. 10 | * 11 | * Provides a `send` method which will 12 | * write the `msg` to all connected peers. 13 | * 14 | * @param {Object} options 15 | * @api private 16 | */ 17 | 18 | module.exports = function(options){ 19 | options = options || {}; 20 | var fallback = options.fallback || function(){}; 21 | 22 | return function(sock){ 23 | 24 | /** 25 | * Bind callback to `sock`. 26 | */ 27 | 28 | fallback = fallback.bind(sock); 29 | 30 | /** 31 | * Initialize counter. 32 | */ 33 | 34 | var n = 0; 35 | 36 | /** 37 | * Sends `msg` to all connected peers round-robin. 38 | */ 39 | 40 | sock.send = function(){ 41 | var socks = this.socks; 42 | var len = socks.length; 43 | var sock = socks[n++ % len]; 44 | 45 | var msg = slice(arguments); 46 | 47 | if (sock && sock.writable) { 48 | sock.write(this.pack(msg)); 49 | } else { 50 | fallback(msg); 51 | } 52 | }; 53 | 54 | }; 55 | }; 56 | -------------------------------------------------------------------------------- /test/test.sub.matches.js: -------------------------------------------------------------------------------- 1 | 2 | var assert = require('better-assert') 3 | , axon = require('..') 4 | , sub = axon.socket('sub'); 5 | 6 | function test(pattern, str) { 7 | sub.clearSubscriptions(); 8 | sub.subscribe(pattern); 9 | return sub.matches(str); 10 | } 11 | 12 | assert(true == test('foo', 'foo')); 13 | assert(false == test('foo', 'foobar')); 14 | assert(false == test('foo', 'barfoo')); 15 | 16 | assert(false == test('foo*', 'foo')); 17 | assert(true == test('foo*', 'foobar')); 18 | assert(true == test('foo*', 'foobarbaz')); 19 | assert(false == test('foo*', 'barfoo')); 20 | 21 | assert(true == test('user:*', 'user:login')); 22 | assert(true == test('user:*', 'user:logout')); 23 | assert(false == test('user:*', 'user')); 24 | 25 | assert(true == test('user:*:logout', 'user:tj:logout')); 26 | assert(false == test('user:*:logout', 'user::logout')); 27 | assert(false == test('user:*:logout', 'user:logout')); 28 | 29 | assert(true == test('user:*:*', 'user:tj:login')); 30 | assert(true == test('user:*:*', 'user:tj:logout')); 31 | assert(false == test('user:*:*', 'user::logout')); 32 | assert(false == test('user:*:*', 'user:logout')); 33 | -------------------------------------------------------------------------------- /test/test.emitter.js: -------------------------------------------------------------------------------- 1 | 2 | var ss = require('..') 3 | , should = require('should'); 4 | 5 | var pub = ss.socket('pub-emitter') 6 | , sub = ss.socket('sub-emitter'); 7 | 8 | // test basic 1-1 pub/sub emitter style 9 | 10 | pub.bind(4000, function(){ 11 | sub.connect(4000, function(){ 12 | sub.on('foo', function(){ 13 | arguments.length.should.equal(0); 14 | }); 15 | 16 | sub.on('bar', function(a, b, c){ 17 | arguments.length.should.equal(3); 18 | a.should.equal(1); 19 | b.should.equal(2); 20 | c.should.equal(3); 21 | }); 22 | 23 | sub.on('hai', function(a, b, c){ 24 | arguments.length.should.equal(3); 25 | a.should.equal(4); 26 | b.should.equal(5); 27 | c.should.equal(6); 28 | }); 29 | 30 | sub.on('baz', function(a){ 31 | arguments.length.should.equal(1); 32 | a.should.have.property('name', 'tobi'); 33 | pub.close(); 34 | sub.close(); 35 | }); 36 | 37 | setTimeout(function(){ 38 | pub.emit('foo'); 39 | pub.emit('bar', 1, 2, 3); 40 | pub.emit('hai', 4, 5, 6); 41 | pub.emit('baz', { name: 'tobi' }); 42 | }, 20); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2012-2013 TJ Holowaychuk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | 'Software'), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /examples/hwm/index.js: -------------------------------------------------------------------------------- 1 | 2 | var axon = require('../..') 3 | , push = axon.socket('push') 4 | , pull = axon.socket('pull') 5 | 6 | // by default the high water mark (HWM) 7 | // is Infinity, allowing the queue to 8 | // grow unbounded. Here it is manually 9 | // set to 3 for demonstration purposes only 10 | 11 | push.connect(3000); 12 | push.set('hwm', 3); 13 | 14 | // faux database as intermediate queue, 15 | // this could be mongo, disk, anything you want 16 | 17 | var db = []; 18 | push.on('drop', function(msg){ 19 | console.log('dropped %s', msg); 20 | db.push(msg); 21 | }); 22 | 23 | push.on('flush', function(msgs){ 24 | console.log('flushed %d', msgs.length); 25 | db.forEach(function(msg){ 26 | push.send(msg); 27 | }); 28 | }); 29 | 30 | pull.on('message', function(msg){ 31 | console.log('recv %s', msg); 32 | if ('10' == msg.toString()) process.exit(); 33 | }); 34 | 35 | pull.bind(3000, function(){ 36 | push.send('1'); 37 | push.send('2'); 38 | push.send('3'); 39 | push.send('4'); 40 | push.send('5'); 41 | pull.close(function(){ 42 | push.send('6'); 43 | push.send('7'); 44 | push.send('8'); 45 | push.send('9'); 46 | push.send('10'); 47 | pull.bind(3000); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Constructors. 4 | */ 5 | 6 | exports.PubEmitterSocket = require('./sockets/pub-emitter'); 7 | exports.SubEmitterSocket = require('./sockets/sub-emitter'); 8 | exports.PushSocket = require('./sockets/push'); 9 | exports.PullSocket = require('./sockets/pull'); 10 | exports.PubSocket = require('./sockets/pub'); 11 | exports.SubSocket = require('./sockets/sub'); 12 | exports.ReqSocket = require('./sockets/req'); 13 | exports.RepSocket = require('./sockets/rep'); 14 | exports.Socket = require('./sockets/sock'); 15 | 16 | /** 17 | * Socket types. 18 | */ 19 | 20 | exports.types = { 21 | 'pub-emitter': exports.PubEmitterSocket, 22 | 'sub-emitter': exports.SubEmitterSocket, 23 | 'push': exports.PushSocket, 24 | 'pull': exports.PullSocket, 25 | 'pub': exports.PubSocket, 26 | 'sub': exports.SubSocket, 27 | 'req': exports.ReqSocket, 28 | 'rep': exports.RepSocket 29 | }; 30 | 31 | /** 32 | * Return a new socket of the given `type`. 33 | * 34 | * @param {String} type 35 | * @param {Object} options 36 | * @return {Socket} 37 | * @api public 38 | */ 39 | 40 | exports.socket = function(type, options){ 41 | var fn = exports.types[type]; 42 | if (!fn) throw new Error('invalid socket type "' + type + '"'); 43 | return new fn(options); 44 | }; 45 | -------------------------------------------------------------------------------- /test/test.emitter.off.js: -------------------------------------------------------------------------------- 1 | 2 | var ss = require('..') 3 | , should = require('should'); 4 | 5 | var pub = ss.socket('pub-emitter') 6 | , sub = ss.socket('sub-emitter'); 7 | 8 | pub.bind(4000); 9 | 10 | var events = []; 11 | 12 | sub.on('user:login', function () { 13 | events.push('user:login'); 14 | sub.off('user:login'); 15 | }); 16 | 17 | sub.on('page:view', function () { 18 | events.push('page:view'); 19 | sub.off('page:view'); 20 | }); 21 | 22 | sub.on('something:else', function () { 23 | events.push('something:else'); 24 | sub.off('something:else'); 25 | }); 26 | 27 | sub.on('foo:bar', function () { 28 | events.push('foo:bar'); 29 | events.map(String).should.eql(expected); 30 | if (expected === on) { 31 | events = []; 32 | expected = off; 33 | fireEvents(); 34 | } else { 35 | pub.close(); 36 | sub.close(); 37 | } 38 | }); 39 | 40 | sub.connect(4000, fireEvents); 41 | 42 | function fireEvents() { 43 | pub.emit('user:login', 'tobi'); 44 | pub.emit('page:view', '/home'); 45 | pub.emit('something:else', 'pork'); 46 | pub.emit('foo:bar', 'baz'); 47 | } 48 | 49 | var on = [ 50 | 'user:login', 51 | 'page:view', 52 | 'something:else', 53 | 'foo:bar' 54 | ]; 55 | 56 | var off = [ 57 | 'foo:bar' 58 | ]; 59 | 60 | var expected = on; -------------------------------------------------------------------------------- /test/test.pubsub.multiple-subscribers.js: -------------------------------------------------------------------------------- 1 | 2 | var ss = require('../') 3 | , should = require('should'); 4 | 5 | var pub = ss.socket('pub') 6 | , a = ss.socket('sub') 7 | , b = ss.socket('sub') 8 | , c = ss.socket('sub'); 9 | 10 | var n = 9; 11 | 12 | var messages = { 13 | a: [] 14 | , b: [] 15 | , c: [] 16 | }; 17 | 18 | // test basic 1-M pub/sub 19 | 20 | pub.bind(4000, function(){ 21 | a.connect(4000, function(){ 22 | b.connect(4000, function(){ 23 | c.connect(4000, function(){ 24 | setTimeout(function(){ 25 | pub.send('foo'); 26 | pub.send('bar'); 27 | pub.send('baz'); 28 | }, 20); 29 | }); 30 | }); 31 | }); 32 | }); 33 | 34 | a.on('message', function(msg){ 35 | messages.a.push(msg.toString()); 36 | --n || done(); 37 | }); 38 | 39 | b.on('message', function(msg){ 40 | messages.b.push(msg.toString()); 41 | --n || done(); 42 | }); 43 | 44 | c.on('message', function(msg){ 45 | messages.c.push(msg.toString()); 46 | --n || done(); 47 | }); 48 | 49 | function done() { 50 | messages.a.should.eql(['foo', 'bar', 'baz']); 51 | messages.b.should.eql(['foo', 'bar', 'baz']); 52 | messages.c.should.eql(['foo', 'bar', 'baz']); 53 | pub.close(); 54 | a.close(); 55 | b.close(); 56 | c.close(); 57 | } -------------------------------------------------------------------------------- /test/test.socket.error-remove-sock.js: -------------------------------------------------------------------------------- 1 | 2 | var axon = require('..') 3 | , assert = require('better-assert'); 4 | 5 | var a = axon.socket('push') 6 | , b = axon.socket('push') 7 | , c = axon.socket('push') 8 | , pull = axon.socket('pull'); 9 | 10 | a.bind(4441); 11 | b.bind(4442); 12 | c.bind(4443); 13 | 14 | pull.connect(4441); 15 | pull.connect(4442); 16 | pull.connect(4443); 17 | 18 | pull.once('error', function(err){ 19 | assert('boom' == err.message); 20 | assert(2 == pull.socks.length); 21 | var err = new Error('faux EPIPE'); 22 | err.code = 'EPIPE'; 23 | pull.socks[0]._destroy(err); 24 | }); 25 | 26 | pull.once('ignored error', function(err){ 27 | assert('EPIPE' == err.code); 28 | assert(1 == pull.socks.length); 29 | a.close(); 30 | b.close(); 31 | c.close(); 32 | pull.close(); 33 | }); 34 | 35 | // 1 peer connect each 36 | a.on('connect', connect); 37 | b.on('connect', connect); 38 | c.on('connect', connect); 39 | 40 | // 3 peer connects 41 | pull.on('connect', connect); 42 | 43 | var pending = 6; 44 | 45 | function connect(){ 46 | --pending || done(); 47 | } 48 | 49 | function done(){ 50 | assert(1 == a.socks.length); 51 | assert(1 == b.socks.length); 52 | assert(1 == c.socks.length); 53 | assert(3 == pull.socks.length); 54 | pull.socks[0]._destroy(new Error('boom')); 55 | } 56 | -------------------------------------------------------------------------------- /test/test.emitter.wildcards.js: -------------------------------------------------------------------------------- 1 | 2 | var axon = require('..') 3 | , assert = require('better-assert') 4 | , pub = axon.socket('pub-emitter') 5 | , sub = axon.socket('sub-emitter') 6 | , pending = 8 7 | 8 | pub.bind(4000); 9 | 10 | sub.on('user:login', function(name){ 11 | assert('tobi' == name); 12 | --pending || done(); 13 | }); 14 | 15 | sub.on('user:logout', function(name){ 16 | assert('tobi' == name); 17 | --pending || done(); 18 | }); 19 | 20 | sub.on('user:*', function(action, name){ 21 | assert('login' == action || 'logout' == action); 22 | assert('tobi' == name); 23 | --pending || done(); 24 | }); 25 | 26 | sub.on('*:*', function(topic, action, name){ 27 | assert('user' == topic); 28 | assert('login' == action || 'logout' == action); 29 | assert('tobi' == name); 30 | --pending || done(); 31 | }); 32 | 33 | sub.on('weird[chars]{*}', function(a, b){ 34 | assert('some stuff' == a); 35 | assert('hello' == b); 36 | --pending || done(); 37 | }); 38 | 39 | sub.on('foo.bar.baz', function(){ 40 | assert(0 == arguments.length); 41 | --pending || done(); 42 | }); 43 | 44 | pub.sock.on('connect', function() { 45 | pub.emit('user:login', 'tobi'); 46 | pub.emit('user:logout', 'tobi'); 47 | pub.emit('weird[chars]{some stuff}', 'hello'); 48 | pub.emit('foo.bar.baz'); 49 | }); 50 | 51 | sub.connect(4000); 52 | 53 | function done() { 54 | sub.close(); 55 | pub.close(); 56 | } -------------------------------------------------------------------------------- /test/test.emitter.many-connect.js: -------------------------------------------------------------------------------- 1 | 2 | var ss = require('..') 3 | , should = require('should'); 4 | 5 | var worker = ss.socket('pub-emitter') 6 | , a = ss.socket('sub-emitter') 7 | , b = ss.socket('sub-emitter') 8 | , c = ss.socket('sub-emitter') 9 | 10 | /* 11 | 12 | +--> a 13 | worker ---|--> b 14 | +--> c 15 | 16 | */ 17 | 18 | a.bind(4000); 19 | b.bind(4445); 20 | c.bind(4446); 21 | 22 | worker.connect(4000, function(){ 23 | worker.connect(4445, function(){ 24 | worker.connect(4446, test); 25 | }); 26 | }); 27 | 28 | var vals = []; 29 | var pending = 3; 30 | 31 | function test() { 32 | a.on('progress', function(id, n){ 33 | vals.push('a'); 34 | id.should.equal('3d2fg'); 35 | n.should.equal(.5); 36 | --pending || done(); 37 | }); 38 | 39 | b.on('progress', function(id, n){ 40 | vals.push('b'); 41 | id.should.equal('3d2fg'); 42 | n.should.equal(.5); 43 | --pending || done(); 44 | }); 45 | 46 | c.on('progress', function(id, n){ 47 | vals.push('c'); 48 | id.should.equal('3d2fg'); 49 | n.should.equal(.5); 50 | --pending || done(); 51 | }); 52 | 53 | setTimeout(function(){ 54 | worker.emit('progress', '3d2fg', .5); 55 | }, 100); 56 | } 57 | 58 | function done() { 59 | vals.should.containEql('a'); 60 | vals.should.containEql('b'); 61 | vals.should.containEql('c'); 62 | vals.should.have.length(3); 63 | worker.close(); 64 | a.close(); 65 | b.close(); 66 | c.close(); 67 | } 68 | -------------------------------------------------------------------------------- /lib/plugins/queue.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var debug = require('debug')('axon:queue'); 7 | 8 | /** 9 | * Queue plugin. 10 | * 11 | * Provides an `.enqueue()` method to the `sock`. Messages 12 | * passed to `enqueue` will be buffered until the next 13 | * `connect` event is emitted. 14 | * 15 | * Emits: 16 | * 17 | * - `drop` (msg) when a message is dropped 18 | * - `flush` (msgs) when the queue is flushed 19 | * 20 | * @param {Object} options 21 | * @api private 22 | */ 23 | 24 | module.exports = function(options){ 25 | options = options || {}; 26 | 27 | return function(sock){ 28 | 29 | /** 30 | * Message buffer. 31 | */ 32 | 33 | sock.queue = []; 34 | 35 | /** 36 | * Flush `buf` on `connect`. 37 | */ 38 | 39 | sock.on('connect', function(){ 40 | var prev = sock.queue; 41 | var len = prev.length; 42 | sock.queue = []; 43 | debug('flush %d messages', len); 44 | 45 | for (var i = 0; i < len; ++i) { 46 | this.send.apply(this, prev[i]); 47 | } 48 | 49 | sock.emit('flush', prev); 50 | }); 51 | 52 | /** 53 | * Pushes `msg` into `buf`. 54 | */ 55 | 56 | sock.enqueue = function(msg){ 57 | var hwm = sock.settings.hwm; 58 | if (sock.queue.length >= hwm) return drop(msg); 59 | sock.queue.push(msg); 60 | }; 61 | 62 | /** 63 | * Drop the given `msg`. 64 | */ 65 | 66 | function drop(msg) { 67 | debug('drop'); 68 | sock.emit('drop', msg); 69 | } 70 | }; 71 | }; 72 | -------------------------------------------------------------------------------- /test/test.emitter.many.js: -------------------------------------------------------------------------------- 1 | 2 | var ss = require('..') 3 | , should = require('should'); 4 | 5 | var worker = ss.socket('pub-emitter') 6 | , relaySub = ss.socket('sub-emitter') 7 | , relayPub = ss.socket('pub-emitter') 8 | , a = ss.socket('sub-emitter') 9 | , b = ss.socket('sub-emitter') 10 | , c = ss.socket('sub-emitter') 11 | 12 | /* 13 | 14 | <--- a 15 | worker ---> relay <--- b 16 | <--- c 17 | 18 | */ 19 | 20 | relaySub.bind(4000); 21 | relayPub.bind(5555); 22 | worker.connect(4000); 23 | a.connect(5555); 24 | b.connect(5555); 25 | c.connect(5555); 26 | 27 | relaySub.on('progress', function(id, n){ 28 | relayPub.emit('progress', id, n); 29 | }); 30 | 31 | var vals = []; 32 | var pending = 3; 33 | 34 | a.on('progress', function(id, n){ 35 | vals.push('a'); 36 | id.should.equal('3d2fg'); 37 | n.should.equal(.5); 38 | --pending || done(); 39 | }); 40 | 41 | b.on('progress', function(id, n){ 42 | vals.push('b'); 43 | id.should.equal('3d2fg'); 44 | n.should.equal(.5); 45 | --pending || done(); 46 | }); 47 | 48 | c.on('progress', function(id, n){ 49 | vals.push('c'); 50 | id.should.equal('3d2fg'); 51 | n.should.equal(.5); 52 | --pending || done(); 53 | }); 54 | 55 | setTimeout(function(){ 56 | worker.emit('progress', '3d2fg', .5); 57 | }, 100); 58 | 59 | function done() { 60 | vals.should.containEql('a'); 61 | vals.should.containEql('b'); 62 | vals.should.containEql('c'); 63 | vals.should.have.length(3); 64 | worker.close(); 65 | relaySub.close(); 66 | relayPub.close(); 67 | a.close(); 68 | b.close(); 69 | c.close(); 70 | } 71 | -------------------------------------------------------------------------------- /test/test.pubsub.unsubscribe.js: -------------------------------------------------------------------------------- 1 | 2 | var axon = require('..') 3 | , should = require('should') 4 | , pub = axon.socket('pub') 5 | , sub = axon.socket('sub') 6 | 7 | pub.bind(4000); 8 | 9 | sub.subscribe(/^user:.+$/); 10 | sub.subscribe(/^user:.+$/); 11 | sub.subscribe(/^page:view$/); 12 | sub.subscribe(/^something:else$/); 13 | sub.subscribe('foo:bar'); 14 | 15 | pub.on('connect', fireEvents); 16 | 17 | function fireEvents() { 18 | pub.send('user:login', 'tobi'); 19 | pub.send('user:login', 'loki'); 20 | pub.send('something:else', 'pork'); 21 | pub.send('user:logout', 'jane'); 22 | pub.send('unrelated', 'message'); 23 | pub.send('other', 'message'); 24 | pub.send('foo:bar', 'baz'); 25 | pub.send('page:view', '/home'); 26 | }; 27 | 28 | sub.connect(4000); 29 | 30 | var msgs = []; 31 | sub.on('message', function(type, name){ 32 | msgs.push(type, name); 33 | if ('page:view' == type) { 34 | msgs.map(String).should.eql(expected); 35 | if (expected === subscribed) { 36 | sub.unsubscribe(/^user:.+$/); 37 | sub.unsubscribe('foo:bar'); 38 | msgs = []; 39 | expected = unsubscribed; 40 | fireEvents(); 41 | } 42 | else { 43 | pub.close(); 44 | sub.close(); 45 | } 46 | } 47 | }); 48 | 49 | var subscribed = [ 50 | 'user:login', 51 | 'tobi', 52 | 'user:login', 53 | 'loki', 54 | 'something:else', 55 | 'pork', 56 | 'user:logout', 57 | 'jane', 58 | 'foo:bar', 59 | 'baz', 60 | 'page:view', 61 | '/home' 62 | ]; 63 | 64 | var unsubscribed = [ 65 | 'something:else', 66 | 'pork', 67 | 'page:view', 68 | '/home' 69 | ]; 70 | 71 | var expected = subscribed; -------------------------------------------------------------------------------- /lib/sockets/rep.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var slice = require('../utils').slice; 7 | var debug = require('debug')('axon:rep'); 8 | var Message = require('amp-message'); 9 | var Socket = require('./sock'); 10 | 11 | /** 12 | * Expose `RepSocket`. 13 | */ 14 | 15 | module.exports = RepSocket; 16 | 17 | /** 18 | * Initialize a new `RepSocket`. 19 | * 20 | * @api private 21 | */ 22 | 23 | function RepSocket() { 24 | Socket.call(this); 25 | } 26 | 27 | /** 28 | * Inherits from `Socket.prototype`. 29 | */ 30 | 31 | RepSocket.prototype.__proto__ = Socket.prototype; 32 | 33 | /** 34 | * Incoming. 35 | * 36 | * @param {net.Socket} sock 37 | * @return {Function} closure(msg, mulitpart) 38 | * @api private 39 | */ 40 | 41 | RepSocket.prototype.onmessage = function(sock){ 42 | var self = this; 43 | 44 | return function (buf){ 45 | var msg = new Message(buf); 46 | var args = msg.args; 47 | 48 | var id = args.pop(); 49 | args.unshift('message'); 50 | args.push(reply); 51 | self.emit.apply(self, args); 52 | 53 | function reply() { 54 | var fn = function(){}; 55 | var args = slice(arguments); 56 | args[0] = args[0] || null; 57 | 58 | var hasCallback = 'function' == typeof args[args.length - 1]; 59 | if (hasCallback) fn = args.pop(); 60 | 61 | args.push(id); 62 | 63 | if (sock.writable) { 64 | sock.write(self.pack(args), function(){ fn(true) }); 65 | return true; 66 | } else { 67 | debug('peer went away'); 68 | process.nextTick(function(){ fn(false) }); 69 | return false; 70 | } 71 | } 72 | }; 73 | }; 74 | 75 | 76 | -------------------------------------------------------------------------------- /benchmark/sub.js: -------------------------------------------------------------------------------- 1 | 2 | var ss = require('..') 3 | , program = require('commander') 4 | , humanize = require('humanize-number'); 5 | 6 | program 7 | .option('-T, --type ', 'socket type [sub]', 'sub') 8 | .option('-s, --size ', 'message size in bytes [1024]', parseInt) 9 | .option('-d, --duration ', 'duration of test [5000]', parseInt) 10 | .parse(process.argv) 11 | 12 | var sock = ss.socket(program.type); 13 | sock.connect(3000); 14 | 15 | var n = 0; 16 | var ops = 5000; 17 | var bytes = program.size || 1024; 18 | var prev = start = Date.now(); 19 | var results = []; 20 | 21 | console.log(); 22 | 23 | sock.on('message', function(msg){ 24 | if (n++ % ops == 0) { 25 | var ms = Date.now() - prev; 26 | var sec = ms / 1000; 27 | var persec = ops / sec | 0; 28 | results.push(persec); 29 | process.stdout.write('\r [' + persec + ' ops/s] [' + n + ']'); 30 | prev = Date.now(); 31 | } 32 | }); 33 | 34 | function sum(arr) { 35 | return arr.reduce(function(sum, n){ 36 | return sum + n; 37 | }); 38 | } 39 | 40 | function min(arr) { 41 | return arr.reduce(function(min, n){ 42 | return n < min 43 | ? n 44 | : min; 45 | }); 46 | } 47 | 48 | function median(arr) { 49 | arr = arr.sort(); 50 | return arr[arr.length / 2 | 0]; 51 | } 52 | 53 | function done(){ 54 | var ms = Date.now() - start; 55 | var avg = n / (ms / 1000); 56 | console.log('\n'); 57 | console.log(' min: %s ops/s', humanize(min(results))); 58 | console.log(' mean: %s ops/s', humanize(avg | 0)); 59 | console.log(' median: %s ops/s', humanize(median(results))); 60 | console.log(' total: %s ops in %ds', humanize(n), ms / 1000); 61 | console.log(' through: %d mb/s', ((avg * bytes) / 1024 / 1024).toFixed(2)); 62 | console.log(); 63 | process.exit(); 64 | } 65 | 66 | process.on('SIGINT', done); 67 | setTimeout(done, program.duration || 5000); 68 | -------------------------------------------------------------------------------- /lib/sockets/sub-emitter.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var Message = require('amp-message'); 7 | var SubSocket = require('./sub'); 8 | 9 | /** 10 | * Expose `SubEmitterSocket`. 11 | */ 12 | 13 | module.exports = SubEmitterSocket; 14 | 15 | /** 16 | * Initialzie a new `SubEmitterSocket`. 17 | * 18 | * @api private 19 | */ 20 | 21 | function SubEmitterSocket() { 22 | this.sock = new SubSocket; 23 | this.sock.onmessage = this.onmessage.bind(this); 24 | this.bind = this.sock.bind.bind(this.sock); 25 | this.connect = this.sock.connect.bind(this.sock); 26 | this.close = this.sock.close.bind(this.sock); 27 | this.listeners = []; 28 | } 29 | 30 | /** 31 | * Message handler. 32 | * 33 | * @param {net.Socket} sock 34 | * @return {Function} closure(msg, mulitpart) 35 | * @api private 36 | */ 37 | 38 | SubEmitterSocket.prototype.onmessage = function(){ 39 | var listeners = this.listeners; 40 | var self = this; 41 | 42 | return function(buf){ 43 | var msg = new Message(buf); 44 | var topic = msg.shift(); 45 | 46 | for (var i = 0; i < listeners.length; ++i) { 47 | var listener = listeners[i]; 48 | 49 | var m = listener.re.exec(topic); 50 | if (!m) continue; 51 | 52 | listener.fn.apply(self, m.slice(1).concat(msg.args)); 53 | } 54 | } 55 | }; 56 | 57 | /** 58 | * Subscribe to `event` and invoke the given callback `fn`. 59 | * 60 | * @param {String} event 61 | * @param {Function} fn 62 | * @return {SubEmitterSocket} self 63 | * @api public 64 | */ 65 | 66 | SubEmitterSocket.prototype.on = function(event, fn){ 67 | var re = this.sock.subscribe(event); 68 | this.listeners.push({ 69 | event: event, 70 | re: re, 71 | fn: fn 72 | }); 73 | return this; 74 | }; 75 | 76 | /** 77 | * Unsubscribe with the given `event`. 78 | * 79 | * @param {String} event 80 | * @return {SubEmitterSocket} self 81 | * @api public 82 | */ 83 | 84 | SubEmitterSocket.prototype.off = function(event){ 85 | for (var i = 0; i < this.listeners.length; ++i) { 86 | if (this.listeners[i].event === event) { 87 | this.sock.unsubscribe(this.listeners[i].re); 88 | this.listeners.splice(i--, 1); 89 | } 90 | } 91 | return this; 92 | }; 93 | -------------------------------------------------------------------------------- /lib/sockets/req.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var debug = require('debug')('axon:req'); 7 | var queue = require('../plugins/queue'); 8 | var slice = require('../utils').slice; 9 | var Message = require('amp-message'); 10 | var Socket = require('./sock'); 11 | 12 | /** 13 | * Expose `ReqSocket`. 14 | */ 15 | 16 | module.exports = ReqSocket; 17 | 18 | /** 19 | * Initialize a new `ReqSocket`. 20 | * 21 | * @api private 22 | */ 23 | 24 | function ReqSocket() { 25 | Socket.call(this); 26 | this.n = 0; 27 | this.ids = 0; 28 | this.callbacks = {}; 29 | this.identity = this.get('identity'); 30 | this.use(queue()); 31 | } 32 | 33 | /** 34 | * Inherits from `Socket.prototype`. 35 | */ 36 | 37 | ReqSocket.prototype.__proto__ = Socket.prototype; 38 | 39 | /** 40 | * Return a message id. 41 | * 42 | * @return {String} 43 | * @api private 44 | */ 45 | 46 | ReqSocket.prototype.id = function(){ 47 | return this.identity + ':' + this.ids++; 48 | }; 49 | 50 | /** 51 | * Emits the "message" event with all message parts 52 | * after the null delimeter part. 53 | * 54 | * @param {net.Socket} sock 55 | * @return {Function} closure(msg, multipart) 56 | * @api private 57 | */ 58 | 59 | ReqSocket.prototype.onmessage = function(){ 60 | var self = this; 61 | return function(buf){ 62 | var msg = new Message(buf); 63 | var id = msg.pop(); 64 | var fn = self.callbacks[id]; 65 | if (!fn) return debug('missing callback %s', id); 66 | fn.apply(null, msg.args); 67 | delete self.callbacks[id]; 68 | }; 69 | }; 70 | 71 | /** 72 | * Sends `msg` to the remote peers. Appends 73 | * the null message part prior to sending. 74 | * 75 | * @param {Mixed} msg 76 | * @api public 77 | */ 78 | 79 | ReqSocket.prototype.send = function(msg){ 80 | var socks = this.socks; 81 | var len = socks.length; 82 | var sock = socks[this.n++ % len]; 83 | var args = slice(arguments); 84 | 85 | if (sock) { 86 | var hasCallback = 'function' == typeof args[args.length - 1]; 87 | if (!hasCallback) args.push(function(){}); 88 | var fn = args.pop(); 89 | fn.id = this.id(); 90 | this.callbacks[fn.id] = fn; 91 | args.push(fn.id); 92 | } 93 | 94 | if (sock) { 95 | sock.write(this.pack(args)); 96 | } else { 97 | debug('no connected peers'); 98 | this.enqueue(args); 99 | } 100 | }; 101 | -------------------------------------------------------------------------------- /lib/sockets/sub.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var debug = require('debug')('axon:sub'); 7 | var escape = require('escape-regexp'); 8 | var Message = require('amp-message'); 9 | var Socket = require('./sock'); 10 | 11 | /** 12 | * Expose `SubSocket`. 13 | */ 14 | 15 | module.exports = SubSocket; 16 | 17 | /** 18 | * Initialize a new `SubSocket`. 19 | * 20 | * @api private 21 | */ 22 | 23 | function SubSocket() { 24 | Socket.call(this); 25 | this.subscriptions = []; 26 | } 27 | 28 | /** 29 | * Inherits from `Socket.prototype`. 30 | */ 31 | 32 | SubSocket.prototype.__proto__ = Socket.prototype; 33 | 34 | /** 35 | * Check if this socket has subscriptions. 36 | * 37 | * @return {Boolean} 38 | * @api public 39 | */ 40 | 41 | SubSocket.prototype.hasSubscriptions = function(){ 42 | return !! this.subscriptions.length; 43 | }; 44 | 45 | /** 46 | * Check if any subscriptions match `topic`. 47 | * 48 | * @param {String} topic 49 | * @return {Boolean} 50 | * @api public 51 | */ 52 | 53 | SubSocket.prototype.matches = function(topic){ 54 | for (var i = 0; i < this.subscriptions.length; ++i) { 55 | if (this.subscriptions[i].test(topic)) { 56 | return true; 57 | } 58 | } 59 | return false; 60 | }; 61 | 62 | /** 63 | * Message handler. 64 | * 65 | * @param {net.Socket} sock 66 | * @return {Function} closure(msg, mulitpart) 67 | * @api private 68 | */ 69 | 70 | SubSocket.prototype.onmessage = function(sock){ 71 | var subs = this.hasSubscriptions(); 72 | var self = this; 73 | 74 | return function(buf){ 75 | var msg = new Message(buf); 76 | 77 | if (subs) { 78 | var topic = msg.args[0]; 79 | if (!self.matches(topic)) return debug('not subscribed to "%s"', topic); 80 | } 81 | 82 | self.emit.apply(self, ['message'].concat(msg.args)); 83 | }; 84 | }; 85 | 86 | /** 87 | * Subscribe with the given `re`. 88 | * 89 | * @param {RegExp|String} re 90 | * @return {RegExp} 91 | * @api public 92 | */ 93 | 94 | SubSocket.prototype.subscribe = function(re){ 95 | debug('subscribe to "%s"', re); 96 | this.subscriptions.push(re = toRegExp(re)); 97 | return re; 98 | }; 99 | 100 | /** 101 | * Unsubscribe with the given `re`. 102 | * 103 | * @param {RegExp|String} re 104 | * @api public 105 | */ 106 | 107 | SubSocket.prototype.unsubscribe = function(re){ 108 | debug('unsubscribe from "%s"', re); 109 | re = toRegExp(re); 110 | for (var i = 0; i < this.subscriptions.length; ++i) { 111 | if (this.subscriptions[i].toString() === re.toString()) { 112 | this.subscriptions.splice(i--, 1); 113 | } 114 | } 115 | }; 116 | 117 | /** 118 | * Clear current subscriptions. 119 | * 120 | * @api public 121 | */ 122 | 123 | SubSocket.prototype.clearSubscriptions = function(){ 124 | this.subscriptions = []; 125 | }; 126 | 127 | /** 128 | * Subscribers should not send messages. 129 | */ 130 | 131 | SubSocket.prototype.send = function(){ 132 | throw new Error('subscribers cannot send messages'); 133 | }; 134 | 135 | /** 136 | * Convert `str` to a `RegExp`. 137 | * 138 | * @param {String} str 139 | * @return {RegExp} 140 | * @api private 141 | */ 142 | 143 | function toRegExp(str) { 144 | if (str instanceof RegExp) return str; 145 | str = escape(str); 146 | str = str.replace(/\\\*/g, '(.+)'); 147 | return new RegExp('^' + str + '$'); 148 | } -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2 | 2.0.1 / 2014-09-09 3 | ================== 4 | 5 | * fix Floating-point durations to setTimeout may cause infinite loop 6 | 7 | 2.0.0 / 2014-02-25 8 | ================== 9 | 10 | * refactor to use the AMP protocol. Closes #577 11 | * remove old codec support 12 | 13 | 1.0.0 / 2013-08-30 14 | ================== 15 | 16 | * change Socket#connect() to use inaddr_any as well 17 | 18 | 0.6.1 / 2013-04-13 19 | ================== 20 | 21 | * fix Socket#close() callback support 22 | * add callback to reply() when peer is gone 23 | 24 | 0.6.0 / 2013-04-13 25 | ================== 26 | 27 | * add optional reply() callback. Closes #95 28 | * add support for optional req.send() callback. Closes #89 29 | 30 | 0.5.2 / 2013-04-09 31 | ================== 32 | 33 | * add `sock.queue` array for logging / debugging etc 34 | * fix connection queue flush which may drop messages on connection 35 | 36 | 0.5.1 / 2013-03-01 37 | ================== 38 | 39 | * add exit() to HWM example 40 | * add better HWM example 41 | * fix: ignore closed sockets on reply(). fixes #82 42 | 43 | 0.5.0 / 2013-01-01 44 | ================== 45 | 46 | * add HWM support. Closes #19 47 | * add ability to pass a callback in to the Socket.close method. 48 | * update benchmarks. Closes #72 49 | * remove batching 50 | 51 | 0.4.6 / 2012-11-15 52 | ================== 53 | 54 | * fix round-robin write to unwritable socket 55 | 56 | 0.4.5 / 2012-10-30 57 | ================== 58 | 59 | * add more network errors to be ignored 60 | * refactor `SubEmitter` 61 | * refactor `PubEmitter` 62 | * fix exponential backoff 63 | 64 | 0.4.4 / 2012-10-29 65 | ================== 66 | 67 | * fix round-robin global var leak for fallback function. Closes #66 68 | 69 | 0.4.3 / 2012-10-27 70 | ================== 71 | 72 | * add 30% throughput increase for sub-emitter by removing some indirection 73 | * fix `PubSocket#flushBatch()` in order to avoid writing to not writable sockets [AlexeyKupershtokh] 74 | 75 | 0.4.2 / 2012-10-18 76 | ================== 77 | 78 | * add 30% throughput increase for sub-emitter by removing some indirection 79 | * add escaping of regexp chars for `SubSocket#subscribe()` 80 | * fix non-multipart `SubEmitterSocket` logic 81 | 82 | 0.4.1 / 2012-10-16 83 | ================== 84 | 85 | * add removal of sockets on error 86 | * add handling of __ECONNRESET__, __ECONNREFUSED__, and __EPIPE__. Closes #17 87 | * add immediate closing of sockets on `.close()` 88 | * fix "bind" event. Closes #53 89 | * fix 'close' event for server sockets 90 | * remove "stream" socket type for now 91 | 92 | 0.4.0 / 2012-10-12 93 | ================== 94 | 95 | * add emitter wildcard support 96 | * add sub socket subscription support 97 | * add `pub-emitter` 98 | * add `sub-emitter` 99 | * perf: remove `.concat()` usage, ~10% gain 100 | * remove greetings 101 | 102 | 0.3.2 / 2012-10-08 103 | ================== 104 | 105 | * change prefix fix to `reply()` only 106 | 107 | 0.3.1 / 2012-10-08 108 | ================== 109 | 110 | * add fix for reply(undefined) 111 | 112 | 0.3.0 / 2012-10-05 113 | ================== 114 | 115 | * add `Socket#address()` to help with ephemeral port binding. Closes #39 116 | * add default identity of __PID__. Closes #35 117 | * remove examples for router/dealer 118 | 119 | 0.2.0 / 2012-09-27 120 | ================== 121 | 122 | * add default random `identity` 123 | * add `req.send()` callback support 124 | * remove router / dealer 125 | * change `ReqSocket` to round-robin send()s 126 | 127 | 0.1.0 / 2012-09-24 128 | ================== 129 | 130 | * add router socket [gjohnson] 131 | * add dealer socket [gjohnson] 132 | * add req socket [gjohnson] 133 | * add rep socket [gjohnson] 134 | * add multipart support [gjohnson] 135 | * add `.set()` / `.get()` configuration methods 136 | * add tcp://hostname:port support to .bind() and .connect(). Closes #16 137 | * add `make bm` 138 | * add Batch#empty() 139 | * remove Socket#option() 140 | 141 | 0.0.3 / 2012-07-14 142 | ================== 143 | 144 | * add resize example 145 | * add `debug()` instrumentation 146 | * add `PullSocket` bind support 147 | * add `Parser` 148 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # Axon 2 | 3 | Axon is a message-oriented socket library for node.js heavily inspired by zeromq. For a light-weight 4 | UDP alternative you may be interested in [punt](https://github.com/tj/punt). 5 | 6 | [![Build Status](https://travis-ci.org/tj/axon.png)](https://travis-ci.org/tj/axon) 7 | 8 | ## Installation 9 | 10 | $ npm install axon 11 | 12 | ## Features 13 | 14 | - message oriented 15 | - automated reconnection 16 | - light-weight wire protocol 17 | - mixed-type arguments (strings, objects, buffers, etc) 18 | - unix domain socket support 19 | - fast (~800 mb/s ~500,000 messages/s) 20 | 21 | ## Events 22 | 23 | - `close` when server or connection is closed 24 | - `error` (err) when an un-handled socket error occurs 25 | - `ignored error` (err) when an axon-handled socket error occurs, but is ignored 26 | - `socket error` (err) emitted regardless of handling, for logging purposes 27 | - `reconnect attempt` when a reconnection attempt is made 28 | - `connect` when connected to the peer, or a peer connection is accepted 29 | - `disconnect` when an accepted peer disconnects 30 | - `bind` when the server is bound 31 | - `drop` (msg) when a message is dropped due to the HWM 32 | - `flush` (msgs) queued when messages are flushed on connection 33 | 34 | ## Patterns 35 | 36 | - push / pull 37 | - pub / sub 38 | - req / rep 39 | - pub-emitter / sub-emitter 40 | 41 | ## Mixed argument types 42 | 43 | Backed by [node-amp-message](https://github.com/tj/node-amp-message) 44 | you may pass strings, objects, and buffers as arguments. 45 | 46 | ```js 47 | push.send('image', { w: 100, h: 200 }, imageBuffer); 48 | pull.on('message', function(type, size, img){}); 49 | ``` 50 | 51 | ## Push / Pull 52 | 53 | `PushSocket`s distribute messages round-robin: 54 | 55 | ```js 56 | var axon = require('axon'); 57 | var sock = axon.socket('push'); 58 | 59 | sock.bind(3000); 60 | console.log('push server started'); 61 | 62 | setInterval(function(){ 63 | sock.send('hello'); 64 | }, 150); 65 | ``` 66 | 67 | Receiver of `PushSocket` messages: 68 | 69 | ```js 70 | var axon = require('axon'); 71 | var sock = axon.socket('pull'); 72 | 73 | sock.connect(3000); 74 | 75 | sock.on('message', function(msg){ 76 | console.log(msg.toString()); 77 | }); 78 | ``` 79 | 80 | 81 | Both `PushSocket`s and `PullSocket`s may `.bind()` or `.connect()`. In the 82 | following configuration the push socket is bound and pull "workers" connect 83 | to it to receive work: 84 | 85 | ![push bind](http://f.cl.ly/items/473u3m1a0k1i0J0I3s04/ss-push.png) 86 | 87 | This configuration shows the inverse, where workers connect to a "sink" 88 | to push results: 89 | 90 | ![pull bind](http://f.cl.ly/items/3Y0j2v153Q0l1r373i0H/ss-pull.png) 91 | 92 | ## Pub / Sub 93 | 94 | `PubSocket`s send messages to all subscribers without queueing. This is an 95 | important difference when compared to a `PushSocket`, where the delivery of 96 | messages will be queued during disconnects and sent again upon the next connection. 97 | 98 | ```js 99 | var axon = require('axon'); 100 | var sock = axon.socket('pub'); 101 | 102 | sock.bind(3000); 103 | console.log('pub server started'); 104 | 105 | setInterval(function(){ 106 | sock.send('hello'); 107 | }, 500); 108 | ``` 109 | 110 | `SubSocket` simply receives any messages from a `PubSocket`: 111 | 112 | ```js 113 | var axon = require('axon'); 114 | var sock = axon.socket('sub'); 115 | 116 | sock.connect(3000); 117 | 118 | sock.on('message', function(msg){ 119 | console.log(msg.toString()); 120 | }); 121 | ``` 122 | 123 | `SubSocket`s may optionally `.subscribe()` to one or more "topics" (the first multipart value), 124 | using string patterns or regular expressions: 125 | 126 | ```js 127 | var axon = require('axon'); 128 | var sock = axon.socket('sub'); 129 | 130 | sock.connect(3000); 131 | sock.subscribe('user:login'); 132 | sock.subscribe('upload:*:progress'); 133 | 134 | sock.on('message', function(topic, msg){ 135 | 136 | }); 137 | ``` 138 | 139 | ## Req / Rep 140 | 141 | `ReqSocket` is similar to a `PushSocket` in that it round-robins messages 142 | to connected `RepSocket`s, however it differs in that this communication is 143 | bi-directional, every `req.send()` _must_ provide a callback which is invoked 144 | when the `RepSocket` replies. 145 | 146 | ```js 147 | var axon = require('axon'); 148 | var sock = axon.socket('req'); 149 | 150 | sock.bind(3000); 151 | 152 | sock.send(img, function(res){ 153 | 154 | }); 155 | ``` 156 | 157 | `RepSocket`s receive a `reply` callback that is used to respond to the request, 158 | you may have several of these nodes. 159 | 160 | ```js 161 | var axon = require('axon'); 162 | var sock = axon.socket('rep'); 163 | 164 | sock.connect(3000); 165 | 166 | sock.on('message', function(img, reply){ 167 | // resize the image 168 | reply(img); 169 | }); 170 | ``` 171 | 172 | Like other sockets you may provide multiple arguments or an array of arguments, 173 | followed by the callbacks. For example here we provide a task name of "resize" 174 | to facilitate multiple tasks over a single socket: 175 | 176 | ```js 177 | var axon = require('axon'); 178 | var sock = axon.socket('req'); 179 | 180 | sock.bind(3000); 181 | 182 | sock.send('resize', img, function(res){ 183 | 184 | }); 185 | ``` 186 | 187 | Respond to the "resize" task: 188 | 189 | ```js 190 | var axon = require('axon'); 191 | var sock = axon.socket('rep'); 192 | 193 | sock.connect(3000); 194 | 195 | sock.on('message', function(task, img, reply){ 196 | switch (task) { 197 | case 'resize': 198 | // resize the image 199 | reply(img); 200 | break; 201 | } 202 | }); 203 | ``` 204 | 205 | ## PubEmitter / SubEmitter 206 | 207 | `PubEmitter` and `SubEmitter` are higher-level `Pub` / `Sub` sockets, using the "json" codec to behave much like node's `EventEmitter`. When a `SubEmitter`'s `.on()` method is invoked, the event name is `.subscribe()`d for you. Each wildcard (`*`) or regexp capture group is passed to the callback along with regular message arguments. 208 | 209 | app.js: 210 | 211 | ```js 212 | var axon = require('axon'); 213 | var sock = axon.socket('pub-emitter'); 214 | 215 | sock.connect(3000); 216 | 217 | setInterval(function(){ 218 | sock.emit('login', { name: 'tobi' }); 219 | }, 500); 220 | ``` 221 | 222 | logger.js: 223 | 224 | ```js 225 | var axon = require('axon'); 226 | var sock = axon.socket('sub-emitter'); 227 | 228 | sock.bind(3000); 229 | 230 | sock.on('user:login', function(user){ 231 | console.log('%s signed in', user.name); 232 | }); 233 | 234 | sock.on('user:*', function(action, user){ 235 | console.log('%s %s', user.name, action); 236 | }); 237 | 238 | sock.on('*', function(event){ 239 | console.log(arguments); 240 | }); 241 | ``` 242 | 243 | ## Socket Options 244 | 245 | Every socket has associated options that can be configured via `get/set`. 246 | 247 | - `identity` - the "name" of the socket that uniqued identifies it. 248 | - `retry timeout` - connection retry timeout in milliseconds [100] 249 | - `retry max timeout` - the cap for retry timeout length in milliseconds [5000] 250 | - `hwm` - the high water mark threshold for queues [Infinity] 251 | 252 | ## Binding / Connecting 253 | 254 | In addition to passing a portno, binding to INADDR_ANY by default, you 255 | may also specify the hostname via `.bind(port, host)`, another alternative 256 | is to specify the url much like zmq via `tcp://:`, thus 257 | the following are equivalent: 258 | 259 | ``` 260 | sock.bind(3000) 261 | sock.bind(3000, '0.0.0.0') 262 | sock.bind('tcp://0.0.0.0:3000') 263 | 264 | sock.connect(3000) 265 | sock.connect(3000, '0.0.0.0') 266 | sock.connect('tcp://0.0.0.0:3000') 267 | ``` 268 | 269 | You may also use unix domain sockets: 270 | 271 | ``` 272 | sock.bind('unix:///some/path') 273 | sock.connect('unix:///some/path') 274 | ``` 275 | 276 | ## Protocol 277 | 278 | Axon 2.x uses the extremely simple [AMP](https://github.com/tj/node-amp) protocol to send messages on the wire. Codecs are no longer required as they were in Axon 1.x. 279 | 280 | ## Performance 281 | 282 | Preliminary benchmarks on my Macbook Pro based on 10 messages 283 | per tick as a realistic production application would likely have 284 | even less than this. "better" numbers may be achieved with batching 285 | and a larger messages/tick count however this is not realistic. 286 | 287 | 64 byte messages: 288 | 289 | ``` 290 | 291 | min: 47,169 ops/s 292 | mean: 465,127 ops/s 293 | median: 500,000 ops/s 294 | total: 2,325,636 ops in 5s 295 | through: 28.39 mb/s 296 | 297 | ``` 298 | 299 | 1k messages: 300 | 301 | ``` 302 | 303 | min: 48,076 ops/s 304 | mean: 120,253 ops/s 305 | median: 121,951 ops/s 306 | total: 601,386 ops in 5.001s 307 | through: 117.43 mb/s 308 | 309 | ``` 310 | 311 | 8k messages: 312 | 313 | ``` 314 | 315 | min: 36,496 ops/s 316 | mean: 53,194 ops/s 317 | median: 50,505 ops/s 318 | total: 266,506 ops in 5.01s 319 | through: 405.84 mb/s 320 | 321 | ```` 322 | 323 | 32k messages: 324 | 325 | ``` 326 | 327 | min: 12,077 ops/s 328 | mean: 14,792 ops/s 329 | median: 16,233 ops/s 330 | total: 74,186 ops in 5.015s 331 | through: 462.28 mb/s 332 | 333 | ``` 334 | 335 | ## What's it good for? 336 | 337 | Axon are not meant to combat zeromq nor provide feature parity, 338 | but provide a nice solution when you don't need the insane 339 | nanosecond latency or language interoperability that zeromq provides 340 | as axon do not rely on any third-party compiled libraries. 341 | 342 | ## Running tests 343 | 344 | ``` 345 | $ npm install 346 | $ make test 347 | ``` 348 | 349 | ## Authors 350 | 351 | - [tj](http://github.com/tj) 352 | - [gjohnson](https://github.com/gjohnson) 353 | 354 | ## Links 355 | 356 | - [Screencast](https://vimeo.com/45818408) 357 | - [Axon RPC](https://github.com/tj/axon-rpc) 358 | 359 | ## License 360 | 361 | MIT 362 | -------------------------------------------------------------------------------- /lib/sockets/sock.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var Emitter = require('events').EventEmitter; 7 | var Configurable = require('configurable'); 8 | var debug = require('debug')('axon:sock'); 9 | var Message = require('amp-message'); 10 | var Parser = require('amp').Stream; 11 | var url = require('url'); 12 | var net = require('net'); 13 | var fs = require('fs'); 14 | 15 | /** 16 | * Errors to ignore. 17 | */ 18 | 19 | var ignore = [ 20 | 'ECONNREFUSED', 21 | 'ECONNRESET', 22 | 'ETIMEDOUT', 23 | 'EHOSTUNREACH', 24 | 'ENETUNREACH', 25 | 'ENETDOWN', 26 | 'EPIPE', 27 | 'ENOENT' 28 | ]; 29 | 30 | /** 31 | * Expose `Socket`. 32 | */ 33 | 34 | module.exports = Socket; 35 | 36 | /** 37 | * Initialize a new `Socket`. 38 | * 39 | * A "Socket" encapsulates the ability of being 40 | * the "client" or the "server" depending on 41 | * whether `connect()` or `bind()` was called. 42 | * 43 | * @api private 44 | */ 45 | 46 | function Socket() { 47 | this.server = null; 48 | this.socks = []; 49 | this.settings = {}; 50 | this.set('hwm', Infinity); 51 | this.set('identity', String(process.pid)); 52 | this.set('retry timeout', 100); 53 | this.set('retry max timeout', 5000); 54 | } 55 | 56 | /** 57 | * Inherit from `Emitter.prototype`. 58 | */ 59 | 60 | Socket.prototype.__proto__ = Emitter.prototype; 61 | 62 | /** 63 | * Make it configurable `.set()` etc. 64 | */ 65 | 66 | Configurable(Socket.prototype); 67 | 68 | /** 69 | * Use the given `plugin`. 70 | * 71 | * @param {Function} plugin 72 | * @api private 73 | */ 74 | 75 | Socket.prototype.use = function(plugin){ 76 | plugin(this); 77 | return this; 78 | }; 79 | 80 | /** 81 | * Creates a new `Message` and write the `args`. 82 | * 83 | * @param {Array} args 84 | * @return {Buffer} 85 | * @api private 86 | */ 87 | 88 | Socket.prototype.pack = function(args){ 89 | var msg = new Message(args); 90 | return msg.toBuffer(); 91 | }; 92 | 93 | /** 94 | * Close all open underlying sockets. 95 | * 96 | * @api private 97 | */ 98 | 99 | Socket.prototype.closeSockets = function(){ 100 | debug('%s closing %d connections', this.type, this.socks.length); 101 | this.socks.forEach(function(sock){ 102 | sock.destroy(); 103 | }); 104 | }; 105 | 106 | /** 107 | * Close the socket. 108 | * 109 | * Delegates to the server or clients 110 | * based on the socket `type`. 111 | * 112 | * @param {Function} [fn] 113 | * @api public 114 | */ 115 | 116 | Socket.prototype.close = function(fn){ 117 | debug('%s closing', this.type); 118 | this.closing = true; 119 | this.closeSockets(); 120 | if (this.server) this.closeServer(fn); 121 | }; 122 | 123 | /** 124 | * Close the server. 125 | * 126 | * @param {Function} [fn] 127 | * @api public 128 | */ 129 | 130 | Socket.prototype.closeServer = function(fn){ 131 | debug('%s closing server', this.type); 132 | this.server.on('close', this.emit.bind(this, 'close')); 133 | this.server.close(); 134 | fn && fn(); 135 | }; 136 | 137 | /** 138 | * Return the server address. 139 | * 140 | * @return {Object} 141 | * @api public 142 | */ 143 | 144 | Socket.prototype.address = function(){ 145 | if (!this.server) return; 146 | var addr = this.server.address(); 147 | addr.string = 'tcp://' + addr.address + ':' + addr.port; 148 | return addr; 149 | }; 150 | 151 | /** 152 | * Remove `sock`. 153 | * 154 | * @param {Socket} sock 155 | * @api private 156 | */ 157 | 158 | Socket.prototype.removeSocket = function(sock){ 159 | var i = this.socks.indexOf(sock); 160 | if (!~i) return; 161 | debug('%s remove socket %d', this.type, i); 162 | this.socks.splice(i, 1); 163 | }; 164 | 165 | /** 166 | * Add `sock`. 167 | * 168 | * @param {Socket} sock 169 | * @api private 170 | */ 171 | 172 | Socket.prototype.addSocket = function(sock){ 173 | var parser = new Parser; 174 | var i = this.socks.push(sock) - 1; 175 | debug('%s add socket %d', this.type, i); 176 | sock.pipe(parser); 177 | parser.on('data', this.onmessage(sock)); 178 | }; 179 | 180 | /** 181 | * Handle `sock` errors. 182 | * 183 | * Emits: 184 | * 185 | * - `error` (err) when the error is not ignored 186 | * - `ignored error` (err) when the error is ignored 187 | * - `socket error` (err) regardless of ignoring 188 | * 189 | * @param {Socket} sock 190 | * @api private 191 | */ 192 | 193 | Socket.prototype.handleErrors = function(sock){ 194 | var self = this; 195 | sock.on('error', function(err){ 196 | debug('%s error %s', self.type, err.code || err.message); 197 | self.emit('socket error', err); 198 | self.removeSocket(sock); 199 | if (!~ignore.indexOf(err.code)) return self.emit('error', err); 200 | debug('%s ignored %s', self.type, err.code); 201 | self.emit('ignored error', err); 202 | }); 203 | }; 204 | 205 | /** 206 | * Handles framed messages emitted from the parser, by 207 | * default it will go ahead and emit the "message" events on 208 | * the socket. However, if the "higher level" socket needs 209 | * to hook into the messages before they are emitted, it 210 | * should override this method and take care of everything 211 | * it self, including emitted the "message" event. 212 | * 213 | * @param {net.Socket} sock 214 | * @return {Function} closure(msg, mulitpart) 215 | * @api private 216 | */ 217 | 218 | Socket.prototype.onmessage = function(sock){ 219 | var self = this; 220 | return function(buf){ 221 | var msg = new Message(buf); 222 | self.emit.apply(self, ['message'].concat(msg.args)); 223 | }; 224 | }; 225 | 226 | /** 227 | * Connect to `port` at `host` and invoke `fn()`. 228 | * 229 | * Defaults `host` to localhost. 230 | * 231 | * TODO: needs big cleanup 232 | * 233 | * @param {Number|String} port 234 | * @param {String} host 235 | * @param {Function} fn 236 | * @return {Socket} 237 | * @api public 238 | */ 239 | 240 | Socket.prototype.connect = function(port, host, fn){ 241 | var self = this; 242 | if ('server' == this.type) throw new Error('cannot connect() after bind()'); 243 | if ('function' == typeof host) { 244 | fn = host; 245 | host = undefined; 246 | } 247 | 248 | if ('string' == typeof port) { 249 | port = url.parse(port); 250 | 251 | if (port.protocol == "unix:") { 252 | host = fn; 253 | fn = undefined; 254 | port = port.pathname; 255 | } else { 256 | host = port.hostname || '0.0.0.0'; 257 | port = parseInt(port.port, 10); 258 | } 259 | } else { 260 | host = host || '0.0.0.0'; 261 | } 262 | 263 | var max = self.get('retry max timeout'); 264 | var sock = new net.Socket; 265 | sock.setNoDelay(); 266 | this.type = 'client'; 267 | 268 | this.handleErrors(sock); 269 | 270 | sock.on('close', function(){ 271 | self.emit('socket close', sock); 272 | self.connected = false; 273 | self.removeSocket(sock); 274 | if (self.closing) return self.emit('close'); 275 | var retry = self.retry || self.get('retry timeout'); 276 | setTimeout(function(){ 277 | debug('%s attempting reconnect', self.type); 278 | self.emit('reconnect attempt'); 279 | sock.destroy(); 280 | self.connect(port, host); 281 | self.retry = Math.round(Math.min(max, retry * 1.5)); 282 | }, retry); 283 | }); 284 | 285 | sock.on('connect', function(){ 286 | debug('%s connect', self.type); 287 | self.connected = true; 288 | self.addSocket(sock); 289 | self.retry = self.get('retry timeout'); 290 | self.emit('connect', sock); 291 | fn && fn(); 292 | }); 293 | 294 | debug('%s connect attempt %s:%s', self.type, host, port); 295 | sock.connect(port, host); 296 | return this; 297 | }; 298 | 299 | /** 300 | * Handle connection. 301 | * 302 | * @param {Socket} sock 303 | * @api private 304 | */ 305 | 306 | Socket.prototype.onconnect = function(sock){ 307 | var self = this; 308 | var addr = sock.remoteAddress + ':' + sock.remotePort; 309 | debug('%s accept %s', self.type, addr); 310 | this.addSocket(sock); 311 | this.handleErrors(sock); 312 | this.emit('connect', sock); 313 | sock.on('close', function(){ 314 | debug('%s disconnect %s', self.type, addr); 315 | self.emit('disconnect', sock); 316 | self.removeSocket(sock); 317 | }); 318 | }; 319 | 320 | /** 321 | * Bind to `port` at `host` and invoke `fn()`. 322 | * 323 | * Defaults `host` to INADDR_ANY. 324 | * 325 | * Emits: 326 | * 327 | * - `connection` when a client connects 328 | * - `disconnect` when a client disconnects 329 | * - `bind` when bound and listening 330 | * 331 | * @param {Number|String} port 332 | * @param {Function} fn 333 | * @return {Socket} 334 | * @api public 335 | */ 336 | 337 | Socket.prototype.bind = function(port, host, fn){ 338 | var self = this; 339 | if ('client' == this.type) throw new Error('cannot bind() after connect()'); 340 | if ('function' == typeof host) { 341 | fn = host; 342 | host = undefined; 343 | } 344 | 345 | var unixSocket = false; 346 | 347 | if ('string' == typeof port) { 348 | port = url.parse(port); 349 | 350 | if ('unix:' == port.protocol) { 351 | host = fn; 352 | fn = undefined; 353 | port = port.pathname; 354 | unixSocket = true; 355 | } else { 356 | host = port.hostname || '0.0.0.0'; 357 | port = parseInt(port.port, 10); 358 | } 359 | } else { 360 | host = host || '0.0.0.0'; 361 | } 362 | 363 | this.type = 'server'; 364 | 365 | this.server = net.createServer(this.onconnect.bind(this)); 366 | 367 | debug('%s bind %s:%s', this.type, host, port); 368 | this.server.on('listening', this.emit.bind(this, 'bind')); 369 | 370 | if (unixSocket) { 371 | // TODO: move out 372 | this.server.on('error', function(e) { 373 | if (e.code == 'EADDRINUSE') { 374 | // Unix file socket and error EADDRINUSE is the case if 375 | // the file socket exists. We check if other processes 376 | // listen on file socket, otherwise it is a stale socket 377 | // that we could reopen 378 | // We try to connect to socket via plain network socket 379 | var clientSocket = new net.Socket(); 380 | 381 | clientSocket.on('error', function(e2) { 382 | if (e2.code == 'ECONNREFUSED') { 383 | // No other server listening, so we can delete stale 384 | // socket file and reopen server socket 385 | fs.unlink(port); 386 | self.server.listen(port, host, fn); 387 | } 388 | }); 389 | 390 | clientSocket.connect({path: port}, function() { 391 | // Connection is possible, so other server is listening 392 | // on this file socket 393 | throw e; 394 | }); 395 | } 396 | }); 397 | } 398 | 399 | this.server.listen(port, host, fn); 400 | return this; 401 | }; 402 | --------------------------------------------------------------------------------