├── .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 |
43 |
44 |
48 |
49 |
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 |
43 |
44 |
48 |
49 |
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 |
--------------------------------------------------------------------------------