123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533 |
- // 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 Base TestChannel implementation.
- *
- */
- goog.provide('goog.labs.net.webChannel.BaseTestChannel');
- goog.require('goog.labs.net.webChannel.Channel');
- goog.require('goog.labs.net.webChannel.ChannelRequest');
- goog.require('goog.labs.net.webChannel.WebChannelDebug');
- goog.require('goog.labs.net.webChannel.requestStats');
- goog.require('goog.labs.net.webChannel.requestStats.Stat');
- goog.require('goog.net.WebChannel');
- /**
- * A TestChannel 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.
- *
- * @constructor
- * @struct
- * @param {!goog.labs.net.webChannel.Channel} channel The channel
- * that owns this test channel.
- * @param {!goog.labs.net.webChannel.WebChannelDebug} channelDebug A
- * WebChannelDebug instance to use for logging.
- * @implements {goog.labs.net.webChannel.Channel}
- */
- goog.labs.net.webChannel.BaseTestChannel = function(channel, channelDebug) {
- /**
- * The channel that owns this test channel
- * @private {!goog.labs.net.webChannel.Channel}
- */
- this.channel_ = channel;
- /**
- * The channel debug to use for logging
- * @private {!goog.labs.net.webChannel.WebChannelDebug}
- */
- this.channelDebug_ = channelDebug;
- /**
- * Extra HTTP headers to add to all the requests sent to the server.
- * @private {Object}
- */
- this.extraHeaders_ = null;
- /**
- * The test request.
- * @private {goog.labs.net.webChannel.ChannelRequest}
- */
- this.request_ = null;
- /**
- * Whether we have received the first result as an intermediate result. This
- * helps us determine whether we're behind a buffering proxy.
- * @private {boolean}
- */
- this.receivedIntermediateResult_ = false;
- /**
- * The relative path for test requests.
- * @private {?string}
- */
- this.path_ = null;
- /**
- * The last status code received.
- * @private {number}
- */
- this.lastStatusCode_ = -1;
- /**
- * A subdomain prefix for using a subdomain in IE for the backchannel
- * requests.
- * @private {?string}
- */
- this.hostPrefix_ = null;
- /**
- * The effective client protocol as indicated by the initial handshake
- * response via the x-client-wire-protocol header.
- *
- * @private {?string}
- */
- this.clientProtocol_ = null;
- };
- goog.scope(function() {
- var WebChannel = goog.net.WebChannel;
- var BaseTestChannel = goog.labs.net.webChannel.BaseTestChannel;
- var WebChannelDebug = goog.labs.net.webChannel.WebChannelDebug;
- var ChannelRequest = goog.labs.net.webChannel.ChannelRequest;
- var requestStats = goog.labs.net.webChannel.requestStats;
- var Channel = goog.labs.net.webChannel.Channel;
- /**
- * Enum type for the test channel state machine
- * @enum {number}
- * @private
- */
- BaseTestChannel.State_ = {
- /**
- * The state for the TestChannel state machine where we making the
- * initial call to get the server configured parameters.
- */
- INIT: 0,
- /**
- * The state for the TestChannel state machine where we're checking to
- * se if we're behind a buffering proxy.
- */
- CONNECTION_TESTING: 1
- };
- /**
- * The state of the state machine for this object.
- *
- * @private {?BaseTestChannel.State_}
- */
- BaseTestChannel.prototype.state_ = null;
- /**
- * Sets extra HTTP headers to add to all the requests sent to the server.
- *
- * @param {Object} extraHeaders The HTTP headers.
- */
- BaseTestChannel.prototype.setExtraHeaders = function(extraHeaders) {
- this.extraHeaders_ = extraHeaders;
- };
- /**
- * Starts the test channel. This initiates connections to the server.
- *
- * @param {string} path The relative uri for the test connection.
- */
- BaseTestChannel.prototype.connect = function(path) {
- this.path_ = path;
- var sendDataUri = this.channel_.getForwardChannelUri(this.path_);
- requestStats.notifyStatEvent(requestStats.Stat.TEST_STAGE_ONE_START);
- // If the channel already has the result of the handshake, then skip it.
- var handshakeResult = this.channel_.getConnectionState().handshakeResult;
- if (goog.isDefAndNotNull(handshakeResult)) {
- this.hostPrefix_ = this.channel_.correctHostPrefix(handshakeResult[0]);
- this.state_ = BaseTestChannel.State_.CONNECTION_TESTING;
- this.checkBufferingProxy_();
- return;
- }
- // the first request returns server specific parameters
- sendDataUri.setParameterValues('MODE', 'init');
- // http-session-id to be generated as the response
- if (!this.channel_.getBackgroundChannelTest() &&
- this.channel_.getHttpSessionIdParam()) {
- sendDataUri.setParameterValues(WebChannel.X_HTTP_SESSION_ID,
- this.channel_.getHttpSessionIdParam());
- }
- this.request_ = ChannelRequest.createChannelRequest(this, this.channelDebug_);
- this.request_.setExtraHeaders(this.extraHeaders_);
- this.request_.xmlHttpGet(
- sendDataUri, false /* decodeChunks */, null /* hostPrefix */,
- true /* opt_noClose */);
- this.state_ = BaseTestChannel.State_.INIT;
- };
- /**
- * 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
- */
- BaseTestChannel.prototype.checkBufferingProxy_ = function() {
- this.channelDebug_.debug('TestConnection: starting stage 2');
- // If the test result is already available, skip its execution.
- var bufferingProxyResult =
- this.channel_.getConnectionState().bufferingProxyResult;
- if (goog.isDefAndNotNull(bufferingProxyResult)) {
- this.channelDebug_.debug(
- 'TestConnection: skipping stage 2, precomputed result is ' +
- bufferingProxyResult ?
- 'Buffered' :
- 'Unbuffered');
- requestStats.notifyStatEvent(requestStats.Stat.TEST_STAGE_TWO_START);
- if (bufferingProxyResult) { // Buffered/Proxy connection
- requestStats.notifyStatEvent(requestStats.Stat.PROXY);
- this.channel_.testConnectionFinished(this, false);
- } else { // Unbuffered/NoProxy connection
- requestStats.notifyStatEvent(requestStats.Stat.NOPROXY);
- this.channel_.testConnectionFinished(this, true);
- }
- return; // Skip the test
- }
- this.request_ = ChannelRequest.createChannelRequest(this, this.channelDebug_);
- this.request_.setExtraHeaders(this.extraHeaders_);
- var recvDataUri = this.channel_.getBackChannelUri(
- this.hostPrefix_,
- /** @type {string} */ (this.path_));
- requestStats.notifyStatEvent(requestStats.Stat.TEST_STAGE_TWO_START);
- recvDataUri.setParameterValues('TYPE', 'xmlhttp');
- var param = this.channel_.getHttpSessionIdParam();
- var value = this.channel_.getHttpSessionId();
- if (param && value) {
- recvDataUri.setParameterValue(param, value);
- }
- this.request_.xmlHttpGet(
- recvDataUri, false /** decodeChunks */, this.hostPrefix_,
- false /** opt_noClose */);
- };
- /**
- * @override
- */
- BaseTestChannel.prototype.createXhrIo = function(hostPrefix) {
- return this.channel_.createXhrIo(hostPrefix);
- };
- /**
- * Aborts the test channel.
- */
- BaseTestChannel.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.
- * @override
- */
- BaseTestChannel.prototype.isClosed = function() {
- return false;
- };
- /**
- * Callback from ChannelRequest for when new data is received
- *
- * @param {ChannelRequest} req The request object.
- * @param {string} responseText The text of the response.
- * @override
- */
- BaseTestChannel.prototype.onRequestData = function(req, responseText) {
- this.lastStatusCode_ = req.getLastStatusCode();
- if (this.state_ == BaseTestChannel.State_.INIT) {
- this.channelDebug_.debug('TestConnection: Got data for stage 1');
- this.applyControlHeaders_(req);
- if (!responseText) {
- this.channelDebug_.debug('TestConnection: Null responseText');
- // The server should always send text; something is wrong here
- this.channel_.testConnectionFailure(this, ChannelRequest.Error.BAD_DATA);
- return;
- }
- try {
- var channel = /** @type {!goog.labs.net.webChannel.WebChannelBase} */ (
- this.channel_);
- var respArray = channel.getWireCodec().decodeMessage(responseText);
- } catch (e) {
- this.channelDebug_.dumpException(e);
- this.channel_.testConnectionFailure(this, ChannelRequest.Error.BAD_DATA);
- return;
- }
- this.hostPrefix_ = this.channel_.correctHostPrefix(respArray[0]);
- } else if (this.state_ == BaseTestChannel.State_.CONNECTION_TESTING) {
- if (this.receivedIntermediateResult_) {
- requestStats.notifyStatEvent(requestStats.Stat.TEST_STAGE_TWO_DATA_TWO);
- } else {
- // '11111' is used instead of '1' to prevent a small amount of buffering
- // by Safari.
- if (responseText == '11111') {
- requestStats.notifyStatEvent(requestStats.Stat.TEST_STAGE_TWO_DATA_ONE);
- this.receivedIntermediateResult_ = true;
- 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');
- requestStats.notifyStatEvent(requestStats.Stat.NOPROXY);
- this.channel_.testConnectionFinished(this, true);
- }
- } else {
- requestStats.notifyStatEvent(
- requestStats.Stat.TEST_STAGE_TWO_DATA_BOTH);
- this.receivedIntermediateResult_ = false;
- }
- }
- }
- };
- /**
- * Callback from ChannelRequest that indicates a request has completed.
- *
- * @param {!ChannelRequest} req The request object.
- * @override
- */
- BaseTestChannel.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_ == BaseTestChannel.State_.INIT) {
- requestStats.notifyStatEvent(requestStats.Stat.TEST_STAGE_ONE_FAILED);
- } else if (this.state_ == BaseTestChannel.State_.CONNECTION_TESTING) {
- requestStats.notifyStatEvent(requestStats.Stat.TEST_STAGE_TWO_FAILED);
- }
- this.channel_.testConnectionFailure(
- this,
- /** @type {ChannelRequest.Error} */
- (this.request_.getLastError()));
- return;
- }
- if (this.state_ == BaseTestChannel.State_.INIT) {
- this.state_ = BaseTestChannel.State_.CONNECTION_TESTING;
- this.channelDebug_.debug(
- 'TestConnection: request complete for initial check');
- this.checkBufferingProxy_();
- } else if (this.state_ == BaseTestChannel.State_.CONNECTION_TESTING) {
- this.channelDebug_.debug('TestConnection: request complete for stage 2');
- var goodConn = this.receivedIntermediateResult_;
- if (goodConn) {
- this.channelDebug_.debug(
- 'Test connection succeeded; using streaming connection');
- requestStats.notifyStatEvent(requestStats.Stat.NOPROXY);
- this.channel_.testConnectionFinished(this, true);
- } else {
- this.channelDebug_.debug('Test connection failed; not using streaming');
- requestStats.notifyStatEvent(requestStats.Stat.PROXY);
- this.channel_.testConnectionFinished(this, false);
- }
- }
- };
- /**
- * Apply any control headers from the initial handshake response.
- *
- * @param {!ChannelRequest} req The request object.
- * @private
- */
- BaseTestChannel.prototype.applyControlHeaders_ = function(req) {
- if (this.channel_.getBackgroundChannelTest()) {
- return;
- }
- var xhr = req.getXhr();
- if (xhr) {
- var protocolHeader = xhr.getStreamingResponseHeader(
- WebChannel.X_CLIENT_WIRE_PROTOCOL);
- this.clientProtocol_ = protocolHeader ? protocolHeader : null;
- if (this.channel_.getHttpSessionIdParam()) {
- var httpSessionIdHeader = xhr.getStreamingResponseHeader(
- WebChannel.X_HTTP_SESSION_ID);
- if (httpSessionIdHeader) {
- this.channel_.setHttpSessionId(httpSessionIdHeader);
- } else {
- this.channelDebug_.warning(
- 'Missing X_HTTP_SESSION_ID in the handshake response');
- }
- }
- }
- };
- /**
- * @return {?string} The client protocol as recorded with the init handshake
- * request.
- */
- BaseTestChannel.prototype.getClientProtocol = function() {
- return this.clientProtocol_;
- };
- /**
- * Returns the last status code received for a request.
- * @return {number} The last status code received for a request.
- */
- BaseTestChannel.prototype.getLastStatusCode = function() {
- return this.lastStatusCode_;
- };
- /**
- * @return {boolean} Whether we should be using secondary domains when the
- * server instructs us to do so.
- * @override
- */
- BaseTestChannel.prototype.shouldUseSecondaryDomains = function() {
- return this.channel_.shouldUseSecondaryDomains();
- };
- /**
- * @override
- */
- BaseTestChannel.prototype.isActive = function() {
- 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
- */
- BaseTestChannel.prototype.checkForEarlyNonBuffered_ = function() {
- return ChannelRequest.supportsXhrStreaming();
- };
- /**
- * @override
- */
- BaseTestChannel.prototype.getForwardChannelUri = goog.abstractMethod;
- /**
- * @override
- */
- BaseTestChannel.prototype.getBackChannelUri = goog.abstractMethod;
- /**
- * @override
- */
- BaseTestChannel.prototype.correctHostPrefix = goog.abstractMethod;
- /**
- * @override
- */
- BaseTestChannel.prototype.createDataUri = goog.abstractMethod;
- /**
- * @override
- */
- BaseTestChannel.prototype.testConnectionFinished = goog.abstractMethod;
- /**
- * @override
- */
- BaseTestChannel.prototype.testConnectionFailure = goog.abstractMethod;
- /**
- * @override
- */
- BaseTestChannel.prototype.getConnectionState = goog.abstractMethod;
- /**
- * @override
- */
- BaseTestChannel.prototype.setHttpSessionIdParam = goog.abstractMethod;
- /**
- * @override
- */
- BaseTestChannel.prototype.getHttpSessionIdParam = goog.abstractMethod;
- /**
- * @override
- */
- BaseTestChannel.prototype.setHttpSessionId = goog.abstractMethod;
- /**
- * @override
- */
- BaseTestChannel.prototype.getHttpSessionId = goog.abstractMethod;
- /**
- * @override
- */
- BaseTestChannel.prototype.getBackgroundChannelTest = goog.abstractMethod;
- }); // goog.scope
|