diff --git a/closure/goog/deps.js b/closure/goog/deps.js index 90cdbaa2db..7c003d5b1b 100644 --- a/closure/goog/deps.js +++ b/closure/goog/deps.js @@ -666,8 +666,8 @@ goog.addDependency('labs/net/webchannel/netutils.js', ['goog.labs.net.webChannel goog.addDependency('labs/net/webchannel/requeststats.js', ['goog.labs.net.webChannel.requestStats', 'goog.labs.net.webChannel.requestStats.Event', 'goog.labs.net.webChannel.requestStats.ServerReachability', 'goog.labs.net.webChannel.requestStats.ServerReachabilityEvent', 'goog.labs.net.webChannel.requestStats.Stat', 'goog.labs.net.webChannel.requestStats.StatEvent', 'goog.labs.net.webChannel.requestStats.TimingEvent'], ['goog.events.Event', 'goog.events.EventTarget'], {}); goog.addDependency('labs/net/webchannel/webchannelbase.js', ['goog.labs.net.webChannel.WebChannelBase'], ['goog.Uri', 'goog.array', 'goog.asserts', 'goog.async.run', 'goog.debug.TextFormatter', 'goog.json', 'goog.labs.net.webChannel.BaseTestChannel', 'goog.labs.net.webChannel.Channel', 'goog.labs.net.webChannel.ChannelRequest', 'goog.labs.net.webChannel.ConnectionState', 'goog.labs.net.webChannel.ForwardChannelRequestPool', 'goog.labs.net.webChannel.WebChannelDebug', 'goog.labs.net.webChannel.Wire', 'goog.labs.net.webChannel.WireV8', 'goog.labs.net.webChannel.netUtils', 'goog.labs.net.webChannel.requestStats', 'goog.log', 'goog.net.WebChannel', 'goog.net.XhrIo', 'goog.net.XmlHttpFactory', 'goog.net.rpc.HttpCors', 'goog.object', 'goog.string', 'goog.structs', 'goog.structs.CircularBuffer'], {}); goog.addDependency('labs/net/webchannel/webchannelbase_test.js', ['goog.labs.net.webChannel.webChannelBaseTest'], ['goog.Timer', 'goog.array', 'goog.dom', 'goog.functions', 'goog.json', 'goog.labs.net.webChannel.ChannelRequest', 'goog.labs.net.webChannel.ForwardChannelRequestPool', 'goog.labs.net.webChannel.WebChannelBase', 'goog.labs.net.webChannel.WebChannelBaseTransport', 'goog.labs.net.webChannel.WebChannelDebug', 'goog.labs.net.webChannel.Wire', 'goog.labs.net.webChannel.netUtils', 'goog.labs.net.webChannel.requestStats', 'goog.labs.net.webChannel.requestStats.Stat', 'goog.structs.Map', 'goog.testing.MockClock', 'goog.testing.PropertyReplacer', 'goog.testing.asserts', 'goog.testing.jsunit'], {}); -goog.addDependency('labs/net/webchannel/webchannelbasetransport.js', ['goog.labs.net.webChannel.WebChannelBaseTransport'], ['goog.asserts', 'goog.events.EventTarget', 'goog.json', 'goog.labs.net.webChannel.ChannelRequest', 'goog.labs.net.webChannel.WebChannelBase', 'goog.log', 'goog.net.WebChannel', 'goog.net.WebChannelTransport', 'goog.object', 'goog.string', 'goog.string.path'], {}); -goog.addDependency('labs/net/webchannel/webchannelbasetransport_test.js', ['goog.labs.net.webChannel.webChannelBaseTransportTest'], ['goog.events', 'goog.functions', 'goog.json', 'goog.labs.net.webChannel.ChannelRequest', 'goog.labs.net.webChannel.WebChannelBase', 'goog.labs.net.webChannel.WebChannelBaseTransport', 'goog.net.WebChannel', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit'], {}); +goog.addDependency('labs/net/webchannel/webchannelbasetransport.js', ['goog.labs.net.webChannel.WebChannelBaseTransport'], ['goog.asserts', 'goog.events.EventTarget', 'goog.json', 'goog.labs.net.webChannel.ChannelRequest', 'goog.labs.net.webChannel.WebChannelBase', 'goog.labs.net.webChannel.Wire', 'goog.log', 'goog.net.WebChannel', 'goog.net.WebChannelTransport', 'goog.object', 'goog.string', 'goog.string.path'], {}); +goog.addDependency('labs/net/webchannel/webchannelbasetransport_test.js', ['goog.labs.net.webChannel.webChannelBaseTransportTest'], ['goog.events', 'goog.functions', 'goog.json', 'goog.labs.net.webChannel.ChannelRequest', 'goog.labs.net.webChannel.WebChannelBase', 'goog.labs.net.webChannel.WebChannelBaseTransport', 'goog.labs.net.webChannel.Wire', 'goog.net.WebChannel', 'goog.testing.PropertyReplacer', 'goog.testing.jsunit'], {}); goog.addDependency('labs/net/webchannel/webchanneldebug.js', ['goog.labs.net.webChannel.WebChannelDebug'], ['goog.json', 'goog.log'], {}); goog.addDependency('labs/net/webchannel/wire.js', ['goog.labs.net.webChannel.Wire'], [], {}); goog.addDependency('labs/net/webchannel/wirev8.js', ['goog.labs.net.webChannel.WireV8'], ['goog.asserts', 'goog.json', 'goog.json.NativeJsonProcessor', 'goog.labs.net.webChannel.Wire', 'goog.structs'], {}); diff --git a/closure/goog/labs/net/webchannel.js b/closure/goog/labs/net/webchannel.js index c5b853ef0d..27d03c5642 100644 --- a/closure/goog/labs/net/webchannel.js +++ b/closure/goog/labs/net/webchannel.js @@ -131,9 +131,9 @@ goog.net.WebChannel.FailureRecovery = function() {}; * testUrl: the test URL for detecting connectivity during the initial * handshake. This parameter defaults to "//test". * - * sendRawJson: whether to bypass v8 encoding of client-sent messages. Will be - * deprecated after v9 wire protocol is introduced. Only safe to set if the - * server is known to support this feature. + * sendRawJson: whether to bypass v8 encoding of client-sent messages. + * This defaults to false now due to legacy servers. New applications should + * always configure this option to true. * * httpSessionIdParam: the URL parameter name that contains the session id ( * for sticky routing of HTTP requests). When this param is specified, a server @@ -156,14 +156,16 @@ goog.net.WebChannel.FailureRecovery = function() {}; * This defaults to false. Long-polling may be necessary when a (MITM) proxy * is buffering data sent by the server. * - * fastHandshake: experimental feature to enable true 0-RTT message delivery, - * e.g. by leveraging QUIC 0-RTT (which requires GET to be used). This option - * defaults to false. When this option is enabled, backgroundChannelTest will - * be forced to true. Note it is allowed to send messages before Open event is - * received, after a channel has been connected. In order to enable 0-RTT, - * messages need be encoded as part of URL and therefore there needs be a size - * limit (e.g. 16KB) for messages that need be sent immediately - * as part of the handshake. + * fastHandshake: enable true 0-RTT message delivery, including + * leveraging QUIC 0-RTT (which requires GET to be used). This option + * defaults to false. Note it is allowed to send messages before Open event is + * received, after a channel has been opened. In order to enable 0-RTT, + * messages will be encoded as part of URL and therefore there needs be a size + * limit for those initial messages that are sent immediately as part of the + * GET handshake request. With sendRawJson=true, this limit is currently set + * to 4K chars and data beyond this limit will be buffered till the handshake + * (1-RTT) finishes. With sendRawJson=false, it's up to the application + * to limit the amount of data that is sent as part of the handshake. * * disableRedact: whether to disable logging redact. By default, redact is * enabled to remove any message payload or user-provided info diff --git a/closure/goog/labs/net/webchannel/webchannelbase.js b/closure/goog/labs/net/webchannel/webchannelbase.js index af1bda4bfc..3e4a6e3c26 100644 --- a/closure/goog/labs/net/webchannel/webchannelbase.js +++ b/closure/goog/labs/net/webchannel/webchannelbase.js @@ -554,6 +554,15 @@ WebChannelBase.ChannelType_ = { WebChannelBase.MAX_MAPS_PER_REQUEST_ = 1000; +/** + * The maximum number of utf-8 chars that can be sent in one GET to enable 0-RTT + * handshake. + * + * @const @private {number} + */ +WebChannelBase.MAX_CHARS_PER_GET_ = 4 * 1024; + + /** * A guess at a cutoff at which to no longer assume the backchannel is dead * when we are slow to receive data. Number in bytes. @@ -565,6 +574,14 @@ WebChannelBase.MAX_MAPS_PER_REQUEST_ = 1000; WebChannelBase.OUTSTANDING_DATA_BACKCHANNEL_RETRY_CUTOFF = 37500; +/** + * @return {number} The server version or 0 if undefined + */ +WebChannelBase.prototype.getServerVersion = function() { + return this.serverVersion_; +}; + + /** * @return {!ForwardChannelRequestPool} The forward channel request pool. */ @@ -1269,7 +1286,11 @@ WebChannelBase.prototype.open_ = function() { request.setExtraHeaders(extraHeaders); } - var requestText = this.dequeueOutgoingMaps_(request); + var requestText = this.dequeueOutgoingMaps_( + request, + this.fastHandshake_ ? this.getMaxNumMessagesForFastHandshake_() : + WebChannelBase.MAX_MAPS_PER_REQUEST_); + var uri = this.forwardChannelUri_.clone(); uri.setParameterValue('RID', rid); @@ -1308,6 +1329,37 @@ WebChannelBase.prototype.open_ = function() { }; +/** + * @return {number} The number of raw JSON messages to be encoded + * with the fast-handshake (GET) request, including zero. If messages are not + * encoded as raw JSON data, return WebChannelBase.MAX_MAPS_PER_REQUEST_ + * @private + */ +WebChannelBase.prototype.getMaxNumMessagesForFastHandshake_ = function() { + var total = 0; + for (var i = 0; i < this.outgoingMaps_.length; i++) { + var map = this.outgoingMaps_[i]; + var size = map.getRawDataSize(); + if (size === undefined) { + break; + } + total += size; + + if (total > WebChannelBase.MAX_CHARS_PER_GET_) { + return i; + } + + if (total === WebChannelBase.MAX_CHARS_PER_GET_ || + i === this.outgoingMaps_.length - 1) { + return i + 1; + } + } + + return WebChannelBase.MAX_MAPS_PER_REQUEST_; +}; + + + /** * Makes a forward channel request using XMLHTTP. * @param {!ChannelRequest=} opt_retryRequest A failed request to retry. @@ -1346,7 +1398,8 @@ WebChannelBase.prototype.makeForwardChannelRequest_ = function( if (opt_retryRequest) { this.requeuePendingMaps_(opt_retryRequest); } - requestText = this.dequeueOutgoingMaps_(request); + requestText = + this.dequeueOutgoingMaps_(request, WebChannelBase.MAX_MAPS_PER_REQUEST_); // Randomize from 50%-100% of the forward channel timeout to avoid // a big hit if servers happen to die at once. @@ -1378,14 +1431,15 @@ WebChannelBase.prototype.addAdditionalParams_ = function(uri) { /** * Returns the request text from the outgoing maps and resets it. - * @param {!ChannelRequest=} request The new request for sending the messages. + * @param {!ChannelRequest} request The new request for sending the messages. + * @param {number} maxNum The maximum number of messages to be encoded * @return {string} The encoded request text created from all the currently * queued outgoing maps. * @private */ -WebChannelBase.prototype.dequeueOutgoingMaps_ = function(request) { - var count = - Math.min(this.outgoingMaps_.length, WebChannelBase.MAX_MAPS_PER_REQUEST_); +WebChannelBase.prototype.dequeueOutgoingMaps_ = function(request, maxNum) { + var count = Math.min(this.outgoingMaps_.length, maxNum); + var badMapHandler = this.handler_ ? goog.bind(this.handler_.badMapError, this.handler_, this) : null; @@ -1969,6 +2023,10 @@ WebChannelBase.prototype.onInput_ = function(respArray, request) { } this.startBackchannelAfterHandshake_(request); + + if (this.outgoingMaps_.length > 0) { + this.ensureForwardChannel_(); + } } else if (nextArray[0] == 'stop' || nextArray[0] == 'close') { // treat close also as an abort this.signalError_(WebChannelBase.Error.STOP); diff --git a/closure/goog/labs/net/webchannel/webchannelbasetransport.js b/closure/goog/labs/net/webchannel/webchannelbasetransport.js index f3b85e815f..35049df360 100644 --- a/closure/goog/labs/net/webchannel/webchannelbasetransport.js +++ b/closure/goog/labs/net/webchannel/webchannelbasetransport.js @@ -29,6 +29,7 @@ goog.require('goog.events.EventTarget'); goog.require('goog.json'); goog.require('goog.labs.net.webChannel.ChannelRequest'); goog.require('goog.labs.net.webChannel.WebChannelBase'); +goog.require('goog.labs.net.webChannel.Wire'); goog.require('goog.log'); goog.require('goog.net.WebChannel'); goog.require('goog.net.WebChannelTransport'); @@ -58,6 +59,7 @@ goog.labs.net.webChannel.WebChannelBaseTransport = function() { goog.scope(function() { var WebChannelBaseTransport = goog.labs.net.webChannel.WebChannelBaseTransport; var WebChannelBase = goog.labs.net.webChannel.WebChannelBase; +var Wire = goog.labs.net.webChannel.Wire; /** @@ -275,11 +277,11 @@ WebChannelBaseTransport.Channel.prototype.send = function(message) { if (goog.isString(message)) { var rawJson = {}; - rawJson['__data__'] = message; + rawJson[Wire.RAW_DATA_KEY] = message; this.channel_.sendMap(rawJson); } else if (this.sendRawJson_) { var rawJson = {}; - rawJson['__data__'] = goog.json.serialize(message); + rawJson[Wire.RAW_DATA_KEY] = goog.json.serialize(message); this.channel_.sendMap(rawJson); } else { this.channel_.sendMap(message); diff --git a/closure/goog/labs/net/webchannel/webchannelbasetransport_test.js b/closure/goog/labs/net/webchannel/webchannelbasetransport_test.js index f6389112f8..4daa20fb47 100644 --- a/closure/goog/labs/net/webchannel/webchannelbasetransport_test.js +++ b/closure/goog/labs/net/webchannel/webchannelbasetransport_test.js @@ -27,6 +27,7 @@ goog.require('goog.json'); goog.require('goog.labs.net.webChannel.ChannelRequest'); goog.require('goog.labs.net.webChannel.WebChannelBase'); goog.require('goog.labs.net.webChannel.WebChannelBaseTransport'); +goog.require('goog.labs.net.webChannel.Wire'); goog.require('goog.net.WebChannel'); goog.require('goog.testing.PropertyReplacer'); goog.require('goog.testing.jsunit'); @@ -248,11 +249,53 @@ function testOpenWithCorsEnabled() { assertTrue(webChannel.channel_.supportsCrossDomainXhrs_); } -function testSendRawJson() { +function testSendRawJsonDefaultValue() { var channelMsg; stubs.set( goog.labs.net.webChannel.WebChannelBase.prototype, 'sendMap', - function(message) { channelMsg = message; }); + function(message) { + channelMsg = message; + }); + + var webChannelTransport = + new goog.labs.net.webChannel.WebChannelBaseTransport(); + webChannel = webChannelTransport.createWebChannel(channelUrl); + webChannel.open(); + + webChannel.send({foo: 'bar'}); + assertEquals('bar', channelMsg.foo); +} + +function testSendRawJsonUndefinedValue() { + var channelMsg; + stubs.set( + goog.labs.net.webChannel.WebChannelBase.prototype, 'sendMap', + function(message) { + channelMsg = message; + }); + + var webChannelTransport = + new goog.labs.net.webChannel.WebChannelBaseTransport(); + var options = {}; + webChannel = webChannelTransport.createWebChannel(channelUrl, options); + webChannel.open(); + + webChannel.send({foo: 'bar'}); + assertEquals('bar', channelMsg.foo); +} + +function testSendRawJsonExplicitTrueValue() { + var channelMsg; + stubs.set( + goog.labs.net.webChannel.WebChannelBase.prototype, 'sendMap', + function(message) { + channelMsg = message; + }); + stubs.set( + goog.labs.net.webChannel.WebChannelBase.prototype, 'getServerVersion', + function() { + return 12; + }); var webChannelTransport = new goog.labs.net.webChannel.WebChannelBaseTransport(); @@ -262,10 +305,34 @@ function testSendRawJson() { webChannel.send({foo: 'bar'}); - var receivedMsg = goog.json.parse(channelMsg['__data__']); + var receivedMsg = + goog.json.parse(channelMsg[goog.labs.net.webChannel.Wire.RAW_DATA_KEY]); assertEquals('bar', receivedMsg.foo); } +function testSendRawJsonExplicitFalseValue() { + var channelMsg; + stubs.set( + goog.labs.net.webChannel.WebChannelBase.prototype, 'sendMap', + function(message) { + channelMsg = message; + }); + stubs.set( + goog.labs.net.webChannel.WebChannelBase.prototype, 'getServerVersion', + function() { + return 12; + }); + + var webChannelTransport = + new goog.labs.net.webChannel.WebChannelBaseTransport(); + var options = {'sendRawJson': false}; + webChannel = webChannelTransport.createWebChannel(channelUrl, options); + webChannel.open(); + + webChannel.send({foo: 'bar'}); + assertEquals('bar', channelMsg.foo); +} + function testOpenThenCloseChannel() { var webChannelTransport = new goog.labs.net.webChannel.WebChannelBaseTransport(); diff --git a/closure/goog/labs/net/webchannel/wire.js b/closure/goog/labs/net/webchannel/wire.js index cdbcd06077..49ab8b6cd4 100644 --- a/closure/goog/labs/net/webchannel/wire.js +++ b/closure/goog/labs/net/webchannel/wire.js @@ -45,6 +45,13 @@ var Wire = goog.labs.net.webChannel.Wire; Wire.LATEST_CHANNEL_VERSION = 8; +/** + * The JSON field key for the raw data wrapper object. + * @type {string} + */ +Wire.RAW_DATA_KEY = '__data__'; + + /** * Simple container class for a (mapId, map) pair. @@ -73,4 +80,20 @@ Wire.QueuedMap = function(mapId, map, opt_context) { */ this.context = opt_context || null; }; + + +/** + * @return {number|undefined} the size of the raw JSON message or + * undefined if the message is not encoded as a raw JSON message + */ +Wire.QueuedMap.prototype.getRawDataSize = function() { + if (Wire.RAW_DATA_KEY in this.map) { + var data = this.map[Wire.RAW_DATA_KEY]; + if (goog.isString(data)) { + return data.length; + } + } + + return undefined; +}; }); // goog.scope