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).
This commit is contained in:
David Glasser
2012-12-05 19:15:51 -08:00
parent 8916b1cd02
commit 7289950b54
4 changed files with 49 additions and 24 deletions

View File

@@ -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) {
}

View File

@@ -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);
}

View File

@@ -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, {

View File

@@ -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