Path: blob/trunk/third_party/closure/goog/net/channelrequest.js
2868 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 Definition of the ChannelRequest class. The ChannelRequest16* object encapsulates the logic for making a single request, either for the17* forward channel, back channel, or test channel, to the server. It contains18* the logic for the three types of transports we use in the BrowserChannel:19* XMLHTTP, Trident ActiveX (ie only), and Image request. It provides timeout20* detection. This class is part of the BrowserChannel implementation and is not21* for use by normal application code.22*23*/242526goog.provide('goog.net.ChannelRequest');27goog.provide('goog.net.ChannelRequest.Error');2829goog.require('goog.Timer');30goog.require('goog.async.Throttle');31goog.require('goog.dom.TagName');32goog.require('goog.dom.safe');33goog.require('goog.events.EventHandler');34goog.require('goog.html.SafeUrl');35goog.require('goog.html.uncheckedconversions');36goog.require('goog.net.ErrorCode');37goog.require('goog.net.EventType');38goog.require('goog.net.XmlHttp');39goog.require('goog.object');40goog.require('goog.string');41goog.require('goog.string.Const');42goog.require('goog.userAgent');4344// TODO(nnaze): This file depends on goog.net.BrowserChannel and vice versa (a45// circular dependency). Usages of BrowserChannel are marked as46// "missingRequire" below for now. This should be fixed through refactoring.47484950/**51* Creates a ChannelRequest object which encapsulates a request to the server.52* A new ChannelRequest is created for each request to the server.53*54* @param {goog.net.BrowserChannel|goog.net.BrowserTestChannel} channel55* The BrowserChannel that owns this request.56* @param {goog.net.ChannelDebug} channelDebug A ChannelDebug to use for57* logging.58* @param {string=} opt_sessionId The session id for the channel.59* @param {string|number=} opt_requestId The request id for this request.60* @param {number=} opt_retryId The retry id for this request.61* @constructor62*/63goog.net.ChannelRequest = function(64channel, channelDebug, opt_sessionId, opt_requestId, opt_retryId) {65/**66* The BrowserChannel object that owns the request.67* @type {goog.net.BrowserChannel|goog.net.BrowserTestChannel}68* @private69*/70this.channel_ = channel;7172/**73* The channel debug to use for logging74* @type {goog.net.ChannelDebug}75* @private76*/77this.channelDebug_ = channelDebug;7879/**80* The Session ID for the channel.81* @type {string|undefined}82* @private83*/84this.sid_ = opt_sessionId;8586/**87* The RID (request ID) for the request.88* @type {string|number|undefined}89* @private90*/91this.rid_ = opt_requestId;929394/**95* The attempt number of the current request.96* @type {number}97* @private98*/99this.retryId_ = opt_retryId || 1;100101102/**103* The timeout in ms before failing the request.104* @type {number}105* @private106*/107this.timeout_ = goog.net.ChannelRequest.TIMEOUT_MS;108109/**110* An object to keep track of the channel request event listeners.111* @type {!goog.events.EventHandler<!goog.net.ChannelRequest>}112* @private113*/114this.eventHandler_ = new goog.events.EventHandler(this);115116/**117* A timer for polling responseText in browsers that don't fire118* onreadystatechange during incremental loading of responseText.119* @type {goog.Timer}120* @private121*/122this.pollingTimer_ = new goog.Timer();123124this.pollingTimer_.setInterval(goog.net.ChannelRequest.POLLING_INTERVAL_MS);125};126127128/**129* Extra HTTP headers to add to all the requests sent to the server.130* @type {Object}131* @private132*/133goog.net.ChannelRequest.prototype.extraHeaders_ = null;134135136/**137* Whether the request was successful. This is only set to true after the138* request successfuly completes.139* @type {boolean}140* @private141*/142goog.net.ChannelRequest.prototype.successful_ = false;143144145/**146* The TimerID of the timer used to detect if the request has timed-out.147* @type {?number}148* @private149*/150goog.net.ChannelRequest.prototype.watchDogTimerId_ = null;151152153/**154* The time in the future when the request will timeout.155* @type {?number}156* @private157*/158goog.net.ChannelRequest.prototype.watchDogTimeoutTime_ = null;159160161/**162* The time the request started.163* @type {?number}164* @private165*/166goog.net.ChannelRequest.prototype.requestStartTime_ = null;167168169/**170* The type of request (XMLHTTP, IMG, Trident)171* @type {?number}172* @private173*/174goog.net.ChannelRequest.prototype.type_ = null;175176177/**178* The base Uri for the request. The includes all the parameters except the179* one that indicates the retry number.180* @type {goog.Uri?}181* @private182*/183goog.net.ChannelRequest.prototype.baseUri_ = null;184185186/**187* The request Uri that was actually used for the most recent request attempt.188* @type {goog.Uri?}189* @private190*/191goog.net.ChannelRequest.prototype.requestUri_ = null;192193194/**195* The post data, if the request is a post.196* @type {?string}197* @private198*/199goog.net.ChannelRequest.prototype.postData_ = null;200201202/**203* The XhrLte request if the request is using XMLHTTP204* @type {goog.net.XhrIo}205* @private206*/207goog.net.ChannelRequest.prototype.xmlHttp_ = null;208209210/**211* The position of where the next unprocessed chunk starts in the response212* text.213* @type {number}214* @private215*/216goog.net.ChannelRequest.prototype.xmlHttpChunkStart_ = 0;217218219/**220* The Trident instance if the request is using Trident.221* @type {Object}222* @private223*/224goog.net.ChannelRequest.prototype.trident_ = null;225226227/**228* The verb (Get or Post) for the request.229* @type {?string}230* @private231*/232goog.net.ChannelRequest.prototype.verb_ = null;233234235/**236* The last error if the request failed.237* @type {?goog.net.ChannelRequest.Error}238* @private239*/240goog.net.ChannelRequest.prototype.lastError_ = null;241242243/**244* The last status code received.245* @type {number}246* @private247*/248goog.net.ChannelRequest.prototype.lastStatusCode_ = -1;249250251/**252* Whether to send the Connection:close header as part of the request.253* @type {boolean}254* @private255*/256goog.net.ChannelRequest.prototype.sendClose_ = true;257258259/**260* Whether the request has been cancelled due to a call to cancel.261* @type {boolean}262* @private263*/264goog.net.ChannelRequest.prototype.cancelled_ = false;265266267/**268* A throttle time in ms for readystatechange events for the backchannel.269* Useful for throttling when ready state is INTERACTIVE (partial data).270* If set to zero no throttle is used.271*272* @see goog.net.BrowserChannel.prototype.readyStateChangeThrottleMs_273*274* @type {number}275* @private276*/277goog.net.ChannelRequest.prototype.readyStateChangeThrottleMs_ = 0;278279280/**281* The throttle for readystatechange events for the current request, or null282* if there is none.283* @type {goog.async.Throttle}284* @private285*/286goog.net.ChannelRequest.prototype.readyStateChangeThrottle_ = null;287288289/**290* Default timeout in MS for a request. The server must return data within this291* time limit for the request to not timeout.292* @type {number}293*/294goog.net.ChannelRequest.TIMEOUT_MS = 45 * 1000;295296297/**298* How often to poll (in MS) for changes to responseText in browsers that don't299* fire onreadystatechange during incremental loading of responseText.300* @type {number}301*/302goog.net.ChannelRequest.POLLING_INTERVAL_MS = 250;303304305/**306* Minimum version of Safari that receives a non-null responseText in ready307* state interactive.308* @type {string}309* @private310*/311goog.net.ChannelRequest.MIN_WEBKIT_FOR_INTERACTIVE_ = '420+';312313314/**315* Enum for channel requests type316* @enum {number}317* @private318*/319goog.net.ChannelRequest.Type_ = {320/**321* XMLHTTP requests.322*/323XML_HTTP: 1,324325/**326* IMG requests.327*/328IMG: 2,329330/**331* Requests that use the MSHTML ActiveX control.332*/333TRIDENT: 3334};335336337/**338* Enum type for identifying a ChannelRequest error.339* @enum {number}340*/341goog.net.ChannelRequest.Error = {342/**343* Errors due to a non-200 status code.344*/345STATUS: 0,346347/**348* Errors due to no data being returned.349*/350NO_DATA: 1,351352/**353* Errors due to a timeout.354*/355TIMEOUT: 2,356357/**358* Errors due to the server returning an unknown.359*/360UNKNOWN_SESSION_ID: 3,361362/**363* Errors due to bad data being received.364*/365BAD_DATA: 4,366367/**368* Errors due to the handler throwing an exception.369*/370HANDLER_EXCEPTION: 5,371372/**373* The browser declared itself offline during the request.374*/375BROWSER_OFFLINE: 6,376377/**378* IE is blocking ActiveX streaming.379*/380ACTIVE_X_BLOCKED: 7381};382383384/**385* Returns a useful error string for debugging based on the specified error386* code.387* @param {goog.net.ChannelRequest.Error} errorCode The error code.388* @param {number} statusCode The HTTP status code.389* @return {string} The error string for the given code combination.390*/391goog.net.ChannelRequest.errorStringFromCode = function(errorCode, statusCode) {392switch (errorCode) {393case goog.net.ChannelRequest.Error.STATUS:394return 'Non-200 return code (' + statusCode + ')';395case goog.net.ChannelRequest.Error.NO_DATA:396return 'XMLHTTP failure (no data)';397case goog.net.ChannelRequest.Error.TIMEOUT:398return 'HttpConnection timeout';399default:400return 'Unknown error';401}402};403404405/**406* Sentinel value used to indicate an invalid chunk in a multi-chunk response.407* @type {Object}408* @private409*/410goog.net.ChannelRequest.INVALID_CHUNK_ = {};411412413/**414* Sentinel value used to indicate an incomplete chunk in a multi-chunk415* response.416* @type {Object}417* @private418*/419goog.net.ChannelRequest.INCOMPLETE_CHUNK_ = {};420421422/**423* Returns whether XHR streaming is supported on this browser.424*425* If XHR streaming is not supported, we will try to use an ActiveXObject426* to create a Forever IFrame.427*428* @return {boolean} Whether XHR streaming is supported.429* @see http://code.google.com/p/closure-library/issues/detail?id=346430*/431goog.net.ChannelRequest.supportsXhrStreaming = function() {432return !goog.userAgent.IE || goog.userAgent.isDocumentModeOrHigher(10);433};434435436/**437* Sets extra HTTP headers to add to all the requests sent to the server.438*439* @param {Object} extraHeaders The HTTP headers.440*/441goog.net.ChannelRequest.prototype.setExtraHeaders = function(extraHeaders) {442this.extraHeaders_ = extraHeaders;443};444445446/**447* Sets the timeout for a request448*449* @param {number} timeout The timeout in MS for when we fail the request.450*/451goog.net.ChannelRequest.prototype.setTimeout = function(timeout) {452this.timeout_ = timeout;453};454455456/**457* Sets the throttle for handling onreadystatechange events for the request.458*459* @param {number} throttle The throttle in ms. A value of zero indicates460* no throttle.461*/462goog.net.ChannelRequest.prototype.setReadyStateChangeThrottle = function(463throttle) {464this.readyStateChangeThrottleMs_ = throttle;465};466467468/**469* Uses XMLHTTP to send an HTTP POST to the server.470*471* @param {goog.Uri} uri The uri of the request.472* @param {string} postData The data for the post body.473* @param {boolean} decodeChunks Whether to the result is expected to be474* encoded for chunking and thus requires decoding.475*/476goog.net.ChannelRequest.prototype.xmlHttpPost = function(477uri, postData, decodeChunks) {478this.type_ = goog.net.ChannelRequest.Type_.XML_HTTP;479this.baseUri_ = uri.clone().makeUnique();480this.postData_ = postData;481this.decodeChunks_ = decodeChunks;482this.sendXmlHttp_(null /* hostPrefix */);483};484485486/**487* Uses XMLHTTP to send an HTTP GET to the server.488*489* @param {goog.Uri} uri The uri of the request.490* @param {boolean} decodeChunks Whether to the result is expected to be491* encoded for chunking and thus requires decoding.492* @param {?string} hostPrefix The host prefix, if we might be using a493* secondary domain. Note that it should also be in the URL, adding this494* won't cause it to be added to the URL.495* @param {boolean=} opt_noClose Whether to request that the tcp/ip connection496* should be closed.497*/498goog.net.ChannelRequest.prototype.xmlHttpGet = function(499uri, decodeChunks, hostPrefix, opt_noClose) {500this.type_ = goog.net.ChannelRequest.Type_.XML_HTTP;501this.baseUri_ = uri.clone().makeUnique();502this.postData_ = null;503this.decodeChunks_ = decodeChunks;504if (opt_noClose) {505this.sendClose_ = false;506}507this.sendXmlHttp_(hostPrefix);508};509510511/**512* Sends a request via XMLHTTP according to the current state of the513* ChannelRequest object.514*515* @param {?string} hostPrefix The host prefix, if we might be using a secondary516* domain.517* @private518*/519goog.net.ChannelRequest.prototype.sendXmlHttp_ = function(hostPrefix) {520this.requestStartTime_ = goog.now();521this.ensureWatchDogTimer_();522523// clone the base URI to create the request URI. The request uri has the524// attempt number as a parameter which helps in debugging.525this.requestUri_ = this.baseUri_.clone();526this.requestUri_.setParameterValues('t', this.retryId_);527528// send the request either as a POST or GET529this.xmlHttpChunkStart_ = 0;530var useSecondaryDomains = this.channel_.shouldUseSecondaryDomains();531this.xmlHttp_ =532this.channel_.createXhrIo(useSecondaryDomains ? hostPrefix : null);533534if (this.readyStateChangeThrottleMs_ > 0) {535this.readyStateChangeThrottle_ = new goog.async.Throttle(536goog.bind(this.xmlHttpHandler_, this, this.xmlHttp_),537this.readyStateChangeThrottleMs_);538}539540this.eventHandler_.listen(541this.xmlHttp_, goog.net.EventType.READY_STATE_CHANGE,542this.readyStateChangeHandler_);543544var headers = this.extraHeaders_ ? goog.object.clone(this.extraHeaders_) : {};545if (this.postData_) {546// todo (jonp) - use POST constant when Dan defines it547this.verb_ = 'POST';548headers['Content-Type'] = 'application/x-www-form-urlencoded';549this.xmlHttp_.send(this.requestUri_, this.verb_, this.postData_, headers);550} else {551// todo (jonp) - use GET constant when Dan defines it552this.verb_ = 'GET';553554// If the user agent is webkit, we cannot send the close header since it is555// disallowed by the browser. If we attempt to set the "Connection: close"556// header in WEBKIT browser, it will actually causes an error message.557if (this.sendClose_ && !goog.userAgent.WEBKIT) {558headers['Connection'] = 'close';559}560this.xmlHttp_.send(this.requestUri_, this.verb_, null, headers);561}562this.channel_.notifyServerReachabilityEvent(563/** @suppress {missingRequire} */ (564goog.net.BrowserChannel.ServerReachability.REQUEST_MADE));565this.channelDebug_.xmlHttpChannelRequest(566this.verb_, this.requestUri_, this.rid_, this.retryId_, this.postData_);567};568569570/**571* Handles a readystatechange event.572* @param {goog.events.Event} evt The event.573* @private574*/575goog.net.ChannelRequest.prototype.readyStateChangeHandler_ = function(evt) {576var xhr = /** @type {goog.net.XhrIo} */ (evt.target);577var throttle = this.readyStateChangeThrottle_;578if (throttle &&579xhr.getReadyState() == goog.net.XmlHttp.ReadyState.INTERACTIVE) {580// Only throttle in the partial data case.581this.channelDebug_.debug('Throttling readystatechange.');582throttle.fire();583} else {584// If we haven't throttled, just handle response directly.585this.xmlHttpHandler_(xhr);586}587};588589590/**591* XmlHttp handler592* @param {goog.net.XhrIo} xmlhttp The XhrIo object for the current request.593* @private594*/595goog.net.ChannelRequest.prototype.xmlHttpHandler_ = function(xmlhttp) {596/** @suppress {missingRequire} */597goog.net.BrowserChannel.onStartExecution();598599600try {601if (xmlhttp == this.xmlHttp_) {602this.onXmlHttpReadyStateChanged_();603} else {604this.channelDebug_.warning(605'Called back with an ' +606'unexpected xmlhttp');607}608} catch (ex) {609this.channelDebug_.debug('Failed call to OnXmlHttpReadyStateChanged_');610if (this.xmlHttp_ && this.xmlHttp_.getResponseText()) {611this.channelDebug_.dumpException(612ex, 'ResponseText: ' + this.xmlHttp_.getResponseText());613} else {614this.channelDebug_.dumpException(ex, 'No response text');615}616} finally {617/** @suppress {missingRequire} */618goog.net.BrowserChannel.onEndExecution();619}620};621622623/**624* Called by the readystate handler for XMLHTTP requests.625*626* @private627*/628goog.net.ChannelRequest.prototype.onXmlHttpReadyStateChanged_ = function() {629var readyState = this.xmlHttp_.getReadyState();630var errorCode = this.xmlHttp_.getLastErrorCode();631var statusCode = this.xmlHttp_.getStatus();632// If it is Safari less than 420+, there is a bug that causes null to be633// in the responseText on ready state interactive so we must wait for634// ready state complete.635if (!goog.net.ChannelRequest.supportsXhrStreaming() ||636(goog.userAgent.WEBKIT &&637!goog.userAgent.isVersionOrHigher(638goog.net.ChannelRequest.MIN_WEBKIT_FOR_INTERACTIVE_))) {639if (readyState < goog.net.XmlHttp.ReadyState.COMPLETE) {640// not yet ready641return;642}643} else {644// we get partial results in browsers that support ready state interactive.645// We also make sure that getResponseText is not null in interactive mode646// before we continue. However, we don't do it in Opera because it only647// fire readyState == INTERACTIVE once. We need the following code to poll648if (readyState < goog.net.XmlHttp.ReadyState.INTERACTIVE ||649readyState == goog.net.XmlHttp.ReadyState.INTERACTIVE &&650!goog.userAgent.OPERA && !this.xmlHttp_.getResponseText()) {651// not yet ready652return;653}654}655656// Dispatch any appropriate network events.657if (!this.cancelled_ && readyState == goog.net.XmlHttp.ReadyState.COMPLETE &&658errorCode != goog.net.ErrorCode.ABORT) {659// Pretty conservative, these are the only known scenarios which we'd660// consider indicative of a truly non-functional network connection.661if (errorCode == goog.net.ErrorCode.TIMEOUT || statusCode <= 0) {662this.channel_.notifyServerReachabilityEvent(663/** @suppress {missingRequire} */664goog.net.BrowserChannel.ServerReachability.REQUEST_FAILED);665} else {666this.channel_.notifyServerReachabilityEvent(667/** @suppress {missingRequire} */668goog.net.BrowserChannel.ServerReachability.REQUEST_SUCCEEDED);669}670}671672// got some data so cancel the watchdog timer673this.cancelWatchDogTimer_();674675var status = this.xmlHttp_.getStatus();676this.lastStatusCode_ = status;677var responseText = this.xmlHttp_.getResponseText();678if (!responseText) {679this.channelDebug_.debug(680'No response text for uri ' + this.requestUri_ + ' status ' + status);681}682this.successful_ = (status == 200);683684this.channelDebug_.xmlHttpChannelResponseMetaData(685/** @type {string} */ (this.verb_), this.requestUri_, this.rid_,686this.retryId_, readyState, status);687688if (!this.successful_) {689if (status == 400 && responseText.indexOf('Unknown SID') > 0) {690// the server error string will include 'Unknown SID' which indicates the691// server doesn't know about the session (maybe it got restarted, maybe692// the user got moved to another server, etc.,). Handlers can special693// case this error694this.lastError_ = goog.net.ChannelRequest.Error.UNKNOWN_SESSION_ID;695/** @suppress {missingRequire} */696goog.net.BrowserChannel.notifyStatEvent(697/** @suppress {missingRequire} */698goog.net.BrowserChannel.Stat.REQUEST_UNKNOWN_SESSION_ID);699this.channelDebug_.warning('XMLHTTP Unknown SID (' + this.rid_ + ')');700} else {701this.lastError_ = goog.net.ChannelRequest.Error.STATUS;702/** @suppress {missingRequire} */703goog.net.BrowserChannel.notifyStatEvent(704/** @suppress {missingRequire} */705goog.net.BrowserChannel.Stat.REQUEST_BAD_STATUS);706this.channelDebug_.warning(707'XMLHTTP Bad status ' + status + ' (' + this.rid_ + ')');708}709this.cleanup_();710this.dispatchFailure_();711return;712}713714if (readyState == goog.net.XmlHttp.ReadyState.COMPLETE) {715this.cleanup_();716}717718if (this.decodeChunks_) {719this.decodeNextChunks_(readyState, responseText);720if (goog.userAgent.OPERA && this.successful_ &&721readyState == goog.net.XmlHttp.ReadyState.INTERACTIVE) {722this.startPolling_();723}724} else {725this.channelDebug_.xmlHttpChannelResponseText(726this.rid_, responseText, null);727this.safeOnRequestData_(responseText);728}729730if (!this.successful_) {731return;732}733734if (!this.cancelled_) {735if (readyState == goog.net.XmlHttp.ReadyState.COMPLETE) {736this.channel_.onRequestComplete(this);737} else {738// The default is false, the result from this callback shouldn't carry739// over to the next callback, otherwise the request looks successful if740// the watchdog timer gets called741this.successful_ = false;742this.ensureWatchDogTimer_();743}744}745};746747748/**749* Decodes the next set of available chunks in the response.750* @param {number} readyState The value of readyState.751* @param {string} responseText The value of responseText.752* @private753*/754goog.net.ChannelRequest.prototype.decodeNextChunks_ = function(755readyState, responseText) {756var decodeNextChunksSuccessful = true;757while (!this.cancelled_ && this.xmlHttpChunkStart_ < responseText.length) {758var chunkText = this.getNextChunk_(responseText);759if (chunkText == goog.net.ChannelRequest.INCOMPLETE_CHUNK_) {760if (readyState == goog.net.XmlHttp.ReadyState.COMPLETE) {761// should have consumed entire response when the request is done762this.lastError_ = goog.net.ChannelRequest.Error.BAD_DATA;763/** @suppress {missingRequire} */764goog.net.BrowserChannel.notifyStatEvent(765/** @suppress {missingRequire} */766goog.net.BrowserChannel.Stat.REQUEST_INCOMPLETE_DATA);767decodeNextChunksSuccessful = false;768}769this.channelDebug_.xmlHttpChannelResponseText(770this.rid_, null, '[Incomplete Response]');771break;772} else if (chunkText == goog.net.ChannelRequest.INVALID_CHUNK_) {773this.lastError_ = goog.net.ChannelRequest.Error.BAD_DATA;774/** @suppress {missingRequire} */775goog.net.BrowserChannel.notifyStatEvent(776/** @suppress {missingRequire} */777goog.net.BrowserChannel.Stat.REQUEST_BAD_DATA);778this.channelDebug_.xmlHttpChannelResponseText(779this.rid_, responseText, '[Invalid Chunk]');780decodeNextChunksSuccessful = false;781break;782} else {783this.channelDebug_.xmlHttpChannelResponseText(784this.rid_, /** @type {string} */ (chunkText), null);785this.safeOnRequestData_(/** @type {string} */ (chunkText));786}787}788if (readyState == goog.net.XmlHttp.ReadyState.COMPLETE &&789responseText.length == 0) {790// also an error if we didn't get any response791this.lastError_ = goog.net.ChannelRequest.Error.NO_DATA;792/** @suppress {missingRequire} */793goog.net.BrowserChannel.notifyStatEvent(794/** @suppress {missingRequire} */795goog.net.BrowserChannel.Stat.REQUEST_NO_DATA);796decodeNextChunksSuccessful = false;797}798this.successful_ = this.successful_ && decodeNextChunksSuccessful;799if (!decodeNextChunksSuccessful) {800// malformed response - we make this trigger retry logic801this.channelDebug_.xmlHttpChannelResponseText(802this.rid_, responseText, '[Invalid Chunked Response]');803this.cleanup_();804this.dispatchFailure_();805}806};807808809/**810* Polls the response for new data.811* @private812*/813goog.net.ChannelRequest.prototype.pollResponse_ = function() {814var readyState = this.xmlHttp_.getReadyState();815var responseText = this.xmlHttp_.getResponseText();816if (this.xmlHttpChunkStart_ < responseText.length) {817this.cancelWatchDogTimer_();818this.decodeNextChunks_(readyState, responseText);819if (this.successful_ &&820readyState != goog.net.XmlHttp.ReadyState.COMPLETE) {821this.ensureWatchDogTimer_();822}823}824};825826827/**828* Starts a polling interval for changes to responseText of the829* XMLHttpRequest, for browsers that don't fire onreadystatechange830* as data comes in incrementally. This timer is disabled in831* cleanup_().832* @private833*/834goog.net.ChannelRequest.prototype.startPolling_ = function() {835this.eventHandler_.listen(836this.pollingTimer_, goog.Timer.TICK, this.pollResponse_);837this.pollingTimer_.start();838};839840841/**842* Returns the next chunk of a chunk-encoded response. This is not standard843* HTTP chunked encoding because browsers don't expose the chunk boundaries to844* the application through XMLHTTP. So we have an additional chunk encoding at845* the application level that lets us tell where the beginning and end of846* individual responses are so that we can only try to eval a complete JS array.847*848* The encoding is the size of the chunk encoded as a decimal string followed849* by a newline followed by the data.850*851* @param {string} responseText The response text from the XMLHTTP response.852* @return {string|Object} The next chunk string or a sentinel object853* indicating a special condition.854* @private855*/856goog.net.ChannelRequest.prototype.getNextChunk_ = function(responseText) {857var sizeStartIndex = this.xmlHttpChunkStart_;858var sizeEndIndex = responseText.indexOf('\n', sizeStartIndex);859if (sizeEndIndex == -1) {860return goog.net.ChannelRequest.INCOMPLETE_CHUNK_;861}862863var sizeAsString = responseText.substring(sizeStartIndex, sizeEndIndex);864var size = Number(sizeAsString);865if (isNaN(size)) {866return goog.net.ChannelRequest.INVALID_CHUNK_;867}868869var chunkStartIndex = sizeEndIndex + 1;870if (chunkStartIndex + size > responseText.length) {871return goog.net.ChannelRequest.INCOMPLETE_CHUNK_;872}873874var chunkText = responseText.substr(chunkStartIndex, size);875this.xmlHttpChunkStart_ = chunkStartIndex + size;876return chunkText;877};878879880/**881* Uses the Trident htmlfile ActiveX control to send a GET request in IE. This882* is the innovation discovered that lets us get intermediate results in883* Internet Explorer. Thanks to http://go/kev884* @param {goog.Uri} uri The uri to request from.885* @param {boolean} usingSecondaryDomain Whether to use a secondary domain.886*/887goog.net.ChannelRequest.prototype.tridentGet = function(888uri, usingSecondaryDomain) {889this.type_ = goog.net.ChannelRequest.Type_.TRIDENT;890this.baseUri_ = uri.clone().makeUnique();891this.tridentGet_(usingSecondaryDomain);892};893894895/**896* Starts the Trident request.897* @param {boolean} usingSecondaryDomain Whether to use a secondary domain.898* @private899*/900goog.net.ChannelRequest.prototype.tridentGet_ = function(usingSecondaryDomain) {901this.requestStartTime_ = goog.now();902this.ensureWatchDogTimer_();903904var hostname = usingSecondaryDomain ? window.location.hostname : '';905this.requestUri_ = this.baseUri_.clone();906this.requestUri_.setParameterValue('DOMAIN', hostname);907this.requestUri_.setParameterValue('t', this.retryId_);908909try {910this.trident_ = new ActiveXObject('htmlfile');911} catch (e) {912this.channelDebug_.severe('ActiveX blocked');913this.cleanup_();914915this.lastError_ = goog.net.ChannelRequest.Error.ACTIVE_X_BLOCKED;916/** @suppress {missingRequire} */917goog.net.BrowserChannel.notifyStatEvent(918/** @suppress {missingRequire} */919goog.net.BrowserChannel.Stat.ACTIVE_X_BLOCKED);920this.dispatchFailure_();921return;922}923924// Using goog.html.SafeHtml.create() might be viable here but since925// this code is now superseded by926// closure/labs/net/webchannel/channelrequest.js it's not worth risking927// the performance regressions and bugs that might result. Instead we928// do an unchecked conversion. Please be extra careful if modifying929// the HTML construction in this code, it's brittle and so it's easy to make930// mistakes.931932var body = '<html><body>';933if (usingSecondaryDomain) {934var escapedHostname =935goog.net.ChannelRequest.escapeForStringInScript_(hostname);936body += '<script>document.domain="' + escapedHostname + '"</scr' +937'ipt>';938}939body += '</body></html>';940var bodyHtml = goog.html.uncheckedconversions941.safeHtmlFromStringKnownToSatisfyTypeContract(942goog.string.Const.from('b/12014412'), body);943944this.trident_.open();945goog.dom.safe.documentWrite(946/** @type {!Document} */ (this.trident_), bodyHtml);947this.trident_.close();948949this.trident_.parentWindow['m'] = goog.bind(this.onTridentRpcMessage_, this);950this.trident_.parentWindow['d'] = goog.bind(this.onTridentDone_, this, true);951this.trident_.parentWindow['rpcClose'] =952goog.bind(this.onTridentDone_, this, false);953954var div = this.trident_.createElement(String(goog.dom.TagName.DIV));955this.trident_.parentWindow.document.body.appendChild(div);956957var safeUrl = goog.html.SafeUrl.sanitize(this.requestUri_.toString());958var sanitizedEscapedUrl =959goog.string.htmlEscape(goog.html.SafeUrl.unwrap(safeUrl));960var iframeHtml =961goog.html.uncheckedconversions962.safeHtmlFromStringKnownToSatisfyTypeContract(963goog.string.Const.from('b/12014412'),964'<iframe src="' + sanitizedEscapedUrl + '"></iframe>');965goog.dom.safe.setInnerHtml(div, iframeHtml);966967this.channelDebug_.tridentChannelRequest(968'GET', this.requestUri_, this.rid_, this.retryId_);969this.channel_.notifyServerReachabilityEvent(970/** @suppress {missingRequire} */971goog.net.BrowserChannel.ServerReachability.REQUEST_MADE);972};973974975/**976* JavaScript-escapes a string so that it can be included inside a JS string.977* Since the JS string is expected to be inside a <script>, HTML-escaping978* cannot be used and thus '<' and '>' are also JS-escaped.979* @param {string} string980* @return {string}981* @private982*/983goog.net.ChannelRequest.escapeForStringInScript_ = function(string) {984var escaped = '';985for (var i = 0; i < string.length; i++) {986var c = string.charAt(i);987if (c == '<') {988escaped += '\\x3c';989} else if (c == '>') {990escaped += '\\x3e';991} else {992// This will escape both " and '.993escaped += goog.string.escapeChar(c);994}995}996return escaped;997};9989991000/**1001* Callback from the Trident htmlfile ActiveX control for when a new message1002* is received.1003*1004* @param {string} msg The data payload.1005* @private1006*/1007goog.net.ChannelRequest.prototype.onTridentRpcMessage_ = function(msg) {1008// need to do async b/c this gets called off of the context of the ActiveX1009/** @suppress {missingRequire} */1010goog.net.BrowserChannel.setTimeout(1011goog.bind(this.onTridentRpcMessageAsync_, this, msg), 0);1012};101310141015/**1016* Callback from the Trident htmlfile ActiveX control for when a new message1017* is received.1018*1019* @param {string} msg The data payload.1020* @private1021*/1022goog.net.ChannelRequest.prototype.onTridentRpcMessageAsync_ = function(msg) {1023if (this.cancelled_) {1024return;1025}1026this.channelDebug_.tridentChannelResponseText(this.rid_, msg);1027this.cancelWatchDogTimer_();1028this.safeOnRequestData_(msg);1029this.ensureWatchDogTimer_();1030};103110321033/**1034* Callback from the Trident htmlfile ActiveX control for when the request1035* is complete1036*1037* @param {boolean} successful Whether the request successfully completed.1038* @private1039*/1040goog.net.ChannelRequest.prototype.onTridentDone_ = function(successful) {1041// need to do async b/c this gets called off of the context of the ActiveX1042/** @suppress {missingRequire} */1043goog.net.BrowserChannel.setTimeout(1044goog.bind(this.onTridentDoneAsync_, this, successful), 0);1045};104610471048/**1049* Callback from the Trident htmlfile ActiveX control for when the request1050* is complete1051*1052* @param {boolean} successful Whether the request successfully completed.1053* @private1054*/1055goog.net.ChannelRequest.prototype.onTridentDoneAsync_ = function(successful) {1056if (this.cancelled_) {1057return;1058}1059this.channelDebug_.tridentChannelResponseDone(this.rid_, successful);1060this.cleanup_();1061this.successful_ = successful;1062this.channel_.onRequestComplete(this);1063this.channel_.notifyServerReachabilityEvent(1064/** @suppress {missingRequire} */1065goog.net.BrowserChannel.ServerReachability.BACK_CHANNEL_ACTIVITY);1066};106710681069/**1070* Uses an IMG tag to send an HTTP get to the server. This is only currently1071* used to terminate the connection, as an IMG tag is the most reliable way to1072* send something to the server while the page is getting torn down.1073* @param {goog.Uri} uri The uri to send a request to.1074*/1075goog.net.ChannelRequest.prototype.sendUsingImgTag = function(uri) {1076this.type_ = goog.net.ChannelRequest.Type_.IMG;1077this.baseUri_ = uri.clone().makeUnique();1078this.imgTagGet_();1079};108010811082/**1083* Starts the IMG request.1084*1085* @private1086*/1087goog.net.ChannelRequest.prototype.imgTagGet_ = function() {1088var eltImg = new Image();1089eltImg.src = this.baseUri_;1090this.requestStartTime_ = goog.now();1091this.ensureWatchDogTimer_();1092};109310941095/**1096* Cancels the request no matter what the underlying transport is.1097*/1098goog.net.ChannelRequest.prototype.cancel = function() {1099this.cancelled_ = true;1100this.cleanup_();1101};110211031104/**1105* Ensures that there is watchdog timeout which is used to ensure that1106* the connection completes in time.1107*1108* @private1109*/1110goog.net.ChannelRequest.prototype.ensureWatchDogTimer_ = function() {1111this.watchDogTimeoutTime_ = goog.now() + this.timeout_;1112this.startWatchDogTimer_(this.timeout_);1113};111411151116/**1117* Starts the watchdog timer which is used to ensure that the connection1118* completes in time.1119* @param {number} time The number of milliseconds to wait.1120* @private1121* @suppress {missingRequire} goog.net.BrowserChannel1122*/1123goog.net.ChannelRequest.prototype.startWatchDogTimer_ = function(time) {1124if (this.watchDogTimerId_ != null) {1125// assertion1126throw Error('WatchDog timer not null');1127}1128/** @private @suppress {missingRequire} Circular dep. */1129this.watchDogTimerId_ = goog.net.BrowserChannel.setTimeout(1130goog.bind(this.onWatchDogTimeout_, this), time);1131};113211331134/**1135* Cancels the watchdog timer if it has been started.1136*1137* @private1138*/1139goog.net.ChannelRequest.prototype.cancelWatchDogTimer_ = function() {1140if (this.watchDogTimerId_) {1141goog.global.clearTimeout(this.watchDogTimerId_);1142this.watchDogTimerId_ = null;1143}1144};114511461147/**1148* Called when the watchdog timer is triggered. It also handles a case where it1149* is called too early which we suspect may be happening sometimes1150* (not sure why)1151*1152* @private1153*/1154goog.net.ChannelRequest.prototype.onWatchDogTimeout_ = function() {1155this.watchDogTimerId_ = null;1156var now = goog.now();1157if (now - this.watchDogTimeoutTime_ >= 0) {1158this.handleTimeout_();1159} else {1160// got called too early for some reason1161this.channelDebug_.warning('WatchDog timer called too early');1162this.startWatchDogTimer_(this.watchDogTimeoutTime_ - now);1163}1164};116511661167/**1168* Called when the request has actually timed out. Will cleanup and notify the1169* channel of the failure.1170*1171* @private1172*/1173goog.net.ChannelRequest.prototype.handleTimeout_ = function() {1174if (this.successful_) {1175// Should never happen.1176this.channelDebug_.severe(1177'Received watchdog timeout even though request loaded successfully');1178}11791180this.channelDebug_.timeoutResponse(this.requestUri_);1181// IMG requests never notice if they were successful, and always 'time out'.1182// This fact says nothing about reachability.1183if (this.type_ != goog.net.ChannelRequest.Type_.IMG) {1184this.channel_.notifyServerReachabilityEvent(1185/** @suppress {missingRequire} */1186goog.net.BrowserChannel.ServerReachability.REQUEST_FAILED);1187}1188this.cleanup_();11891190// set error and dispatch failure1191this.lastError_ = goog.net.ChannelRequest.Error.TIMEOUT;1192/** @suppress {missingRequire} */1193goog.net.BrowserChannel.notifyStatEvent(1194/** @suppress {missingRequire} */1195goog.net.BrowserChannel.Stat.REQUEST_TIMEOUT);1196this.dispatchFailure_();1197};119811991200/**1201* Notifies the channel that this request failed.1202* @private1203*/1204goog.net.ChannelRequest.prototype.dispatchFailure_ = function() {1205if (this.channel_.isClosed() || this.cancelled_) {1206return;1207}12081209this.channel_.onRequestComplete(this);1210};121112121213/**1214* Cleans up the objects used to make the request. This function is1215* idempotent.1216*1217* @private1218*/1219goog.net.ChannelRequest.prototype.cleanup_ = function() {1220this.cancelWatchDogTimer_();12211222goog.dispose(this.readyStateChangeThrottle_);1223this.readyStateChangeThrottle_ = null;12241225// Stop the polling timer, if necessary.1226this.pollingTimer_.stop();12271228// Unhook all event handlers.1229this.eventHandler_.removeAll();12301231if (this.xmlHttp_) {1232// clear out this.xmlHttp_ before aborting so we handle getting reentered1233// inside abort1234var xmlhttp = this.xmlHttp_;1235this.xmlHttp_ = null;1236xmlhttp.abort();1237xmlhttp.dispose();1238}12391240if (this.trident_) {1241this.trident_ = null;1242}1243};124412451246/**1247* Indicates whether the request was successful. Only valid after the handler1248* is called to indicate completion of the request.1249*1250* @return {boolean} True if the request succeeded.1251*/1252goog.net.ChannelRequest.prototype.getSuccess = function() {1253return this.successful_;1254};125512561257/**1258* If the request was not successful, returns the reason.1259*1260* @return {?goog.net.ChannelRequest.Error} The last error.1261*/1262goog.net.ChannelRequest.prototype.getLastError = function() {1263return this.lastError_;1264};126512661267/**1268* Returns the status code of the last request.1269* @return {number} The status code of the last request.1270*/1271goog.net.ChannelRequest.prototype.getLastStatusCode = function() {1272return this.lastStatusCode_;1273};127412751276/**1277* Returns the session id for this channel.1278*1279* @return {string|undefined} The session ID.1280*/1281goog.net.ChannelRequest.prototype.getSessionId = function() {1282return this.sid_;1283};128412851286/**1287* Returns the request id for this request. Each request has a unique request1288* id and the request IDs are a sequential increasing count.1289*1290* @return {string|number|undefined} The request ID.1291*/1292goog.net.ChannelRequest.prototype.getRequestId = function() {1293return this.rid_;1294};129512961297/**1298* Returns the data for a post, if this request is a post.1299*1300* @return {?string} The POST data provided by the request initiator.1301*/1302goog.net.ChannelRequest.prototype.getPostData = function() {1303return this.postData_;1304};130513061307/**1308* Returns the time that the request started, if it has started.1309*1310* @return {?number} The time the request started, as returned by goog.now().1311*/1312goog.net.ChannelRequest.prototype.getRequestStartTime = function() {1313return this.requestStartTime_;1314};131513161317/**1318* Helper to call the callback's onRequestData, which catches any1319* exception and cleans up the request.1320* @param {string} data The request data.1321* @private1322*/1323goog.net.ChannelRequest.prototype.safeOnRequestData_ = function(data) {13241325try {1326this.channel_.onRequestData(this, data);1327/** @suppress {missingRequire} goog.net.BrowserChannel */1328this.channel_.notifyServerReachabilityEvent(1329goog.net.BrowserChannel.ServerReachability.BACK_CHANNEL_ACTIVITY);1330} catch (e) {1331// Dump debug info, but keep going without closing the channel.1332this.channelDebug_.dumpException(e, 'Error in httprequest callback');1333}1334};133513361337