diff --git a/NEWS.md b/NEWS.md index 45446c1b..4ad698a4 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,11 @@ +2015-10-25 +========== + +In order to support future clustering support, the legacy `/sioconfig` +endpoint is being deprecated. Instead, you should make a request to +`/socketconfig/.json`. See [the +documentation](docs/socketconfig.md) for more information. + 2015-10-04 ========== diff --git a/docs/socketconfig.md b/docs/socketconfig.md new file mode 100644 index 00000000..0c57a5c7 --- /dev/null +++ b/docs/socketconfig.md @@ -0,0 +1,57 @@ +Socket.IO Client Configuration +============================== + +As of 2015-10-25, the legacy `/sioconfig` JavaScript for retrieving connection +information is being deprecated in favor of a new API. The purpose of this +change is to allow partitioning channels across multiple servers in order to +better handle increasing traffic. + +To get the socket.io configuration for the server hosting a particular channel, +make a `GET` request to `/socketconfig/.json`. The response will +be a JSON object containing a list of acceptable servers to connect to, or an +error message. + +Examples: + +``` +GET /socketconfig/test.json +200 OK + +{ + "servers": [ + { + "url": "https://localhost:8443", + "secure": true + }, + { + "url": "http://localhost:1337", + "secure": false + }, + { + "url": "https://local6:8443", + "secure": true, + "ipv6": true + }, + { + "url": "http://local6:1337", + "secure": false, + "ipv6": true + } + ] +} + +GET /socketconfig/$invalid$.json +404 Not Found + +{ + "error": "Channel \"$invalid$\" does not exist." +} +``` + +Each entry in the `servers` array has `"secure":true` if the connection is +secured with TLS, otherwise it it is false. An entry with `"ipv6":true` +indicates that the server is listening on the IPv6 protocol. + +You can pick any URL to connect socket.io to in order to join the specified +channel. I recommend picking one with `"secure":true`, only choosing an +insecure connection if implementing a TLS connection is infeasible. diff --git a/package.json b/package.json index 454beddc..16de6baa 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "author": "Calvin Montgomery", "name": "CyTube", "description": "Online media synchronizer and chat", - "version": "3.11.2", + "version": "3.12.0", "repository": { "url": "http://github.com/calzoneman/sync" }, diff --git a/src/configuration/ioconfig.js b/src/configuration/ioconfig.js new file mode 100644 index 00000000..edb3093b --- /dev/null +++ b/src/configuration/ioconfig.js @@ -0,0 +1,47 @@ +export default class IOConfiguration { + constructor(config) { + this.config = config; + } + + getSocketEndpoints() { + return this.config.endpoints.slice(); + } +} + +IOConfiguration.fromOldConfig = function (oldConfig) { + const config = { + endpoints: [] + }; + + if (oldConfig.get('io.ipv4-ssl')) { + config.endpoints.push({ + url: oldConfig.get('io.ipv4-ssl'), + secure: true + }); + } + + if (oldConfig.get('io.ipv4-nossl')) { + config.endpoints.push({ + url: oldConfig.get('io.ipv4-nossl'), + secure: false + }); + } + + if (oldConfig.get('io.ipv6-ssl')) { + config.endpoints.push({ + url: oldConfig.get('io.ipv4-ssl'), + secure: true, + ipv6: true + }); + } + + if (oldConfig.get('io.ipv6-nossl')) { + config.endpoints.push({ + url: oldConfig.get('io.ipv4-nossl'), + secure: false, + ipv6: true + }); + } + + return new IOConfiguration(config); +}; diff --git a/src/io/cluster/nullclusterclient.js b/src/io/cluster/nullclusterclient.js new file mode 100644 index 00000000..89284355 --- /dev/null +++ b/src/io/cluster/nullclusterclient.js @@ -0,0 +1,14 @@ +import Promise from 'bluebird'; + +export default class NullClusterClient { + constructor(ioConfig) { + this.ioConfig = ioConfig; + } + + getSocketConfig(channel) { + const servers = this.ioConfig.getSocketEndpoints(); + return Promise.resolve({ + servers: servers + }); + } +} diff --git a/src/web/routes/socketconfig.js b/src/web/routes/socketconfig.js new file mode 100644 index 00000000..836a6916 --- /dev/null +++ b/src/web/routes/socketconfig.js @@ -0,0 +1,27 @@ +import IOConfiguration from '../../configuration/ioconfig'; +import NullClusterClient from '../../io/cluster/nullclusterclient'; +import Config from '../../config'; +import CyTubeUtil from '../../utilities'; +import Logger from '../../logger'; + +export default function initialize(app) { + const ioConfig = IOConfiguration.fromOldConfig(Config); + const clusterClient = new NullClusterClient(ioConfig); + + app.get('/socketconfig/:channel.json', (req, res) => { + if (!req.params.channel || !CyTubeUtil.isValidChannelName(req.params.channel)) { + return res.status(404).json({ + error: `Channel "${req.params.channel}" does not exist.` + }); + } + + clusterClient.getSocketConfig(req.params.channel).then(config => { + res.json(config); + }).catch(err => { + Logger.errlog.log(err.stack); + return res.status(500).json({ + error: err.message + }); + }); + }); +} diff --git a/src/web/webserver.js b/src/web/webserver.js index 3bd43647..a15d0d47 100644 --- a/src/web/webserver.js +++ b/src/web/webserver.js @@ -117,7 +117,8 @@ function handleIndex(req, res) { } /** - * Handles a request for the socket.io information + * Legacy socket.io configuration endpoint. This is being migrated to + * /socketconfig/.json (see ./routes/socketconfig.js) */ function handleSocketConfig(req, res) { if (/\.json$/.test(req.path)) { @@ -243,6 +244,7 @@ module.exports = { app.get("/r/:channel", handleChannel); app.get("/", handleIndex); app.get("/sioconfig(.json)?", handleSocketConfig); + require("./routes/socketconfig")(app); app.get("/useragreement", handleUserAgreement); app.get("/contact", handleContactPage); require("./auth").init(app); diff --git a/templates/channel.jade b/templates/channel.jade index 6b9a4c3b..6c8a2c6a 100644 --- a/templates/channel.jade +++ b/templates/channel.jade @@ -239,7 +239,6 @@ html(lang="en") mixin footer() script(src=sioSource) script(src="/js/data.js") - script(src="/sioconfig") script(src="/js/util.js") script(src="/js/player.js") script(src="/js/paginator.js") diff --git a/www/js/callbacks.js b/www/js/callbacks.js index 84efc394..3fbf409f 100644 --- a/www/js/callbacks.js +++ b/www/js/callbacks.js @@ -1096,27 +1096,57 @@ setupCallbacks = function() { }); })(key); } -} +}; -try { +(function () { if (typeof io === "undefined") { makeAlert("Uh oh!", "It appears the connection to " + IO_URL + " " + "has failed. If this error persists, a firewall or " + "antivirus is likely blocking the connection, or the " + "server is down.", "alert-danger") .appendTo($("#announcements")); - throw false; + Callbacks.disconnect(); + return; } - var opts = { transports: ["websocket", "polling"] }; - if (IO_URL === IO_URLS["ipv4-ssl"] || IO_URL === IO_URLS["ipv6-ssl"]) { - opts.secure = true; - socket = io(IO_URL, { secure: true }); - } - socket = io(IO_URL, opts); - setupCallbacks(); -} catch (e) { - if (e) { - Callbacks.disconnect(); - } -} + $.getJSON("/socketconfig/" + CHANNEL.name + ".json") + .done(function (socketConfig) { + if (socketConfig.error) { + makeAlert("Error", "Socket.io configuration returned error: " + + socketConfig.error, "alert-danger") + .appendTo($("#announcements")); + return; + } + + var chosenServer = null; + socketConfig.servers.forEach(function (server) { + if (chosenServer === null) { + chosenServer = server; + } else if (server.secure && !chosenServer.secure) { + chosenServer = server; + } else if (!server.ipv6Only && chosenServer.ipv6Only) { + chosenServer = server; + } + }); + + if (chosenServer === null) { + makeAlert("Error", + "Socket.io configuration was unable to find a suitable server", + "alert-danger") + .appendTo($("#announcements")); + } + + var opts = { + transports: ["websocket", "polling"], + secure: chosenServer.secure + }; + + socket = io(chosenServer.url, opts); + setupCallbacks(); + }).fail(function () { + makeAlert("Error", "Failed to retrieve socket.io configuration", + "alert-danger") + .appendTo($("#announcements")); + Callbacks.disconnect(); + }); +})();