├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── VERSION-GEN ├── examples ├── sockjs-express3 │ ├── README.md │ ├── index.html │ ├── package.json │ └── server.js └── sockjs │ ├── README.md │ ├── index.html │ ├── package.json │ └── server.js ├── multiplex_client.js ├── multiplex_server.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | examples/sockjs/node_modules 2 | 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017, Marek Majkowski. All rights reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | 3 | VER:=$(shell ./VERSION-GEN) 4 | MAJVER:=$(shell echo $(VER)|sed 's|^\([^.]\+[.][^.]\+\).*$$|\1|' ) 5 | 6 | CLIENT_ARTIFACTS=\ 7 | websocket-multiplex-$(VER).js \ 8 | websocket-multiplex-$(MAJVER).js 9 | 10 | upload_client: 11 | echo "VER=$(VER) MAJVER=$(MAJVER)" 12 | cp multiplex_client.js websocket-multiplex-$(VER).js 13 | cp multiplex_client.js websocket-multiplex-$(MAJVER).js 14 | @echo -e 'Run:' 15 | @echo -e '\ts3cmd put --acl-public index.html $(CLIENT_ARTIFACTS) s3://sockjs' 16 | @echo -e '\tmake clean' 17 | 18 | clean: 19 | rm $(CLIENT_ARTIFACTS) 20 | rm -rf examples/sockjs/node_modules 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | WebSocket-multiplex 2 | =================== 3 | 4 | WebSocket-multiplex is a small library on top of SockJS that allows 5 | you to do multiplexing over a single SockJS connection. 6 | 7 | The rationale for that is explained in details in the following blog 8 | post: 9 | 10 | * https://www.rabbitmq.com/blog/2012/02/23/how-to-compose-apps-using-websockets/ 11 | 12 | 13 | Usage from the browser 14 | ---------------------- 15 | 16 | On the client side (browser) load library like that: 17 | 18 | 20 | 21 | Alternatively, if you're using SSL: 22 | 23 | 25 | 26 | Usage example: 27 | 28 | ```javascript 29 | var sockjs_url = '/multiplex'; 30 | var sockjs = new SockJS(sockjs_url); 31 | 32 | var multiplexer = new WebSocketMultiplex(sockjs); 33 | var ann = multiplexer.channel('ann'); 34 | var bob = multiplexer.channel('bob'); 35 | var carl = multiplexer.channel('carl'); 36 | ``` 37 | 38 | Usage from the node.js server 39 | ----------------------------- 40 | 41 | On the node.js server side, you can use npm to get the code: 42 | 43 | npm install websocket-multiplex 44 | 45 | And a simplistic example: 46 | 47 | ```javascript 48 | var multiplex_server = require('websocket-multiplex'); 49 | 50 | // 1. Setup SockJS server 51 | var service = sockjs.createServer(); 52 | 53 | // 2. Setup multiplexing 54 | var multiplexer = new multiplex_server.MultiplexServer(service); 55 | 56 | var ann = multiplexer.registerChannel('ann'); 57 | ann.on('connection', function(conn) { 58 | conn.write('Ann says hi!'); 59 | conn.on('data', function(data) { 60 | conn.write('Ann nods: ' + data); 61 | }); 62 | }); 63 | 64 | // 3. Setup http server 65 | var server = http.createServer(); 66 | sockjs_echo.installHandlers(server, {prefix:'/multiplex'}); 67 | var app = express.createServer(); 68 | ``` 69 | 70 | For a full-featured example see the 71 | [/examples/sockjs](https://github.com/sockjs/websocket-multiplex/tree/master/examples/sockjs) 72 | directory. 73 | 74 | 75 | Protocol 76 | -------- 77 | 78 | The underlying protocol is quite simple. Each message is a string consisting of 79 | three comma separated parts: _type_, _topic_ and _payload_. There are 80 | three valid message types: 81 | 82 | * `sub` - expresses a will to subscribe to a given _topic_. 83 | * `msg` - a message with _payload_ is being sent on a _topic_. 84 | * `uns` - a will to unsubscribe from a _topic_. 85 | 86 | Invalid messages like wrong unsubscriptions or publishes to a _topic_ 87 | to which a client was not subscribed to are simply ignored. 88 | 89 | This protocol assumes that both parties are generally willing to 90 | cooperate and that no party makes errors. All invalid 91 | messages should be ignored. 92 | 93 | It's important to notice that the namespace is shared between both 94 | parties. It is not a good idea to use the same topic names on the 95 | client and on the server side because both parties may unsubscribe 96 | the other from a topic. 97 | -------------------------------------------------------------------------------- /VERSION-GEN: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var fs = require('fs') 3 | console.log(JSON.parse(fs.readFileSync('package.json')).version) 4 | -------------------------------------------------------------------------------- /examples/sockjs-express3/README.md: -------------------------------------------------------------------------------- 1 | WebSocket-multiplex SockJS example 2 | ================================== 3 | 4 | To run this example, first install dependencies: 5 | 6 | npm install 7 | 8 | And run a server: 9 | 10 | node server.js 11 | 12 | 13 | That will spawn an http server at http://127.0.0.1:9999/ which will 14 | serve both html (served from the current directory) and also SockJS 15 | service (under the [/multiplex](http://127.0.0.1:9999/multiplex) 16 | path). 17 | 18 | With that set up, WebSocket-multiplex is able to push three virtual 19 | connections over a single SockJS connection. See the code for details. 20 | -------------------------------------------------------------------------------- /examples/sockjs-express3/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 36 | 37 |

SockJS Multiplex example

38 | 39 |
40 |
41 |
42 |
43 | 44 |
45 |
46 |
47 |
48 | 49 |
50 |
51 |
52 |
53 | 54 | 96 | 97 | -------------------------------------------------------------------------------- /examples/sockjs-express3/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "websocket-multiplex-sockjs-express3-example", 3 | "version" : "0.0.0-unreleasable", 4 | "dependencies" : { 5 | "express" : "3.0.0", 6 | "sockjs" : "0.3.x", 7 | "websocket-multiplex" : "*" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/sockjs-express3/server.js: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | var express = require('express'); 3 | var sockjs = require('sockjs'); 4 | 5 | var websocket_multiplex = require('websocket-multiplex'); 6 | 7 | 8 | // 1. Setup SockJS server 9 | var sockjs_opts = {sockjs_url: "http://cdn.sockjs.org/sockjs-0.3.min.js"}; 10 | var service = sockjs.createServer(sockjs_opts); 11 | 12 | 13 | // 2. Setup multiplexing 14 | var multiplexer = new websocket_multiplex.MultiplexServer(service); 15 | 16 | var ann = multiplexer.registerChannel('ann'); 17 | ann.on('connection', function(conn) { 18 | conn.write('Ann says hi!'); 19 | conn.on('data', function(data) { 20 | conn.write('Ann nods: ' + data); 21 | }); 22 | }); 23 | 24 | var bob = multiplexer.registerChannel('bob'); 25 | bob.on('connection', function(conn) { 26 | conn.write('Bob doesn\'t agree.'); 27 | conn.on('data', function(data) { 28 | conn.write('Bob says no to: ' + data); 29 | }); 30 | }); 31 | 32 | var carl = multiplexer.registerChannel('carl'); 33 | carl.on('connection', function(conn) { 34 | conn.write('Carl says goodbye!'); 35 | // Explicitly cancel connection 36 | conn.end(); 37 | }); 38 | 39 | 40 | // 3. Express server 41 | var app = express(); 42 | var server = http.createServer(app); 43 | 44 | service.installHandlers(server, {prefix:'/multiplex'}); 45 | 46 | console.log(' [*] Listening on 0.0.0.0:9999' ); 47 | server.listen(9999, '0.0.0.0'); 48 | 49 | app.get('/', function (req, res) { 50 | res.sendfile(__dirname + '/index.html'); 51 | }); 52 | 53 | app.get('/multiplex.js', function (req, res) { 54 | res.sendfile(__dirname + '/multiplex.js'); 55 | }); 56 | -------------------------------------------------------------------------------- /examples/sockjs/README.md: -------------------------------------------------------------------------------- 1 | WebSocket-multiplex SockJS example 2 | ================================== 3 | 4 | To run this example, first install dependencies: 5 | 6 | npm install 7 | 8 | And run a server: 9 | 10 | node server.js 11 | 12 | 13 | That will spawn an http server at http://127.0.0.1:9999/ which will 14 | serve both html (served from the current directory) and also SockJS 15 | service (under the [/multiplex](http://127.0.0.1:9999/multiplex) 16 | path). 17 | 18 | With that set up, WebSocket-multiplex is able to push three virtual 19 | connections over a single SockJS connection. See the code for details. 20 | -------------------------------------------------------------------------------- /examples/sockjs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 36 | 37 |

SockJS Multiplex example

38 | 39 |
40 |
41 |
42 |
43 | 44 |
45 |
46 |
47 |
48 | 49 |
50 |
51 |
52 |
53 | 54 | 96 | 97 | -------------------------------------------------------------------------------- /examples/sockjs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "websocket-multiplex-sockjs-example", 3 | "version" : "0.0.0-unreleasable", 4 | "dependencies" : { 5 | "express" : "2.5.8", 6 | "sockjs" : "0.3.x", 7 | "websocket-multiplex" : "*" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/sockjs/server.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var sockjs = require('sockjs'); 3 | 4 | var websocket_multiplex = require('websocket-multiplex'); 5 | 6 | 7 | // 1. Setup SockJS server 8 | var sockjs_opts = {sockjs_url: "http://cdn.sockjs.org/sockjs-0.3.min.js"}; 9 | var service = sockjs.createServer(sockjs_opts); 10 | 11 | 12 | // 2. Setup multiplexing 13 | var multiplexer = new websocket_multiplex.MultiplexServer(service); 14 | 15 | var ann = multiplexer.registerChannel('ann'); 16 | ann.on('connection', function(conn) { 17 | conn.write('Ann says hi!'); 18 | conn.on('data', function(data) { 19 | conn.write('Ann nods: ' + data); 20 | }); 21 | }); 22 | 23 | var bob = multiplexer.registerChannel('bob'); 24 | bob.on('connection', function(conn) { 25 | conn.write('Bob doesn\'t agree.'); 26 | conn.on('data', function(data) { 27 | conn.write('Bob says no to: ' + data); 28 | }); 29 | }); 30 | 31 | var carl = multiplexer.registerChannel('carl'); 32 | carl.on('connection', function(conn) { 33 | conn.write('Carl says goodbye!'); 34 | // Explicitly cancel connection 35 | conn.end(); 36 | }); 37 | 38 | 39 | // 3. Express server 40 | var app = express.createServer(); 41 | service.installHandlers(app, {prefix:'/multiplex'}); 42 | 43 | console.log(' [*] Listening on 0.0.0.0:9999' ); 44 | app.listen(9999, '0.0.0.0'); 45 | 46 | app.get('/', function (req, res) { 47 | res.sendfile(__dirname + '/index.html'); 48 | }); 49 | 50 | app.get('/multiplex.js', function (req, res) { 51 | res.sendfile(__dirname + '/multiplex.js'); 52 | }); 53 | -------------------------------------------------------------------------------- /multiplex_client.js: -------------------------------------------------------------------------------- 1 | var WebSocketMultiplex = (function(){ 2 | 3 | 4 | // **** 5 | 6 | var DumbEventTarget = function() { 7 | this._listeners = {}; 8 | }; 9 | DumbEventTarget.prototype._ensure = function(type) { 10 | if(!(type in this._listeners)) this._listeners[type] = []; 11 | }; 12 | DumbEventTarget.prototype.addEventListener = function(type, listener) { 13 | this._ensure(type); 14 | this._listeners[type].push(listener); 15 | }; 16 | DumbEventTarget.prototype.emit = function(type) { 17 | this._ensure(type); 18 | var args = Array.prototype.slice.call(arguments, 1); 19 | if(this['on' + type]) this['on' + type].apply(this, args); 20 | for(var i=0; i < this._listeners[type].length; i++) { 21 | this._listeners[type][i].apply(this, args); 22 | } 23 | }; 24 | 25 | 26 | // **** 27 | 28 | var WebSocketMultiplex = function(ws) { 29 | var that = this; 30 | this.ws = ws; 31 | this.channels = {}; 32 | this.ws.addEventListener('message', function(e) { 33 | var t = e.data.split(','); 34 | var type = t.shift(), name = t.shift(), payload = t.join(); 35 | if(!(name in that.channels)) { 36 | return; 37 | } 38 | var sub = that.channels[name]; 39 | 40 | switch(type) { 41 | case 'uns': 42 | delete that.channels[name]; 43 | sub.emit('close', {}); 44 | break; 45 | case 'msg': 46 | sub.emit('message', {data: payload}); 47 | break; 48 | } 49 | }); 50 | }; 51 | WebSocketMultiplex.prototype.channel = function(raw_name) { 52 | return this.channels[escape(raw_name)] = 53 | new Channel(this.ws, escape(raw_name), this.channels); 54 | }; 55 | 56 | 57 | var Channel = function(ws, name, channels) { 58 | DumbEventTarget.call(this); 59 | var that = this; 60 | this.ws = ws; 61 | this.name = name; 62 | this.channels = channels; 63 | var onopen = function() { 64 | that.ws.send('sub,' + that.name); 65 | that.emit('open'); 66 | }; 67 | if(ws.readyState > 0) { 68 | setTimeout(onopen, 0); 69 | } else { 70 | this.ws.addEventListener('open', onopen); 71 | } 72 | }; 73 | Channel.prototype = new DumbEventTarget() 74 | 75 | Channel.prototype.send = function(data) { 76 | this.ws.send('msg,' + this.name + ',' + data); 77 | }; 78 | Channel.prototype.close = function() { 79 | var that = this; 80 | this.ws.send('uns,' + this.name); 81 | delete this.channels[this.name]; 82 | setTimeout(function(){that.emit('close', {});},0); 83 | }; 84 | 85 | return WebSocketMultiplex; 86 | })(); 87 | -------------------------------------------------------------------------------- /multiplex_server.js: -------------------------------------------------------------------------------- 1 | var events = require('events'); 2 | var stream = require('stream'); 3 | 4 | 5 | exports.MultiplexServer = MultiplexServer = function(service) { 6 | var that = this; 7 | this.registered_channels = {}; 8 | this.service = service; 9 | this.service.on('connection', function(conn) { 10 | var channels = {}; 11 | 12 | conn.on('data', function(message) { 13 | var t = message.split(','); 14 | var type = t.shift(), topic = t.shift(), payload = t.join(); 15 | if (!(topic in that.registered_channels)) { 16 | return; 17 | } 18 | if (topic in channels) { 19 | var sub = channels[topic]; 20 | 21 | switch(type) { 22 | case 'uns': 23 | delete channels[topic]; 24 | sub.emit('close'); 25 | break; 26 | case 'msg': 27 | sub.emit('data', payload); 28 | break; 29 | } 30 | } else { 31 | switch(type) { 32 | case 'sub': 33 | var sub = channels[topic] = new Channel(conn, topic, 34 | channels); 35 | that.registered_channels[topic].emit('connection', sub) 36 | break; 37 | } 38 | } 39 | }); 40 | conn.on('close', function() { 41 | for (topic in channels) { 42 | channels[topic].emit('close'); 43 | } 44 | channels = {}; 45 | }); 46 | }); 47 | }; 48 | 49 | MultiplexServer.prototype.registerChannel = function(name) { 50 | return this.registered_channels[escape(name)] = new events.EventEmitter(); 51 | }; 52 | 53 | 54 | var Channel = function(conn, topic, channels) { 55 | this.conn = conn; 56 | this.topic = topic; 57 | this.channels = channels; 58 | stream.Stream.call(this); 59 | }; 60 | Channel.prototype = new stream.Stream(); 61 | 62 | Channel.prototype.write = function(data) { 63 | this.conn.write('msg,' + this.topic + ',' + data); 64 | }; 65 | Channel.prototype.end = function(data) { 66 | var that = this; 67 | if (data) this.write(data); 68 | if (this.topic in this.channels) { 69 | this.conn.write('uns,' + this.topic); 70 | delete this.channels[this.topic]; 71 | process.nextTick(function(){that.emit('close');}); 72 | } 73 | }; 74 | Channel.prototype.destroy = Channel.prototype.destroySoon = 75 | function() { 76 | this.removeAllListeners(); 77 | this.end(); 78 | }; 79 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "websocket-multiplex", 3 | "author" : "Marek Majkowski", 4 | "version" : "0.1.0", 5 | "description" : "WebSocket-multiplex is a thin library on top of SockJS that allows you to do multiplexing of many virtual WebSockets connection over a single physical one.", 6 | "keywords" : ["websockets", "websocket", "sockjs", "multiplex", "multiplexing"], 7 | "homepage" : "https://github.com/sockjs/websocket-multiplex", 8 | 9 | "repository": {"type": "git", 10 | "url": "https://github.com/sockjs/websocket-multiplex.git"}, 11 | "dependencies": { 12 | "sockjs" : "*" 13 | }, 14 | "main": "multiplex_server" 15 | } 16 | --------------------------------------------------------------------------------