Path: blob/trunk/third_party/closure/goog/labs/net/webchannel/basetestchannel.js
1865 views
// Copyright 2006 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314/**15* @fileoverview Base TestChannel implementation.16*17*/181920goog.provide('goog.labs.net.webChannel.BaseTestChannel');2122goog.require('goog.labs.net.webChannel.Channel');23goog.require('goog.labs.net.webChannel.ChannelRequest');24goog.require('goog.labs.net.webChannel.WebChannelDebug');25goog.require('goog.labs.net.webChannel.requestStats');26goog.require('goog.labs.net.webChannel.requestStats.Stat');27goog.require('goog.net.WebChannel');28293031/**32* A TestChannel is used during the first part of channel negotiation33* with the server to create the channel. It helps us determine whether we're34* behind a buffering proxy.35*36* @constructor37* @struct38* @param {!goog.labs.net.webChannel.Channel} channel The channel39* that owns this test channel.40* @param {!goog.labs.net.webChannel.WebChannelDebug} channelDebug A41* WebChannelDebug instance to use for logging.42* @implements {goog.labs.net.webChannel.Channel}43*/44goog.labs.net.webChannel.BaseTestChannel = function(channel, channelDebug) {45/**46* The channel that owns this test channel47* @private {!goog.labs.net.webChannel.Channel}48*/49this.channel_ = channel;5051/**52* The channel debug to use for logging53* @private {!goog.labs.net.webChannel.WebChannelDebug}54*/55this.channelDebug_ = channelDebug;5657/**58* Extra HTTP headers to add to all the requests sent to the server.59* @private {Object}60*/61this.extraHeaders_ = null;6263/**64* The test request.65* @private {goog.labs.net.webChannel.ChannelRequest}66*/67this.request_ = null;6869/**70* Whether we have received the first result as an intermediate result. This71* helps us determine whether we're behind a buffering proxy.72* @private {boolean}73*/74this.receivedIntermediateResult_ = false;7576/**77* The relative path for test requests.78* @private {?string}79*/80this.path_ = null;8182/**83* The last status code received.84* @private {number}85*/86this.lastStatusCode_ = -1;8788/**89* A subdomain prefix for using a subdomain in IE for the backchannel90* requests.91* @private {?string}92*/93this.hostPrefix_ = null;9495/**96* The effective client protocol as indicated by the initial handshake97* response via the x-client-wire-protocol header.98*99* @private {?string}100*/101this.clientProtocol_ = null;102};103104105goog.scope(function() {106var WebChannel = goog.net.WebChannel;107var BaseTestChannel = goog.labs.net.webChannel.BaseTestChannel;108var WebChannelDebug = goog.labs.net.webChannel.WebChannelDebug;109var ChannelRequest = goog.labs.net.webChannel.ChannelRequest;110var requestStats = goog.labs.net.webChannel.requestStats;111var Channel = goog.labs.net.webChannel.Channel;112113114/**115* Enum type for the test channel state machine116* @enum {number}117* @private118*/119BaseTestChannel.State_ = {120/**121* The state for the TestChannel state machine where we making the122* initial call to get the server configured parameters.123*/124INIT: 0,125126/**127* The state for the TestChannel state machine where we're checking to128* se if we're behind a buffering proxy.129*/130CONNECTION_TESTING: 1131};132133134/**135* The state of the state machine for this object.136*137* @private {?BaseTestChannel.State_}138*/139BaseTestChannel.prototype.state_ = null;140141142/**143* Sets extra HTTP headers to add to all the requests sent to the server.144*145* @param {Object} extraHeaders The HTTP headers.146*/147BaseTestChannel.prototype.setExtraHeaders = function(extraHeaders) {148this.extraHeaders_ = extraHeaders;149};150151152/**153* Starts the test channel. This initiates connections to the server.154*155* @param {string} path The relative uri for the test connection.156*/157BaseTestChannel.prototype.connect = function(path) {158this.path_ = path;159var sendDataUri = this.channel_.getForwardChannelUri(this.path_);160161requestStats.notifyStatEvent(requestStats.Stat.TEST_STAGE_ONE_START);162163// If the channel already has the result of the handshake, then skip it.164var handshakeResult = this.channel_.getConnectionState().handshakeResult;165if (goog.isDefAndNotNull(handshakeResult)) {166this.hostPrefix_ = this.channel_.correctHostPrefix(handshakeResult[0]);167this.state_ = BaseTestChannel.State_.CONNECTION_TESTING;168this.checkBufferingProxy_();169return;170}171172// the first request returns server specific parameters173sendDataUri.setParameterValues('MODE', 'init');174175// http-session-id to be generated as the response176if (!this.channel_.getBackgroundChannelTest() &&177this.channel_.getHttpSessionIdParam()) {178sendDataUri.setParameterValues(WebChannel.X_HTTP_SESSION_ID,179this.channel_.getHttpSessionIdParam());180}181182this.request_ = ChannelRequest.createChannelRequest(this, this.channelDebug_);183184this.request_.setExtraHeaders(this.extraHeaders_);185186this.request_.xmlHttpGet(187sendDataUri, false /* decodeChunks */, null /* hostPrefix */,188true /* opt_noClose */);189this.state_ = BaseTestChannel.State_.INIT;190};191192193/**194* Begins the second stage of the test channel where we test to see if we're195* behind a buffering proxy. The server sends back a multi-chunked response196* with the first chunk containing the content '1' and then two seconds later197* sending the second chunk containing the content '2'. Depending on how we198* receive the content, we can tell if we're behind a buffering proxy.199* @private200*/201BaseTestChannel.prototype.checkBufferingProxy_ = function() {202this.channelDebug_.debug('TestConnection: starting stage 2');203204// If the test result is already available, skip its execution.205var bufferingProxyResult =206this.channel_.getConnectionState().bufferingProxyResult;207if (goog.isDefAndNotNull(bufferingProxyResult)) {208this.channelDebug_.debug(209'TestConnection: skipping stage 2, precomputed result is ' +210bufferingProxyResult ?211'Buffered' :212'Unbuffered');213requestStats.notifyStatEvent(requestStats.Stat.TEST_STAGE_TWO_START);214if (bufferingProxyResult) { // Buffered/Proxy connection215requestStats.notifyStatEvent(requestStats.Stat.PROXY);216this.channel_.testConnectionFinished(this, false);217} else { // Unbuffered/NoProxy connection218requestStats.notifyStatEvent(requestStats.Stat.NOPROXY);219this.channel_.testConnectionFinished(this, true);220}221return; // Skip the test222}223this.request_ = ChannelRequest.createChannelRequest(this, this.channelDebug_);224this.request_.setExtraHeaders(this.extraHeaders_);225var recvDataUri = this.channel_.getBackChannelUri(226this.hostPrefix_,227/** @type {string} */ (this.path_));228229requestStats.notifyStatEvent(requestStats.Stat.TEST_STAGE_TWO_START);230recvDataUri.setParameterValues('TYPE', 'xmlhttp');231232var param = this.channel_.getHttpSessionIdParam();233var value = this.channel_.getHttpSessionId();234if (param && value) {235recvDataUri.setParameterValue(param, value);236}237238this.request_.xmlHttpGet(239recvDataUri, false /** decodeChunks */, this.hostPrefix_,240false /** opt_noClose */);241};242243244/**245* @override246*/247BaseTestChannel.prototype.createXhrIo = function(hostPrefix) {248return this.channel_.createXhrIo(hostPrefix);249};250251252/**253* Aborts the test channel.254*/255BaseTestChannel.prototype.abort = function() {256if (this.request_) {257this.request_.cancel();258this.request_ = null;259}260this.lastStatusCode_ = -1;261};262263264/**265* Returns whether the test channel is closed. The ChannelRequest object expects266* this method to be implemented on its handler.267*268* @return {boolean} Whether the channel is closed.269* @override270*/271BaseTestChannel.prototype.isClosed = function() {272return false;273};274275276/**277* Callback from ChannelRequest for when new data is received278*279* @param {ChannelRequest} req The request object.280* @param {string} responseText The text of the response.281* @override282*/283BaseTestChannel.prototype.onRequestData = function(req, responseText) {284this.lastStatusCode_ = req.getLastStatusCode();285if (this.state_ == BaseTestChannel.State_.INIT) {286this.channelDebug_.debug('TestConnection: Got data for stage 1');287288this.applyControlHeaders_(req);289290if (!responseText) {291this.channelDebug_.debug('TestConnection: Null responseText');292// The server should always send text; something is wrong here293this.channel_.testConnectionFailure(this, ChannelRequest.Error.BAD_DATA);294return;295}296297298try {299var channel = /** @type {!goog.labs.net.webChannel.WebChannelBase} */ (300this.channel_);301var respArray = channel.getWireCodec().decodeMessage(responseText);302} catch (e) {303this.channelDebug_.dumpException(e);304this.channel_.testConnectionFailure(this, ChannelRequest.Error.BAD_DATA);305return;306}307this.hostPrefix_ = this.channel_.correctHostPrefix(respArray[0]);308} else if (this.state_ == BaseTestChannel.State_.CONNECTION_TESTING) {309if (this.receivedIntermediateResult_) {310requestStats.notifyStatEvent(requestStats.Stat.TEST_STAGE_TWO_DATA_TWO);311} else {312// '11111' is used instead of '1' to prevent a small amount of buffering313// by Safari.314if (responseText == '11111') {315requestStats.notifyStatEvent(requestStats.Stat.TEST_STAGE_TWO_DATA_ONE);316this.receivedIntermediateResult_ = true;317if (this.checkForEarlyNonBuffered_()) {318// If early chunk detection is on, and we passed the tests,319// assume HTTP_OK, cancel the test and turn on noproxy mode.320this.lastStatusCode_ = 200;321this.request_.cancel();322this.channelDebug_.debug(323'Test connection succeeded; using streaming connection');324requestStats.notifyStatEvent(requestStats.Stat.NOPROXY);325this.channel_.testConnectionFinished(this, true);326}327} else {328requestStats.notifyStatEvent(329requestStats.Stat.TEST_STAGE_TWO_DATA_BOTH);330this.receivedIntermediateResult_ = false;331}332}333}334};335336337/**338* Callback from ChannelRequest that indicates a request has completed.339*340* @param {!ChannelRequest} req The request object.341* @override342*/343BaseTestChannel.prototype.onRequestComplete = function(req) {344this.lastStatusCode_ = this.request_.getLastStatusCode();345if (!this.request_.getSuccess()) {346this.channelDebug_.debug(347'TestConnection: request failed, in state ' + this.state_);348if (this.state_ == BaseTestChannel.State_.INIT) {349requestStats.notifyStatEvent(requestStats.Stat.TEST_STAGE_ONE_FAILED);350} else if (this.state_ == BaseTestChannel.State_.CONNECTION_TESTING) {351requestStats.notifyStatEvent(requestStats.Stat.TEST_STAGE_TWO_FAILED);352}353this.channel_.testConnectionFailure(354this,355/** @type {ChannelRequest.Error} */356(this.request_.getLastError()));357return;358}359360if (this.state_ == BaseTestChannel.State_.INIT) {361this.state_ = BaseTestChannel.State_.CONNECTION_TESTING;362363this.channelDebug_.debug(364'TestConnection: request complete for initial check');365366this.checkBufferingProxy_();367} else if (this.state_ == BaseTestChannel.State_.CONNECTION_TESTING) {368this.channelDebug_.debug('TestConnection: request complete for stage 2');369370var goodConn = this.receivedIntermediateResult_;371if (goodConn) {372this.channelDebug_.debug(373'Test connection succeeded; using streaming connection');374requestStats.notifyStatEvent(requestStats.Stat.NOPROXY);375this.channel_.testConnectionFinished(this, true);376} else {377this.channelDebug_.debug('Test connection failed; not using streaming');378requestStats.notifyStatEvent(requestStats.Stat.PROXY);379this.channel_.testConnectionFinished(this, false);380}381}382};383384385/**386* Apply any control headers from the initial handshake response.387*388* @param {!ChannelRequest} req The request object.389* @private390*/391BaseTestChannel.prototype.applyControlHeaders_ = function(req) {392if (this.channel_.getBackgroundChannelTest()) {393return;394}395396var xhr = req.getXhr();397if (xhr) {398var protocolHeader = xhr.getStreamingResponseHeader(399WebChannel.X_CLIENT_WIRE_PROTOCOL);400this.clientProtocol_ = protocolHeader ? protocolHeader : null;401402if (this.channel_.getHttpSessionIdParam()) {403var httpSessionIdHeader = xhr.getStreamingResponseHeader(404WebChannel.X_HTTP_SESSION_ID);405if (httpSessionIdHeader) {406this.channel_.setHttpSessionId(httpSessionIdHeader);407} else {408this.channelDebug_.warning(409'Missing X_HTTP_SESSION_ID in the handshake response');410}411}412}413};414415416/**417* @return {?string} The client protocol as recorded with the init handshake418* request.419*/420BaseTestChannel.prototype.getClientProtocol = function() {421return this.clientProtocol_;422};423424425/**426* Returns the last status code received for a request.427* @return {number} The last status code received for a request.428*/429BaseTestChannel.prototype.getLastStatusCode = function() {430return this.lastStatusCode_;431};432433434/**435* @return {boolean} Whether we should be using secondary domains when the436* server instructs us to do so.437* @override438*/439BaseTestChannel.prototype.shouldUseSecondaryDomains = function() {440return this.channel_.shouldUseSecondaryDomains();441};442443444/**445* @override446*/447BaseTestChannel.prototype.isActive = function() {448return this.channel_.isActive();449};450451452/**453* @return {boolean} True if test stage 2 detected a non-buffered454* channel early and early no buffering detection is enabled.455* @private456*/457BaseTestChannel.prototype.checkForEarlyNonBuffered_ = function() {458return ChannelRequest.supportsXhrStreaming();459};460461462/**463* @override464*/465BaseTestChannel.prototype.getForwardChannelUri = goog.abstractMethod;466467468/**469* @override470*/471BaseTestChannel.prototype.getBackChannelUri = goog.abstractMethod;472473474/**475* @override476*/477BaseTestChannel.prototype.correctHostPrefix = goog.abstractMethod;478479480/**481* @override482*/483BaseTestChannel.prototype.createDataUri = goog.abstractMethod;484485486/**487* @override488*/489BaseTestChannel.prototype.testConnectionFinished = goog.abstractMethod;490491492/**493* @override494*/495BaseTestChannel.prototype.testConnectionFailure = goog.abstractMethod;496497498/**499* @override500*/501BaseTestChannel.prototype.getConnectionState = goog.abstractMethod;502503504/**505* @override506*/507BaseTestChannel.prototype.setHttpSessionIdParam = goog.abstractMethod;508509510/**511* @override512*/513BaseTestChannel.prototype.getHttpSessionIdParam = goog.abstractMethod;514515516/**517* @override518*/519BaseTestChannel.prototype.setHttpSessionId = goog.abstractMethod;520521522/**523* @override524*/525BaseTestChannel.prototype.getHttpSessionId = goog.abstractMethod;526527528/**529* @override530*/531BaseTestChannel.prototype.getBackgroundChannelTest = goog.abstractMethod;532}); // goog.scope533534535