From 7289950b54bd4e113e5fc14e4ae35e5ee424c761 Mon Sep 17 00:00:00 2001 From: David Glasser Date: Wed, 5 Dec 2012 19:15:51 -0800 Subject: [PATCH] Implement "ddp+sockjs://" and "ddpi+sockjs://" URLs for Meteor.connect. This is not yet documented or fully supported (ie, it may change before 1.0). ddp+sockjs:// URLs are translated into https:// URLs and explicitly contain the "/sockjs" endpoint. Any '*' in the hostname should be changed into a random digit before opening a SockJS connection, to help avoid browser per-hostname connection limits. ddpi+sockjs:// is identical but uses http:// instead. The DEFAULT_DDP_ENDPOINT environment variable has been renamed DDP_DEFAULT_CONNECTION_URL. (For now, 'meteor deploy' will continue to also provide non-"ddp+sockjs://" URLs in the old environment variable so that old apps continue to work). --- packages/livedata/client_convenience.js | 12 +++--- packages/livedata/livedata_connection.js | 7 +--- packages/livedata/server_convenience.js | 6 ++- packages/stream/stream_client.js | 48 ++++++++++++++++++------ 4 files changed, 49 insertions(+), 24 deletions(-) diff --git a/packages/livedata/client_convenience.js b/packages/livedata/client_convenience.js index 01da0011f5..ee435ac86e 100644 --- a/packages/livedata/client_convenience.js +++ b/packages/livedata/client_convenience.js @@ -1,14 +1,14 @@ (function () { // By default, try to connect back to the same endpoint as the page // was served from. - var ddp_endpoint = '/'; - if (typeof __meteor_runtime_config__ !== "undefined" && - __meteor_runtime_config__.DEFAULT_DDP_ENDPOINT) - ddp_endpoint = __meteor_runtime_config__.DEFAULT_DDP_ENDPOINT; + var ddpUrl = '/'; + if (typeof __meteor_runtime_config__ !== "undefined") { + if (__meteor_runtime_config__.DDP_DEFAULT_CONNECTION_URL) + ddpUrl = __meteor_runtime_config__.DDP_DEFAULT_CONNECTION_URL; + } _.extend(Meteor, { - default_connection: Meteor.connect(ddp_endpoint, - true /* restart_on_update */), + default_connection: Meteor.connect(ddpUrl, true /* restart_on_update */), refresh: function (notification) { } diff --git a/packages/livedata/livedata_connection.js b/packages/livedata/livedata_connection.js index c4b55a8940..13d416674e 100644 --- a/packages/livedata/livedata_connection.js +++ b/packages/livedata/livedata_connection.js @@ -9,8 +9,8 @@ if (Meteor.isServer) { // we can't do recursive Meteor.autosubscribe(). var captureSubs = null; -// @param url {String|Object} URL to Meteor app or sockjs endpoint (deprecated), -// or an object as a test hook (see code) +// @param url {String|Object} URL to Meteor app, +// or an object as a test hook (see code) // Options: // reloadOnUpdate: should we try to reload when the server says // there's new code available? @@ -30,9 +30,6 @@ Meteor._LivedataConnection = function (url, options) { // as a test hook, allow passing a stream instead of a url. if (typeof url === "object") { self._stream = url; - // if we have two test streams, auto reload stuff will break because - // the url is used as a key for the migration data. - url = "/debug"; } else { self._stream = new Meteor._Stream(url); } diff --git a/packages/livedata/server_convenience.js b/packages/livedata/server_convenience.js index 547181ab66..0ade2e9afa 100644 --- a/packages/livedata/server_convenience.js +++ b/packages/livedata/server_convenience.js @@ -1,5 +1,7 @@ -if (process.env.DEFAULT_DDP_ENDPOINT) - __meteor_runtime_config__.DEFAULT_DDP_ENDPOINT = process.env.DEFAULT_DDP_ENDPOINT; +if (process.env.DDP_DEFAULT_CONNECTION_URL) { + __meteor_runtime_config__.DDP_DEFAULT_CONNECTION_URL = + process.env.DDP_DEFAULT_CONNECTION_URL; +} _.extend(Meteor, { diff --git a/packages/stream/stream_client.js b/packages/stream/stream_client.js index 43aa9b57d7..e8c2880d26 100644 --- a/packages/stream/stream_client.js +++ b/packages/stream/stream_client.js @@ -3,7 +3,7 @@ Meteor._Stream = function (url) { var self = this; - self.url = Meteor._Stream._toSockjsUrl(url); + self.rawUrl = url; self.socket = null; self.event_callbacks = {}; // name -> [callback] self.server_id = null; @@ -63,9 +63,12 @@ Meteor._Stream = function (url) { }; _.extend(Meteor._Stream, { - // @param url {String} URL to Meteor app, or to sockjs endpoint (deprecated) + // @param url {String} URL to Meteor app, eg: + // "/" or "madewith.meteor.com" or "https://foo.meteor.com" + // or "ddp+sockjs://ddp--****-foo.meteor.com/sockjs" // @returns {String} URL to the sockjs endpoint, e.g. // "http://subdomain.meteor.com/sockjs" or "/sockjs" + // or "https://ddp--1234-foo.meteor.com/sockjs" _toSockjsUrl: function(url) { // XXX from Underscore.String (http://epeli.github.com/underscore.string/) var startsWith = function(str, starts) { @@ -77,14 +80,32 @@ _.extend(Meteor._Stream, { str.substring(str.length - ends.length) === ends; }; + var ddpUrlMatch = url.match(/^ddp(i?)\+sockjs:\/\//); + if (ddpUrlMatch) { + // Remove scheme and split off the host. + var urlAfterDDP = url.substr(ddpUrlMatch[0].length); + var newScheme = ddpUrlMatch[1] === 'i' ? 'http' : 'https'; + var slashPos = urlAfterDDP.indexOf('/'); + var host = + slashPos === -1 ? urlAfterDDP : urlAfterDDP.substr(0, slashPos); + var rest = slashPos === -1 ? '' : urlAfterDDP.substr(slashPos); + + // In the host (ONLY!), change '*' characters into random digits. This + // allows different stream connections to connect to different hostnames + // and avoid browser per-hostname connection limits. + host = host.replace(/\*/g, function () { + return Math.floor(Math.random()*10); + }); + + return newScheme + '://' + host + rest; + } + // Prefix FQDNs but not relative URLs if (url.indexOf("://") === -1 && !startsWith(url, "/")) { url = "http://" + url; } - if (endsWith(url, "/sockjs")) - return url; - else if (endsWith(url, "/")) + if (endsWith(url, "/")) return url + "sockjs"; else return url + "/sockjs"; @@ -311,12 +332,17 @@ _.extend(Meteor._Stream.prototype, { var self = this; self._cleanup_socket(); // cleanup the old socket, if there was one. - self.socket = new SockJS(self.url, undefined, { - debug: false, protocols_whitelist: [ - // only allow polling protocols. no websockets or streaming. - // streaming makes safari spin, and websockets hurt chrome. - 'xdr-polling', 'xhr-polling', 'iframe-xhr-polling', 'jsonp-polling' - ]}); + // Convert raw URL to SockJS URL each time we open a connection, so that we + // can connect to random hostnames and get around browser per-host + // connection limits. + self.socket = new SockJS( + Meteor._Stream._toSockjsUrl(self.rawUrl), + undefined, { + debug: false, protocols_whitelist: [ + // only allow polling protocols. no websockets or streaming. + // streaming makes safari spin, and websockets hurt chrome. + 'xdr-polling', 'xhr-polling', 'iframe-xhr-polling', 'jsonp-polling' + ]}); self.socket.onmessage = function (data) { // first message we get when we're connecting goes to _connected, // which connects us. All subsequent messages (while connected) go to