Software Engineer at Mathspace:
Main author of SocketCluster:
Twitter: https://twitter.com/jgrosdubois
Jonathan Gros-Dubois
There are ways to emulate WebSockets using HTTP long polling.
SockJS
These solutions are great at first but things can get difficult when you need to scale beyond a single process/host.
Emulating a single realtime connection over multiple HTTP requests has the following shortcomings:
Renders to =>
Basic example (PolymerJS):
Declarative over imperative means that you don't need to get references to particular elements and explicitly render stuff inside them.
You declare the relationship between your views and your data and the rest is automatic. Data is free to flow throughout your views. Less control logic.
Too much control can be dangerous
Writing too much control logic on the server is an anti-pattern.
Because WebSockets are bidirectional, you may be tempted to get references to specific client sockets when you want to provide them with data.
The most common question from new users
of SocketCluster who are running their code on
multiple processes goes like this:
"I want to send a message to a specific user from the server side. How can I get a reference to a
socket which is hosted on a different worker process?"
The server should regulate data flow, not dictate it. That means avoiding doing lookups to get a hold of specific sockets! Managing propertyName=>socketID mappings is hard work... Even if you've only running your code on a single process, you have to remember to cleanup the mappings when sockets leave.
The solution is to give more control to the consumer of the data (the front-end) - Pub/Sub is a good, scalable way to achieve that. With a middleware/authorization layer, you can enforce access control.
socket.emit('get', {type: 'Product', id: 123, field: 'price'}, errorAndDataHandlerFn);
socket.emit('set', {type: 'Product', id: 123, field: 'price', value: 99.95}, errorHandlerFn);
socket.emit('delete', {type: 'Product', id: 123}, errorHandlerFn);
var productChannel = socket.subscribe('Product/123/price');
productChannel.watch(channelDataHandler);
// Realtime equivalent to REST's GET method
// Equivalent to REST's POST and UPDATE methods - You can also separate this
// into 'set' and 'update' if you like
// Equivalent to REST's DELETE method
// This is the one which gives us realtime updates. In practice, you could use
// REST over HTTP instead of all the others and only use sockets for this one; that
// way the realtime update can be a progressive enhancement for newer browsers only.
scServer.on('connection', function (socket) {
socket.on('get', function (query, callback) {
var deepKey = [query.type, query.id];
if (query.field) {
deepKey.push(query.field);
}
scServer.global.get(deepKey, callback);
});
socket.on('set', function (query, callback) {
var deepKey = [query.type, query.id];
if (query.field) {
deepKey.push(query.field);
}
scServer.global.set(deepKey, query.value, function (err) {
if (!err) {
var channelName;
if (query.field) {
scServer.global.publish(query.type + '/' + query.id + '/' + query.field, query.value);
}
// TODO: Also publish to higher levels in hierarchy: query.type + '/' + query.id and just query.type channels
}
callback(err);
});
});
});
Here we are using SC's internal 'global' hierarchical data store to hold the data in memory (not persistent - So only for demo purposes) - You can use any database/datastore you like.