123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622 |
- // Copyright 2006 The Closure Library Authors. All Rights Reserved.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS-IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- /**
- * @fileoverview Definition of the BrowserTestChannel class. A
- * BrowserTestChannel is used during the first part of channel negotiation
- * with the server to create the channel. It helps us determine whether we're
- * behind a buffering proxy. It also runs the logic to see if the channel
- * has been blocked by a network administrator. This class is part of the
- * BrowserChannel implementation and is not for use by normal application code.
- *
- */
- goog.provide('goog.net.BrowserTestChannel');
- goog.require('goog.json.NativeJsonProcessor');
- goog.require('goog.net.ChannelRequest');
- goog.require('goog.net.ChannelRequest.Error');
- goog.require('goog.net.tmpnetwork');
- goog.require('goog.string.Parser');
- /**
- * Encapsulates the logic for a single BrowserTestChannel.
- *
- * @constructor
- * @param {goog.net.BrowserChannel} channel The BrowserChannel that owns this
- * test channel.
- * @param {goog.net.ChannelDebug} channelDebug A ChannelDebug to use for
- * logging.
- * @final
- */
- goog.net.BrowserTestChannel = function(channel, channelDebug) {
- /**
- * The BrowserChannel that owns this test channel
- * @type {goog.net.BrowserChannel}
- * @private
- */
- this.channel_ = channel;
- /**
- * The channel debug to use for logging
- * @type {goog.net.ChannelDebug}
- * @private
- */
- this.channelDebug_ = channelDebug;
- /**
- * Parser for a response payload. The parser should return an array.
- * @type {goog.string.Parser}
- * @private
- */
- this.parser_ = new goog.json.NativeJsonProcessor();
- };
- /**
- * Extra HTTP headers to add to all the requests sent to the server.
- * @type {Object}
- * @private
- */
- goog.net.BrowserTestChannel.prototype.extraHeaders_ = null;
- /**
- * The test request.
- * @type {goog.net.ChannelRequest}
- * @private
- */
- goog.net.BrowserTestChannel.prototype.request_ = null;
- /**
- * Whether we have received the first result as an intermediate result. This
- * helps us determine whether we're behind a buffering proxy.
- * @type {boolean}
- * @private
- */
- goog.net.BrowserTestChannel.prototype.receivedIntermediateResult_ = false;
- /**
- * The time when the test request was started. We use timing in IE as
- * a heuristic for whether we're behind a buffering proxy.
- * @type {?number}
- * @private
- */
- goog.net.BrowserTestChannel.prototype.startTime_ = null;
- /**
- * The time for of the first result part. We use timing in IE as a
- * heuristic for whether we're behind a buffering proxy.
- * @type {?number}
- * @private
- */
- goog.net.BrowserTestChannel.prototype.firstTime_ = null;
- /**
- * The time for of the last result part. We use timing in IE as a
- * heuristic for whether we're behind a buffering proxy.
- * @type {?number}
- * @private
- */
- goog.net.BrowserTestChannel.prototype.lastTime_ = null;
- /**
- * The relative path for test requests.
- * @type {?string}
- * @private
- */
- goog.net.BrowserTestChannel.prototype.path_ = null;
- /**
- * The state of the state machine for this object.
- *
- * @type {?number}
- * @private
- */
- goog.net.BrowserTestChannel.prototype.state_ = null;
- /**
- * The last status code received.
- * @type {number}
- * @private
- */
- goog.net.BrowserTestChannel.prototype.lastStatusCode_ = -1;
- /**
- * A subdomain prefix for using a subdomain in IE for the backchannel
- * requests.
- * @type {?string}
- * @private
- */
- goog.net.BrowserTestChannel.prototype.hostPrefix_ = null;
- /**
- * A subdomain prefix for testing whether the channel was disabled by
- * a network administrator;
- * @type {?string}
- * @private
- */
- goog.net.BrowserTestChannel.prototype.blockedPrefix_ = null;
- /**
- * Enum type for the browser test channel state machine
- * @enum {number}
- * @private
- */
- goog.net.BrowserTestChannel.State_ = {
- /**
- * The state for the BrowserTestChannel state machine where we making the
- * initial call to get the server configured parameters.
- */
- INIT: 0,
- /**
- * The state for the BrowserTestChannel state machine where we're checking to
- * see if the channel has been blocked.
- */
- CHECKING_BLOCKED: 1,
- /**
- * The state for the BrowserTestChannel state machine where we're checking to
- * se if we're behind a buffering proxy.
- */
- CONNECTION_TESTING: 2
- };
- /**
- * Time in MS for waiting for the request to see if the channel is blocked.
- * If the response takes longer than this many ms, we assume the request has
- * failed.
- * @type {number}
- * @private
- */
- goog.net.BrowserTestChannel.BLOCKED_TIMEOUT_ = 5000;
- /**
- * Number of attempts to try to see if the check to see if we're blocked
- * succeeds. Sometimes the request can fail because of flaky network conditions
- * and checking multiple times reduces false positives.
- * @type {number}
- * @private
- */
- goog.net.BrowserTestChannel.BLOCKED_RETRIES_ = 3;
- /**
- * Time in ms between retries of the blocked request
- * @type {number}
- * @private
- */
- goog.net.BrowserTestChannel.BLOCKED_PAUSE_BETWEEN_RETRIES_ = 2000;
- /**
- * Time between chunks in the test connection that indicates that we
- * are not behind a buffering proxy. This value should be less than or
- * equals to the time between chunks sent from the server.
- * @type {number}
- * @private
- */
- goog.net.BrowserTestChannel.MIN_TIME_EXPECTED_BETWEEN_DATA_ = 500;
- /**
- * Sets extra HTTP headers to add to all the requests sent to the server.
- *
- * @param {Object} extraHeaders The HTTP headers.
- */
- goog.net.BrowserTestChannel.prototype.setExtraHeaders = function(extraHeaders) {
- this.extraHeaders_ = extraHeaders;
- };
- /**
- * Sets a new parser for the response payload.
- * @param {!goog.string.Parser} parser Parser.
- */
- goog.net.BrowserTestChannel.prototype.setParser = function(parser) {
- this.parser_ = parser;
- };
- /**
- * Starts the test channel. This initiates connections to the server.
- *
- * @param {string} path The relative uri for the test connection.
- */
- goog.net.BrowserTestChannel.prototype.connect = function(path) {
- this.path_ = path;
- var sendDataUri = this.channel_.getForwardChannelUri(this.path_);
- goog.net.BrowserChannel.notifyStatEvent(
- goog.net.BrowserChannel.Stat.TEST_STAGE_ONE_START);
- this.startTime_ = goog.now();
- // If the channel already has the result of the first test, then skip it.
- var firstTestResults = this.channel_.getFirstTestResults();
- if (goog.isDefAndNotNull(firstTestResults)) {
- this.hostPrefix_ = this.channel_.correctHostPrefix(firstTestResults[0]);
- this.blockedPrefix_ = firstTestResults[1];
- if (this.blockedPrefix_) {
- this.state_ = goog.net.BrowserTestChannel.State_.CHECKING_BLOCKED;
- this.checkBlocked_();
- } else {
- this.state_ = goog.net.BrowserTestChannel.State_.CONNECTION_TESTING;
- this.connectStage2_();
- }
- return;
- }
- // the first request returns server specific parameters
- sendDataUri.setParameterValues('MODE', 'init');
- this.request_ =
- goog.net.BrowserChannel.createChannelRequest(this, this.channelDebug_);
- this.request_.setExtraHeaders(this.extraHeaders_);
- this.request_.xmlHttpGet(
- sendDataUri, false /* decodeChunks */, null /* hostPrefix */,
- true /* opt_noClose */);
- this.state_ = goog.net.BrowserTestChannel.State_.INIT;
- };
- /**
- * Checks to see whether the channel is blocked. This is for implementing the
- * feature that allows network administrators to block Gmail Chat. The
- * strategy to determine if we're blocked is to try to load an image off a
- * special subdomain that network administrators will block access to if they
- * are trying to block chat. For Gmail Chat, the subdomain is
- * chatenabled.mail.google.com.
- * @private
- */
- goog.net.BrowserTestChannel.prototype.checkBlocked_ = function() {
- var uri = this.channel_.createDataUri(
- this.blockedPrefix_, '/mail/images/cleardot.gif');
- uri.makeUnique();
- goog.net.tmpnetwork.testLoadImageWithRetries(
- uri.toString(), goog.net.BrowserTestChannel.BLOCKED_TIMEOUT_,
- goog.bind(this.checkBlockedCallback_, this),
- goog.net.BrowserTestChannel.BLOCKED_RETRIES_,
- goog.net.BrowserTestChannel.BLOCKED_PAUSE_BETWEEN_RETRIES_);
- this.notifyServerReachabilityEvent(
- goog.net.BrowserChannel.ServerReachability.REQUEST_MADE);
- };
- /**
- * Callback for testLoadImageWithRetries to check if browser channel is
- * blocked.
- * @param {boolean} succeeded Whether the request succeeded.
- * @private
- */
- goog.net.BrowserTestChannel.prototype.checkBlockedCallback_ = function(
- succeeded) {
- if (succeeded) {
- this.state_ = goog.net.BrowserTestChannel.State_.CONNECTION_TESTING;
- this.connectStage2_();
- } else {
- goog.net.BrowserChannel.notifyStatEvent(
- goog.net.BrowserChannel.Stat.CHANNEL_BLOCKED);
- this.channel_.testConnectionBlocked(this);
- }
- // We don't dispatch a REQUEST_FAILED server reachability event when the
- // block request fails, as such a failure is not a good signal that the
- // server has actually become unreachable.
- if (succeeded) {
- this.notifyServerReachabilityEvent(
- goog.net.BrowserChannel.ServerReachability.REQUEST_SUCCEEDED);
- }
- };
- /**
- * Begins the second stage of the test channel where we test to see if we're
- * behind a buffering proxy. The server sends back a multi-chunked response
- * with the first chunk containing the content '1' and then two seconds later
- * sending the second chunk containing the content '2'. Depending on how we
- * receive the content, we can tell if we're behind a buffering proxy.
- * @private
- * @suppress {missingRequire} goog.net.BrowserChannel
- */
- goog.net.BrowserTestChannel.prototype.connectStage2_ = function() {
- this.channelDebug_.debug('TestConnection: starting stage 2');
- // If the second test results are available, skip its execution.
- var secondTestResults = this.channel_.getSecondTestResults();
- if (goog.isDefAndNotNull(secondTestResults)) {
- this.channelDebug_.debug(
- 'TestConnection: skipping stage 2, precomputed result is ' +
- secondTestResults ?
- 'Buffered' :
- 'Unbuffered');
- goog.net.BrowserChannel.notifyStatEvent(
- goog.net.BrowserChannel.Stat.TEST_STAGE_TWO_START);
- if (secondTestResults) { // Buffered/Proxy connection
- goog.net.BrowserChannel.notifyStatEvent(
- goog.net.BrowserChannel.Stat.PROXY);
- this.channel_.testConnectionFinished(this, false);
- } else { // Unbuffered/NoProxy connection
- goog.net.BrowserChannel.notifyStatEvent(
- goog.net.BrowserChannel.Stat.NOPROXY);
- this.channel_.testConnectionFinished(this, true);
- }
- return; // Skip the test
- }
- /** @private @suppress {missingRequire} Circular dep. */
- this.request_ =
- goog.net.BrowserChannel.createChannelRequest(this, this.channelDebug_);
- this.request_.setExtraHeaders(this.extraHeaders_);
- var recvDataUri = this.channel_.getBackChannelUri(
- this.hostPrefix_,
- /** @type {string} */ (this.path_));
- goog.net.BrowserChannel.notifyStatEvent(
- goog.net.BrowserChannel.Stat.TEST_STAGE_TWO_START);
- if (!goog.net.ChannelRequest.supportsXhrStreaming()) {
- recvDataUri.setParameterValues('TYPE', 'html');
- this.request_.tridentGet(recvDataUri, Boolean(this.hostPrefix_));
- } else {
- recvDataUri.setParameterValues('TYPE', 'xmlhttp');
- this.request_.xmlHttpGet(
- recvDataUri, false /** decodeChunks */, this.hostPrefix_,
- false /** opt_noClose */);
- }
- };
- /**
- * Factory method for XhrIo objects.
- * @param {?string} hostPrefix The host prefix, if we need an XhrIo object
- * capable of calling a secondary domain.
- * @return {!goog.net.XhrIo} New XhrIo object.
- */
- goog.net.BrowserTestChannel.prototype.createXhrIo = function(hostPrefix) {
- return this.channel_.createXhrIo(hostPrefix);
- };
- /**
- * Aborts the test channel.
- */
- goog.net.BrowserTestChannel.prototype.abort = function() {
- if (this.request_) {
- this.request_.cancel();
- this.request_ = null;
- }
- this.lastStatusCode_ = -1;
- };
- /**
- * Returns whether the test channel is closed. The ChannelRequest object expects
- * this method to be implemented on its handler.
- *
- * @return {boolean} Whether the channel is closed.
- */
- goog.net.BrowserTestChannel.prototype.isClosed = function() {
- return false;
- };
- /**
- * Callback from ChannelRequest for when new data is received
- *
- * @param {goog.net.ChannelRequest} req The request object.
- * @param {string} responseText The text of the response.
- */
- goog.net.BrowserTestChannel.prototype.onRequestData = function(
- req, responseText) {
- this.lastStatusCode_ = req.getLastStatusCode();
- if (this.state_ == goog.net.BrowserTestChannel.State_.INIT) {
- this.channelDebug_.debug('TestConnection: Got data for stage 1');
- if (!responseText) {
- this.channelDebug_.debug('TestConnection: Null responseText');
- // The server should always send text; something is wrong here
- this.channel_.testConnectionFailure(
- this, goog.net.ChannelRequest.Error.BAD_DATA);
- return;
- }
- try {
- var respArray = this.parser_.parse(responseText);
- } catch (e) {
- this.channelDebug_.dumpException(e);
- this.channel_.testConnectionFailure(
- this, goog.net.ChannelRequest.Error.BAD_DATA);
- return;
- }
- this.hostPrefix_ = this.channel_.correctHostPrefix(respArray[0]);
- this.blockedPrefix_ = respArray[1];
- } else if (
- this.state_ == goog.net.BrowserTestChannel.State_.CONNECTION_TESTING) {
- if (this.receivedIntermediateResult_) {
- goog.net.BrowserChannel.notifyStatEvent(
- goog.net.BrowserChannel.Stat.TEST_STAGE_TWO_DATA_TWO);
- this.lastTime_ = goog.now();
- } else {
- // '11111' is used instead of '1' to prevent a small amount of buffering
- // by Safari.
- if (responseText == '11111') {
- goog.net.BrowserChannel.notifyStatEvent(
- goog.net.BrowserChannel.Stat.TEST_STAGE_TWO_DATA_ONE);
- this.receivedIntermediateResult_ = true;
- this.firstTime_ = goog.now();
- if (this.checkForEarlyNonBuffered_()) {
- // If early chunk detection is on, and we passed the tests,
- // assume HTTP_OK, cancel the test and turn on noproxy mode.
- this.lastStatusCode_ = 200;
- this.request_.cancel();
- this.channelDebug_.debug(
- 'Test connection succeeded; using streaming connection');
- goog.net.BrowserChannel.notifyStatEvent(
- goog.net.BrowserChannel.Stat.NOPROXY);
- this.channel_.testConnectionFinished(this, true);
- }
- } else {
- goog.net.BrowserChannel.notifyStatEvent(
- goog.net.BrowserChannel.Stat.TEST_STAGE_TWO_DATA_BOTH);
- this.firstTime_ = this.lastTime_ = goog.now();
- this.receivedIntermediateResult_ = false;
- }
- }
- }
- };
- /**
- * Callback from ChannelRequest that indicates a request has completed.
- *
- * @param {goog.net.ChannelRequest} req The request object.
- * @suppress {missingRequire} Cannot depend on goog.net.BrowserChannel because
- * it creates a circular dependency.
- */
- goog.net.BrowserTestChannel.prototype.onRequestComplete = function(req) {
- this.lastStatusCode_ = this.request_.getLastStatusCode();
- if (!this.request_.getSuccess()) {
- this.channelDebug_.debug(
- 'TestConnection: request failed, in state ' + this.state_);
- if (this.state_ == goog.net.BrowserTestChannel.State_.INIT) {
- goog.net.BrowserChannel.notifyStatEvent(
- goog.net.BrowserChannel.Stat.TEST_STAGE_ONE_FAILED);
- } else if (
- this.state_ == goog.net.BrowserTestChannel.State_.CONNECTION_TESTING) {
- goog.net.BrowserChannel.notifyStatEvent(
- goog.net.BrowserChannel.Stat.TEST_STAGE_TWO_FAILED);
- }
- this.channel_.testConnectionFailure(
- this,
- /** @type {goog.net.ChannelRequest.Error} */
- (this.request_.getLastError()));
- return;
- }
- if (this.state_ == goog.net.BrowserTestChannel.State_.INIT) {
- this.channelDebug_.debug(
- 'TestConnection: request complete for initial check');
- if (this.blockedPrefix_) {
- this.state_ = goog.net.BrowserTestChannel.State_.CHECKING_BLOCKED;
- this.checkBlocked_();
- } else {
- this.state_ = goog.net.BrowserTestChannel.State_.CONNECTION_TESTING;
- this.connectStage2_();
- }
- } else if (
- this.state_ == goog.net.BrowserTestChannel.State_.CONNECTION_TESTING) {
- this.channelDebug_.debug('TestConnection: request complete for stage 2');
- var goodConn = false;
- if (!goog.net.ChannelRequest.supportsXhrStreaming()) {
- // we always get Trident responses in separate calls to
- // onRequestData, so we have to check the time they came
- var ms = this.lastTime_ - this.firstTime_;
- if (ms < 200) {
- // TODO: need to empirically verify that this number is OK
- // for slow computers
- goodConn = false;
- } else {
- goodConn = true;
- }
- } else {
- goodConn = this.receivedIntermediateResult_;
- }
- if (goodConn) {
- this.channelDebug_.debug(
- 'Test connection succeeded; using streaming connection');
- goog.net.BrowserChannel.notifyStatEvent(
- goog.net.BrowserChannel.Stat.NOPROXY);
- this.channel_.testConnectionFinished(this, true);
- } else {
- this.channelDebug_.debug('Test connection failed; not using streaming');
- /** @suppress {missingRequire} Circular dep */
- goog.net.BrowserChannel.notifyStatEvent(
- goog.net.BrowserChannel.Stat.PROXY);
- this.channel_.testConnectionFinished(this, false);
- }
- }
- };
- /**
- * Returns the last status code received for a request.
- * @return {number} The last status code received for a request.
- */
- goog.net.BrowserTestChannel.prototype.getLastStatusCode = function() {
- return this.lastStatusCode_;
- };
- /**
- * @return {boolean} Whether we should be using secondary domains when the
- * server instructs us to do so.
- */
- goog.net.BrowserTestChannel.prototype.shouldUseSecondaryDomains = function() {
- return this.channel_.shouldUseSecondaryDomains();
- };
- /**
- * Gets whether this channel is currently active. This is used to determine the
- * length of time to wait before retrying.
- *
- * @param {goog.net.BrowserChannel} browserChannel The browser channel.
- * @return {boolean} Whether the channel is currently active.
- */
- goog.net.BrowserTestChannel.prototype.isActive = function(browserChannel) {
- return this.channel_.isActive();
- };
- /**
- * @return {boolean} True if test stage 2 detected a non-buffered
- * channel early and early no buffering detection is enabled.
- * @private
- */
- goog.net.BrowserTestChannel.prototype.checkForEarlyNonBuffered_ = function() {
- var ms = this.firstTime_ - this.startTime_;
- // we always get Trident responses in separate calls to
- // onRequestData, so we have to check the time that the first came in
- // and verify that the data arrived before the second portion could
- // have been sent. For all other browser's we skip the timing test.
- return goog.net.ChannelRequest.supportsXhrStreaming() ||
- ms < goog.net.BrowserTestChannel.MIN_TIME_EXPECTED_BETWEEN_DATA_;
- };
- /**
- * Notifies the channel of a fine grained network event.
- * @param {goog.net.BrowserChannel.ServerReachability} reachabilityType The
- * reachability event type.
- */
- goog.net.BrowserTestChannel.prototype.notifyServerReachabilityEvent = function(
- reachabilityType) {
- this.channel_.notifyServerReachabilityEvent(reachabilityType);
- };
|