123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335 |
- // 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 ChannelRequest class. The ChannelRequest
- * object encapsulates the logic for making a single request, either for the
- * forward channel, back channel, or test channel, to the server. It contains
- * the logic for the three types of transports we use in the BrowserChannel:
- * XMLHTTP, Trident ActiveX (ie only), and Image request. It provides timeout
- * detection. This class is part of the BrowserChannel implementation and is not
- * for use by normal application code.
- *
- */
- goog.provide('goog.net.ChannelRequest');
- goog.provide('goog.net.ChannelRequest.Error');
- goog.require('goog.Timer');
- goog.require('goog.async.Throttle');
- goog.require('goog.dom.TagName');
- goog.require('goog.dom.safe');
- goog.require('goog.events.EventHandler');
- goog.require('goog.html.SafeUrl');
- goog.require('goog.html.uncheckedconversions');
- goog.require('goog.net.ErrorCode');
- goog.require('goog.net.EventType');
- goog.require('goog.net.XmlHttp');
- goog.require('goog.object');
- goog.require('goog.string');
- goog.require('goog.string.Const');
- goog.require('goog.userAgent');
- // TODO(nnaze): This file depends on goog.net.BrowserChannel and vice versa (a
- // circular dependency). Usages of BrowserChannel are marked as
- // "missingRequire" below for now. This should be fixed through refactoring.
- /**
- * Creates a ChannelRequest object which encapsulates a request to the server.
- * A new ChannelRequest is created for each request to the server.
- *
- * @param {goog.net.BrowserChannel|goog.net.BrowserTestChannel} channel
- * The BrowserChannel that owns this request.
- * @param {goog.net.ChannelDebug} channelDebug A ChannelDebug to use for
- * logging.
- * @param {string=} opt_sessionId The session id for the channel.
- * @param {string|number=} opt_requestId The request id for this request.
- * @param {number=} opt_retryId The retry id for this request.
- * @constructor
- */
- goog.net.ChannelRequest = function(
- channel, channelDebug, opt_sessionId, opt_requestId, opt_retryId) {
- /**
- * The BrowserChannel object that owns the request.
- * @type {goog.net.BrowserChannel|goog.net.BrowserTestChannel}
- * @private
- */
- this.channel_ = channel;
- /**
- * The channel debug to use for logging
- * @type {goog.net.ChannelDebug}
- * @private
- */
- this.channelDebug_ = channelDebug;
- /**
- * The Session ID for the channel.
- * @type {string|undefined}
- * @private
- */
- this.sid_ = opt_sessionId;
- /**
- * The RID (request ID) for the request.
- * @type {string|number|undefined}
- * @private
- */
- this.rid_ = opt_requestId;
- /**
- * The attempt number of the current request.
- * @type {number}
- * @private
- */
- this.retryId_ = opt_retryId || 1;
- /**
- * The timeout in ms before failing the request.
- * @type {number}
- * @private
- */
- this.timeout_ = goog.net.ChannelRequest.TIMEOUT_MS;
- /**
- * An object to keep track of the channel request event listeners.
- * @type {!goog.events.EventHandler<!goog.net.ChannelRequest>}
- * @private
- */
- this.eventHandler_ = new goog.events.EventHandler(this);
- /**
- * A timer for polling responseText in browsers that don't fire
- * onreadystatechange during incremental loading of responseText.
- * @type {goog.Timer}
- * @private
- */
- this.pollingTimer_ = new goog.Timer();
- this.pollingTimer_.setInterval(goog.net.ChannelRequest.POLLING_INTERVAL_MS);
- };
- /**
- * Extra HTTP headers to add to all the requests sent to the server.
- * @type {Object}
- * @private
- */
- goog.net.ChannelRequest.prototype.extraHeaders_ = null;
- /**
- * Whether the request was successful. This is only set to true after the
- * request successfuly completes.
- * @type {boolean}
- * @private
- */
- goog.net.ChannelRequest.prototype.successful_ = false;
- /**
- * The TimerID of the timer used to detect if the request has timed-out.
- * @type {?number}
- * @private
- */
- goog.net.ChannelRequest.prototype.watchDogTimerId_ = null;
- /**
- * The time in the future when the request will timeout.
- * @type {?number}
- * @private
- */
- goog.net.ChannelRequest.prototype.watchDogTimeoutTime_ = null;
- /**
- * The time the request started.
- * @type {?number}
- * @private
- */
- goog.net.ChannelRequest.prototype.requestStartTime_ = null;
- /**
- * The type of request (XMLHTTP, IMG, Trident)
- * @type {?number}
- * @private
- */
- goog.net.ChannelRequest.prototype.type_ = null;
- /**
- * The base Uri for the request. The includes all the parameters except the
- * one that indicates the retry number.
- * @type {goog.Uri?}
- * @private
- */
- goog.net.ChannelRequest.prototype.baseUri_ = null;
- /**
- * The request Uri that was actually used for the most recent request attempt.
- * @type {goog.Uri?}
- * @private
- */
- goog.net.ChannelRequest.prototype.requestUri_ = null;
- /**
- * The post data, if the request is a post.
- * @type {?string}
- * @private
- */
- goog.net.ChannelRequest.prototype.postData_ = null;
- /**
- * The XhrLte request if the request is using XMLHTTP
- * @type {goog.net.XhrIo}
- * @private
- */
- goog.net.ChannelRequest.prototype.xmlHttp_ = null;
- /**
- * The position of where the next unprocessed chunk starts in the response
- * text.
- * @type {number}
- * @private
- */
- goog.net.ChannelRequest.prototype.xmlHttpChunkStart_ = 0;
- /**
- * The Trident instance if the request is using Trident.
- * @type {Object}
- * @private
- */
- goog.net.ChannelRequest.prototype.trident_ = null;
- /**
- * The verb (Get or Post) for the request.
- * @type {?string}
- * @private
- */
- goog.net.ChannelRequest.prototype.verb_ = null;
- /**
- * The last error if the request failed.
- * @type {?goog.net.ChannelRequest.Error}
- * @private
- */
- goog.net.ChannelRequest.prototype.lastError_ = null;
- /**
- * The last status code received.
- * @type {number}
- * @private
- */
- goog.net.ChannelRequest.prototype.lastStatusCode_ = -1;
- /**
- * Whether to send the Connection:close header as part of the request.
- * @type {boolean}
- * @private
- */
- goog.net.ChannelRequest.prototype.sendClose_ = true;
- /**
- * Whether the request has been cancelled due to a call to cancel.
- * @type {boolean}
- * @private
- */
- goog.net.ChannelRequest.prototype.cancelled_ = false;
- /**
- * A throttle time in ms for readystatechange events for the backchannel.
- * Useful for throttling when ready state is INTERACTIVE (partial data).
- * If set to zero no throttle is used.
- *
- * @see goog.net.BrowserChannel.prototype.readyStateChangeThrottleMs_
- *
- * @type {number}
- * @private
- */
- goog.net.ChannelRequest.prototype.readyStateChangeThrottleMs_ = 0;
- /**
- * The throttle for readystatechange events for the current request, or null
- * if there is none.
- * @type {goog.async.Throttle}
- * @private
- */
- goog.net.ChannelRequest.prototype.readyStateChangeThrottle_ = null;
- /**
- * Default timeout in MS for a request. The server must return data within this
- * time limit for the request to not timeout.
- * @type {number}
- */
- goog.net.ChannelRequest.TIMEOUT_MS = 45 * 1000;
- /**
- * How often to poll (in MS) for changes to responseText in browsers that don't
- * fire onreadystatechange during incremental loading of responseText.
- * @type {number}
- */
- goog.net.ChannelRequest.POLLING_INTERVAL_MS = 250;
- /**
- * Minimum version of Safari that receives a non-null responseText in ready
- * state interactive.
- * @type {string}
- * @private
- */
- goog.net.ChannelRequest.MIN_WEBKIT_FOR_INTERACTIVE_ = '420+';
- /**
- * Enum for channel requests type
- * @enum {number}
- * @private
- */
- goog.net.ChannelRequest.Type_ = {
- /**
- * XMLHTTP requests.
- */
- XML_HTTP: 1,
- /**
- * IMG requests.
- */
- IMG: 2,
- /**
- * Requests that use the MSHTML ActiveX control.
- */
- TRIDENT: 3
- };
- /**
- * Enum type for identifying a ChannelRequest error.
- * @enum {number}
- */
- goog.net.ChannelRequest.Error = {
- /**
- * Errors due to a non-200 status code.
- */
- STATUS: 0,
- /**
- * Errors due to no data being returned.
- */
- NO_DATA: 1,
- /**
- * Errors due to a timeout.
- */
- TIMEOUT: 2,
- /**
- * Errors due to the server returning an unknown.
- */
- UNKNOWN_SESSION_ID: 3,
- /**
- * Errors due to bad data being received.
- */
- BAD_DATA: 4,
- /**
- * Errors due to the handler throwing an exception.
- */
- HANDLER_EXCEPTION: 5,
- /**
- * The browser declared itself offline during the request.
- */
- BROWSER_OFFLINE: 6,
- /**
- * IE is blocking ActiveX streaming.
- */
- ACTIVE_X_BLOCKED: 7
- };
- /**
- * Returns a useful error string for debugging based on the specified error
- * code.
- * @param {goog.net.ChannelRequest.Error} errorCode The error code.
- * @param {number} statusCode The HTTP status code.
- * @return {string} The error string for the given code combination.
- */
- goog.net.ChannelRequest.errorStringFromCode = function(errorCode, statusCode) {
- switch (errorCode) {
- case goog.net.ChannelRequest.Error.STATUS:
- return 'Non-200 return code (' + statusCode + ')';
- case goog.net.ChannelRequest.Error.NO_DATA:
- return 'XMLHTTP failure (no data)';
- case goog.net.ChannelRequest.Error.TIMEOUT:
- return 'HttpConnection timeout';
- default:
- return 'Unknown error';
- }
- };
- /**
- * Sentinel value used to indicate an invalid chunk in a multi-chunk response.
- * @type {Object}
- * @private
- */
- goog.net.ChannelRequest.INVALID_CHUNK_ = {};
- /**
- * Sentinel value used to indicate an incomplete chunk in a multi-chunk
- * response.
- * @type {Object}
- * @private
- */
- goog.net.ChannelRequest.INCOMPLETE_CHUNK_ = {};
- /**
- * Returns whether XHR streaming is supported on this browser.
- *
- * If XHR streaming is not supported, we will try to use an ActiveXObject
- * to create a Forever IFrame.
- *
- * @return {boolean} Whether XHR streaming is supported.
- * @see http://code.google.com/p/closure-library/issues/detail?id=346
- */
- goog.net.ChannelRequest.supportsXhrStreaming = function() {
- return !goog.userAgent.IE || goog.userAgent.isDocumentModeOrHigher(10);
- };
- /**
- * Sets extra HTTP headers to add to all the requests sent to the server.
- *
- * @param {Object} extraHeaders The HTTP headers.
- */
- goog.net.ChannelRequest.prototype.setExtraHeaders = function(extraHeaders) {
- this.extraHeaders_ = extraHeaders;
- };
- /**
- * Sets the timeout for a request
- *
- * @param {number} timeout The timeout in MS for when we fail the request.
- */
- goog.net.ChannelRequest.prototype.setTimeout = function(timeout) {
- this.timeout_ = timeout;
- };
- /**
- * Sets the throttle for handling onreadystatechange events for the request.
- *
- * @param {number} throttle The throttle in ms. A value of zero indicates
- * no throttle.
- */
- goog.net.ChannelRequest.prototype.setReadyStateChangeThrottle = function(
- throttle) {
- this.readyStateChangeThrottleMs_ = throttle;
- };
- /**
- * Uses XMLHTTP to send an HTTP POST to the server.
- *
- * @param {goog.Uri} uri The uri of the request.
- * @param {string} postData The data for the post body.
- * @param {boolean} decodeChunks Whether to the result is expected to be
- * encoded for chunking and thus requires decoding.
- */
- goog.net.ChannelRequest.prototype.xmlHttpPost = function(
- uri, postData, decodeChunks) {
- this.type_ = goog.net.ChannelRequest.Type_.XML_HTTP;
- this.baseUri_ = uri.clone().makeUnique();
- this.postData_ = postData;
- this.decodeChunks_ = decodeChunks;
- this.sendXmlHttp_(null /* hostPrefix */);
- };
- /**
- * Uses XMLHTTP to send an HTTP GET to the server.
- *
- * @param {goog.Uri} uri The uri of the request.
- * @param {boolean} decodeChunks Whether to the result is expected to be
- * encoded for chunking and thus requires decoding.
- * @param {?string} hostPrefix The host prefix, if we might be using a
- * secondary domain. Note that it should also be in the URL, adding this
- * won't cause it to be added to the URL.
- * @param {boolean=} opt_noClose Whether to request that the tcp/ip connection
- * should be closed.
- */
- goog.net.ChannelRequest.prototype.xmlHttpGet = function(
- uri, decodeChunks, hostPrefix, opt_noClose) {
- this.type_ = goog.net.ChannelRequest.Type_.XML_HTTP;
- this.baseUri_ = uri.clone().makeUnique();
- this.postData_ = null;
- this.decodeChunks_ = decodeChunks;
- if (opt_noClose) {
- this.sendClose_ = false;
- }
- this.sendXmlHttp_(hostPrefix);
- };
- /**
- * Sends a request via XMLHTTP according to the current state of the
- * ChannelRequest object.
- *
- * @param {?string} hostPrefix The host prefix, if we might be using a secondary
- * domain.
- * @private
- */
- goog.net.ChannelRequest.prototype.sendXmlHttp_ = function(hostPrefix) {
- this.requestStartTime_ = goog.now();
- this.ensureWatchDogTimer_();
- // clone the base URI to create the request URI. The request uri has the
- // attempt number as a parameter which helps in debugging.
- this.requestUri_ = this.baseUri_.clone();
- this.requestUri_.setParameterValues('t', this.retryId_);
- // send the request either as a POST or GET
- this.xmlHttpChunkStart_ = 0;
- var useSecondaryDomains = this.channel_.shouldUseSecondaryDomains();
- this.xmlHttp_ =
- this.channel_.createXhrIo(useSecondaryDomains ? hostPrefix : null);
- if (this.readyStateChangeThrottleMs_ > 0) {
- this.readyStateChangeThrottle_ = new goog.async.Throttle(
- goog.bind(this.xmlHttpHandler_, this, this.xmlHttp_),
- this.readyStateChangeThrottleMs_);
- }
- this.eventHandler_.listen(
- this.xmlHttp_, goog.net.EventType.READY_STATE_CHANGE,
- this.readyStateChangeHandler_);
- var headers = this.extraHeaders_ ? goog.object.clone(this.extraHeaders_) : {};
- if (this.postData_) {
- // todo (jonp) - use POST constant when Dan defines it
- this.verb_ = 'POST';
- headers['Content-Type'] = 'application/x-www-form-urlencoded';
- this.xmlHttp_.send(this.requestUri_, this.verb_, this.postData_, headers);
- } else {
- // todo (jonp) - use GET constant when Dan defines it
- this.verb_ = 'GET';
- // If the user agent is webkit, we cannot send the close header since it is
- // disallowed by the browser. If we attempt to set the "Connection: close"
- // header in WEBKIT browser, it will actually causes an error message.
- if (this.sendClose_ && !goog.userAgent.WEBKIT) {
- headers['Connection'] = 'close';
- }
- this.xmlHttp_.send(this.requestUri_, this.verb_, null, headers);
- }
- this.channel_.notifyServerReachabilityEvent(
- /** @suppress {missingRequire} */ (
- goog.net.BrowserChannel.ServerReachability.REQUEST_MADE));
- this.channelDebug_.xmlHttpChannelRequest(
- this.verb_, this.requestUri_, this.rid_, this.retryId_, this.postData_);
- };
- /**
- * Handles a readystatechange event.
- * @param {goog.events.Event} evt The event.
- * @private
- */
- goog.net.ChannelRequest.prototype.readyStateChangeHandler_ = function(evt) {
- var xhr = /** @type {goog.net.XhrIo} */ (evt.target);
- var throttle = this.readyStateChangeThrottle_;
- if (throttle &&
- xhr.getReadyState() == goog.net.XmlHttp.ReadyState.INTERACTIVE) {
- // Only throttle in the partial data case.
- this.channelDebug_.debug('Throttling readystatechange.');
- throttle.fire();
- } else {
- // If we haven't throttled, just handle response directly.
- this.xmlHttpHandler_(xhr);
- }
- };
- /**
- * XmlHttp handler
- * @param {goog.net.XhrIo} xmlhttp The XhrIo object for the current request.
- * @private
- */
- goog.net.ChannelRequest.prototype.xmlHttpHandler_ = function(xmlhttp) {
- /** @suppress {missingRequire} */
- goog.net.BrowserChannel.onStartExecution();
- try {
- if (xmlhttp == this.xmlHttp_) {
- this.onXmlHttpReadyStateChanged_();
- } else {
- this.channelDebug_.warning(
- 'Called back with an ' +
- 'unexpected xmlhttp');
- }
- } catch (ex) {
- this.channelDebug_.debug('Failed call to OnXmlHttpReadyStateChanged_');
- if (this.xmlHttp_ && this.xmlHttp_.getResponseText()) {
- this.channelDebug_.dumpException(
- ex, 'ResponseText: ' + this.xmlHttp_.getResponseText());
- } else {
- this.channelDebug_.dumpException(ex, 'No response text');
- }
- } finally {
- /** @suppress {missingRequire} */
- goog.net.BrowserChannel.onEndExecution();
- }
- };
- /**
- * Called by the readystate handler for XMLHTTP requests.
- *
- * @private
- */
- goog.net.ChannelRequest.prototype.onXmlHttpReadyStateChanged_ = function() {
- var readyState = this.xmlHttp_.getReadyState();
- var errorCode = this.xmlHttp_.getLastErrorCode();
- var statusCode = this.xmlHttp_.getStatus();
- // If it is Safari less than 420+, there is a bug that causes null to be
- // in the responseText on ready state interactive so we must wait for
- // ready state complete.
- if (!goog.net.ChannelRequest.supportsXhrStreaming() ||
- (goog.userAgent.WEBKIT &&
- !goog.userAgent.isVersionOrHigher(
- goog.net.ChannelRequest.MIN_WEBKIT_FOR_INTERACTIVE_))) {
- if (readyState < goog.net.XmlHttp.ReadyState.COMPLETE) {
- // not yet ready
- return;
- }
- } else {
- // we get partial results in browsers that support ready state interactive.
- // We also make sure that getResponseText is not null in interactive mode
- // before we continue. However, we don't do it in Opera because it only
- // fire readyState == INTERACTIVE once. We need the following code to poll
- if (readyState < goog.net.XmlHttp.ReadyState.INTERACTIVE ||
- readyState == goog.net.XmlHttp.ReadyState.INTERACTIVE &&
- !goog.userAgent.OPERA && !this.xmlHttp_.getResponseText()) {
- // not yet ready
- return;
- }
- }
- // Dispatch any appropriate network events.
- if (!this.cancelled_ && readyState == goog.net.XmlHttp.ReadyState.COMPLETE &&
- errorCode != goog.net.ErrorCode.ABORT) {
- // Pretty conservative, these are the only known scenarios which we'd
- // consider indicative of a truly non-functional network connection.
- if (errorCode == goog.net.ErrorCode.TIMEOUT || statusCode <= 0) {
- this.channel_.notifyServerReachabilityEvent(
- /** @suppress {missingRequire} */
- goog.net.BrowserChannel.ServerReachability.REQUEST_FAILED);
- } else {
- this.channel_.notifyServerReachabilityEvent(
- /** @suppress {missingRequire} */
- goog.net.BrowserChannel.ServerReachability.REQUEST_SUCCEEDED);
- }
- }
- // got some data so cancel the watchdog timer
- this.cancelWatchDogTimer_();
- var status = this.xmlHttp_.getStatus();
- this.lastStatusCode_ = status;
- var responseText = this.xmlHttp_.getResponseText();
- if (!responseText) {
- this.channelDebug_.debug(
- 'No response text for uri ' + this.requestUri_ + ' status ' + status);
- }
- this.successful_ = (status == 200);
- this.channelDebug_.xmlHttpChannelResponseMetaData(
- /** @type {string} */ (this.verb_), this.requestUri_, this.rid_,
- this.retryId_, readyState, status);
- if (!this.successful_) {
- if (status == 400 && responseText.indexOf('Unknown SID') > 0) {
- // the server error string will include 'Unknown SID' which indicates the
- // server doesn't know about the session (maybe it got restarted, maybe
- // the user got moved to another server, etc.,). Handlers can special
- // case this error
- this.lastError_ = goog.net.ChannelRequest.Error.UNKNOWN_SESSION_ID;
- /** @suppress {missingRequire} */
- goog.net.BrowserChannel.notifyStatEvent(
- /** @suppress {missingRequire} */
- goog.net.BrowserChannel.Stat.REQUEST_UNKNOWN_SESSION_ID);
- this.channelDebug_.warning('XMLHTTP Unknown SID (' + this.rid_ + ')');
- } else {
- this.lastError_ = goog.net.ChannelRequest.Error.STATUS;
- /** @suppress {missingRequire} */
- goog.net.BrowserChannel.notifyStatEvent(
- /** @suppress {missingRequire} */
- goog.net.BrowserChannel.Stat.REQUEST_BAD_STATUS);
- this.channelDebug_.warning(
- 'XMLHTTP Bad status ' + status + ' (' + this.rid_ + ')');
- }
- this.cleanup_();
- this.dispatchFailure_();
- return;
- }
- if (readyState == goog.net.XmlHttp.ReadyState.COMPLETE) {
- this.cleanup_();
- }
- if (this.decodeChunks_) {
- this.decodeNextChunks_(readyState, responseText);
- if (goog.userAgent.OPERA && this.successful_ &&
- readyState == goog.net.XmlHttp.ReadyState.INTERACTIVE) {
- this.startPolling_();
- }
- } else {
- this.channelDebug_.xmlHttpChannelResponseText(
- this.rid_, responseText, null);
- this.safeOnRequestData_(responseText);
- }
- if (!this.successful_) {
- return;
- }
- if (!this.cancelled_) {
- if (readyState == goog.net.XmlHttp.ReadyState.COMPLETE) {
- this.channel_.onRequestComplete(this);
- } else {
- // The default is false, the result from this callback shouldn't carry
- // over to the next callback, otherwise the request looks successful if
- // the watchdog timer gets called
- this.successful_ = false;
- this.ensureWatchDogTimer_();
- }
- }
- };
- /**
- * Decodes the next set of available chunks in the response.
- * @param {number} readyState The value of readyState.
- * @param {string} responseText The value of responseText.
- * @private
- */
- goog.net.ChannelRequest.prototype.decodeNextChunks_ = function(
- readyState, responseText) {
- var decodeNextChunksSuccessful = true;
- while (!this.cancelled_ && this.xmlHttpChunkStart_ < responseText.length) {
- var chunkText = this.getNextChunk_(responseText);
- if (chunkText == goog.net.ChannelRequest.INCOMPLETE_CHUNK_) {
- if (readyState == goog.net.XmlHttp.ReadyState.COMPLETE) {
- // should have consumed entire response when the request is done
- this.lastError_ = goog.net.ChannelRequest.Error.BAD_DATA;
- /** @suppress {missingRequire} */
- goog.net.BrowserChannel.notifyStatEvent(
- /** @suppress {missingRequire} */
- goog.net.BrowserChannel.Stat.REQUEST_INCOMPLETE_DATA);
- decodeNextChunksSuccessful = false;
- }
- this.channelDebug_.xmlHttpChannelResponseText(
- this.rid_, null, '[Incomplete Response]');
- break;
- } else if (chunkText == goog.net.ChannelRequest.INVALID_CHUNK_) {
- this.lastError_ = goog.net.ChannelRequest.Error.BAD_DATA;
- /** @suppress {missingRequire} */
- goog.net.BrowserChannel.notifyStatEvent(
- /** @suppress {missingRequire} */
- goog.net.BrowserChannel.Stat.REQUEST_BAD_DATA);
- this.channelDebug_.xmlHttpChannelResponseText(
- this.rid_, responseText, '[Invalid Chunk]');
- decodeNextChunksSuccessful = false;
- break;
- } else {
- this.channelDebug_.xmlHttpChannelResponseText(
- this.rid_, /** @type {string} */ (chunkText), null);
- this.safeOnRequestData_(/** @type {string} */ (chunkText));
- }
- }
- if (readyState == goog.net.XmlHttp.ReadyState.COMPLETE &&
- responseText.length == 0) {
- // also an error if we didn't get any response
- this.lastError_ = goog.net.ChannelRequest.Error.NO_DATA;
- /** @suppress {missingRequire} */
- goog.net.BrowserChannel.notifyStatEvent(
- /** @suppress {missingRequire} */
- goog.net.BrowserChannel.Stat.REQUEST_NO_DATA);
- decodeNextChunksSuccessful = false;
- }
- this.successful_ = this.successful_ && decodeNextChunksSuccessful;
- if (!decodeNextChunksSuccessful) {
- // malformed response - we make this trigger retry logic
- this.channelDebug_.xmlHttpChannelResponseText(
- this.rid_, responseText, '[Invalid Chunked Response]');
- this.cleanup_();
- this.dispatchFailure_();
- }
- };
- /**
- * Polls the response for new data.
- * @private
- */
- goog.net.ChannelRequest.prototype.pollResponse_ = function() {
- var readyState = this.xmlHttp_.getReadyState();
- var responseText = this.xmlHttp_.getResponseText();
- if (this.xmlHttpChunkStart_ < responseText.length) {
- this.cancelWatchDogTimer_();
- this.decodeNextChunks_(readyState, responseText);
- if (this.successful_ &&
- readyState != goog.net.XmlHttp.ReadyState.COMPLETE) {
- this.ensureWatchDogTimer_();
- }
- }
- };
- /**
- * Starts a polling interval for changes to responseText of the
- * XMLHttpRequest, for browsers that don't fire onreadystatechange
- * as data comes in incrementally. This timer is disabled in
- * cleanup_().
- * @private
- */
- goog.net.ChannelRequest.prototype.startPolling_ = function() {
- this.eventHandler_.listen(
- this.pollingTimer_, goog.Timer.TICK, this.pollResponse_);
- this.pollingTimer_.start();
- };
- /**
- * Returns the next chunk of a chunk-encoded response. This is not standard
- * HTTP chunked encoding because browsers don't expose the chunk boundaries to
- * the application through XMLHTTP. So we have an additional chunk encoding at
- * the application level that lets us tell where the beginning and end of
- * individual responses are so that we can only try to eval a complete JS array.
- *
- * The encoding is the size of the chunk encoded as a decimal string followed
- * by a newline followed by the data.
- *
- * @param {string} responseText The response text from the XMLHTTP response.
- * @return {string|Object} The next chunk string or a sentinel object
- * indicating a special condition.
- * @private
- */
- goog.net.ChannelRequest.prototype.getNextChunk_ = function(responseText) {
- var sizeStartIndex = this.xmlHttpChunkStart_;
- var sizeEndIndex = responseText.indexOf('\n', sizeStartIndex);
- if (sizeEndIndex == -1) {
- return goog.net.ChannelRequest.INCOMPLETE_CHUNK_;
- }
- var sizeAsString = responseText.substring(sizeStartIndex, sizeEndIndex);
- var size = Number(sizeAsString);
- if (isNaN(size)) {
- return goog.net.ChannelRequest.INVALID_CHUNK_;
- }
- var chunkStartIndex = sizeEndIndex + 1;
- if (chunkStartIndex + size > responseText.length) {
- return goog.net.ChannelRequest.INCOMPLETE_CHUNK_;
- }
- var chunkText = responseText.substr(chunkStartIndex, size);
- this.xmlHttpChunkStart_ = chunkStartIndex + size;
- return chunkText;
- };
- /**
- * Uses the Trident htmlfile ActiveX control to send a GET request in IE. This
- * is the innovation discovered that lets us get intermediate results in
- * Internet Explorer. Thanks to http://go/kev
- * @param {goog.Uri} uri The uri to request from.
- * @param {boolean} usingSecondaryDomain Whether to use a secondary domain.
- */
- goog.net.ChannelRequest.prototype.tridentGet = function(
- uri, usingSecondaryDomain) {
- this.type_ = goog.net.ChannelRequest.Type_.TRIDENT;
- this.baseUri_ = uri.clone().makeUnique();
- this.tridentGet_(usingSecondaryDomain);
- };
- /**
- * Starts the Trident request.
- * @param {boolean} usingSecondaryDomain Whether to use a secondary domain.
- * @private
- */
- goog.net.ChannelRequest.prototype.tridentGet_ = function(usingSecondaryDomain) {
- this.requestStartTime_ = goog.now();
- this.ensureWatchDogTimer_();
- var hostname = usingSecondaryDomain ? window.location.hostname : '';
- this.requestUri_ = this.baseUri_.clone();
- this.requestUri_.setParameterValue('DOMAIN', hostname);
- this.requestUri_.setParameterValue('t', this.retryId_);
- try {
- this.trident_ = new ActiveXObject('htmlfile');
- } catch (e) {
- this.channelDebug_.severe('ActiveX blocked');
- this.cleanup_();
- this.lastError_ = goog.net.ChannelRequest.Error.ACTIVE_X_BLOCKED;
- /** @suppress {missingRequire} */
- goog.net.BrowserChannel.notifyStatEvent(
- /** @suppress {missingRequire} */
- goog.net.BrowserChannel.Stat.ACTIVE_X_BLOCKED);
- this.dispatchFailure_();
- return;
- }
- // Using goog.html.SafeHtml.create() might be viable here but since
- // this code is now superseded by
- // closure/labs/net/webchannel/channelrequest.js it's not worth risking
- // the performance regressions and bugs that might result. Instead we
- // do an unchecked conversion. Please be extra careful if modifying
- // the HTML construction in this code, it's brittle and so it's easy to make
- // mistakes.
- var body = '<html><body>';
- if (usingSecondaryDomain) {
- var escapedHostname =
- goog.net.ChannelRequest.escapeForStringInScript_(hostname);
- body += '<script>document.domain="' + escapedHostname + '"</scr' +
- 'ipt>';
- }
- body += '</body></html>';
- var bodyHtml = goog.html.uncheckedconversions
- .safeHtmlFromStringKnownToSatisfyTypeContract(
- goog.string.Const.from('b/12014412'), body);
- this.trident_.open();
- goog.dom.safe.documentWrite(
- /** @type {!Document} */ (this.trident_), bodyHtml);
- this.trident_.close();
- this.trident_.parentWindow['m'] = goog.bind(this.onTridentRpcMessage_, this);
- this.trident_.parentWindow['d'] = goog.bind(this.onTridentDone_, this, true);
- this.trident_.parentWindow['rpcClose'] =
- goog.bind(this.onTridentDone_, this, false);
- var div = this.trident_.createElement(String(goog.dom.TagName.DIV));
- this.trident_.parentWindow.document.body.appendChild(div);
- var safeUrl = goog.html.SafeUrl.sanitize(this.requestUri_.toString());
- var sanitizedEscapedUrl =
- goog.string.htmlEscape(goog.html.SafeUrl.unwrap(safeUrl));
- var iframeHtml =
- goog.html.uncheckedconversions
- .safeHtmlFromStringKnownToSatisfyTypeContract(
- goog.string.Const.from('b/12014412'),
- '<iframe src="' + sanitizedEscapedUrl + '"></iframe>');
- goog.dom.safe.setInnerHtml(div, iframeHtml);
- this.channelDebug_.tridentChannelRequest(
- 'GET', this.requestUri_, this.rid_, this.retryId_);
- this.channel_.notifyServerReachabilityEvent(
- /** @suppress {missingRequire} */
- goog.net.BrowserChannel.ServerReachability.REQUEST_MADE);
- };
- /**
- * JavaScript-escapes a string so that it can be included inside a JS string.
- * Since the JS string is expected to be inside a <script>, HTML-escaping
- * cannot be used and thus '<' and '>' are also JS-escaped.
- * @param {string} string
- * @return {string}
- * @private
- */
- goog.net.ChannelRequest.escapeForStringInScript_ = function(string) {
- var escaped = '';
- for (var i = 0; i < string.length; i++) {
- var c = string.charAt(i);
- if (c == '<') {
- escaped += '\\x3c';
- } else if (c == '>') {
- escaped += '\\x3e';
- } else {
- // This will escape both " and '.
- escaped += goog.string.escapeChar(c);
- }
- }
- return escaped;
- };
- /**
- * Callback from the Trident htmlfile ActiveX control for when a new message
- * is received.
- *
- * @param {string} msg The data payload.
- * @private
- */
- goog.net.ChannelRequest.prototype.onTridentRpcMessage_ = function(msg) {
- // need to do async b/c this gets called off of the context of the ActiveX
- /** @suppress {missingRequire} */
- goog.net.BrowserChannel.setTimeout(
- goog.bind(this.onTridentRpcMessageAsync_, this, msg), 0);
- };
- /**
- * Callback from the Trident htmlfile ActiveX control for when a new message
- * is received.
- *
- * @param {string} msg The data payload.
- * @private
- */
- goog.net.ChannelRequest.prototype.onTridentRpcMessageAsync_ = function(msg) {
- if (this.cancelled_) {
- return;
- }
- this.channelDebug_.tridentChannelResponseText(this.rid_, msg);
- this.cancelWatchDogTimer_();
- this.safeOnRequestData_(msg);
- this.ensureWatchDogTimer_();
- };
- /**
- * Callback from the Trident htmlfile ActiveX control for when the request
- * is complete
- *
- * @param {boolean} successful Whether the request successfully completed.
- * @private
- */
- goog.net.ChannelRequest.prototype.onTridentDone_ = function(successful) {
- // need to do async b/c this gets called off of the context of the ActiveX
- /** @suppress {missingRequire} */
- goog.net.BrowserChannel.setTimeout(
- goog.bind(this.onTridentDoneAsync_, this, successful), 0);
- };
- /**
- * Callback from the Trident htmlfile ActiveX control for when the request
- * is complete
- *
- * @param {boolean} successful Whether the request successfully completed.
- * @private
- */
- goog.net.ChannelRequest.prototype.onTridentDoneAsync_ = function(successful) {
- if (this.cancelled_) {
- return;
- }
- this.channelDebug_.tridentChannelResponseDone(this.rid_, successful);
- this.cleanup_();
- this.successful_ = successful;
- this.channel_.onRequestComplete(this);
- this.channel_.notifyServerReachabilityEvent(
- /** @suppress {missingRequire} */
- goog.net.BrowserChannel.ServerReachability.BACK_CHANNEL_ACTIVITY);
- };
- /**
- * Uses an IMG tag to send an HTTP get to the server. This is only currently
- * used to terminate the connection, as an IMG tag is the most reliable way to
- * send something to the server while the page is getting torn down.
- * @param {goog.Uri} uri The uri to send a request to.
- */
- goog.net.ChannelRequest.prototype.sendUsingImgTag = function(uri) {
- this.type_ = goog.net.ChannelRequest.Type_.IMG;
- this.baseUri_ = uri.clone().makeUnique();
- this.imgTagGet_();
- };
- /**
- * Starts the IMG request.
- *
- * @private
- */
- goog.net.ChannelRequest.prototype.imgTagGet_ = function() {
- var eltImg = new Image();
- eltImg.src = this.baseUri_;
- this.requestStartTime_ = goog.now();
- this.ensureWatchDogTimer_();
- };
- /**
- * Cancels the request no matter what the underlying transport is.
- */
- goog.net.ChannelRequest.prototype.cancel = function() {
- this.cancelled_ = true;
- this.cleanup_();
- };
- /**
- * Ensures that there is watchdog timeout which is used to ensure that
- * the connection completes in time.
- *
- * @private
- */
- goog.net.ChannelRequest.prototype.ensureWatchDogTimer_ = function() {
- this.watchDogTimeoutTime_ = goog.now() + this.timeout_;
- this.startWatchDogTimer_(this.timeout_);
- };
- /**
- * Starts the watchdog timer which is used to ensure that the connection
- * completes in time.
- * @param {number} time The number of milliseconds to wait.
- * @private
- * @suppress {missingRequire} goog.net.BrowserChannel
- */
- goog.net.ChannelRequest.prototype.startWatchDogTimer_ = function(time) {
- if (this.watchDogTimerId_ != null) {
- // assertion
- throw Error('WatchDog timer not null');
- }
- /** @private @suppress {missingRequire} Circular dep. */
- this.watchDogTimerId_ = goog.net.BrowserChannel.setTimeout(
- goog.bind(this.onWatchDogTimeout_, this), time);
- };
- /**
- * Cancels the watchdog timer if it has been started.
- *
- * @private
- */
- goog.net.ChannelRequest.prototype.cancelWatchDogTimer_ = function() {
- if (this.watchDogTimerId_) {
- goog.global.clearTimeout(this.watchDogTimerId_);
- this.watchDogTimerId_ = null;
- }
- };
- /**
- * Called when the watchdog timer is triggered. It also handles a case where it
- * is called too early which we suspect may be happening sometimes
- * (not sure why)
- *
- * @private
- */
- goog.net.ChannelRequest.prototype.onWatchDogTimeout_ = function() {
- this.watchDogTimerId_ = null;
- var now = goog.now();
- if (now - this.watchDogTimeoutTime_ >= 0) {
- this.handleTimeout_();
- } else {
- // got called too early for some reason
- this.channelDebug_.warning('WatchDog timer called too early');
- this.startWatchDogTimer_(this.watchDogTimeoutTime_ - now);
- }
- };
- /**
- * Called when the request has actually timed out. Will cleanup and notify the
- * channel of the failure.
- *
- * @private
- */
- goog.net.ChannelRequest.prototype.handleTimeout_ = function() {
- if (this.successful_) {
- // Should never happen.
- this.channelDebug_.severe(
- 'Received watchdog timeout even though request loaded successfully');
- }
- this.channelDebug_.timeoutResponse(this.requestUri_);
- // IMG requests never notice if they were successful, and always 'time out'.
- // This fact says nothing about reachability.
- if (this.type_ != goog.net.ChannelRequest.Type_.IMG) {
- this.channel_.notifyServerReachabilityEvent(
- /** @suppress {missingRequire} */
- goog.net.BrowserChannel.ServerReachability.REQUEST_FAILED);
- }
- this.cleanup_();
- // set error and dispatch failure
- this.lastError_ = goog.net.ChannelRequest.Error.TIMEOUT;
- /** @suppress {missingRequire} */
- goog.net.BrowserChannel.notifyStatEvent(
- /** @suppress {missingRequire} */
- goog.net.BrowserChannel.Stat.REQUEST_TIMEOUT);
- this.dispatchFailure_();
- };
- /**
- * Notifies the channel that this request failed.
- * @private
- */
- goog.net.ChannelRequest.prototype.dispatchFailure_ = function() {
- if (this.channel_.isClosed() || this.cancelled_) {
- return;
- }
- this.channel_.onRequestComplete(this);
- };
- /**
- * Cleans up the objects used to make the request. This function is
- * idempotent.
- *
- * @private
- */
- goog.net.ChannelRequest.prototype.cleanup_ = function() {
- this.cancelWatchDogTimer_();
- goog.dispose(this.readyStateChangeThrottle_);
- this.readyStateChangeThrottle_ = null;
- // Stop the polling timer, if necessary.
- this.pollingTimer_.stop();
- // Unhook all event handlers.
- this.eventHandler_.removeAll();
- if (this.xmlHttp_) {
- // clear out this.xmlHttp_ before aborting so we handle getting reentered
- // inside abort
- var xmlhttp = this.xmlHttp_;
- this.xmlHttp_ = null;
- xmlhttp.abort();
- xmlhttp.dispose();
- }
- if (this.trident_) {
- this.trident_ = null;
- }
- };
- /**
- * Indicates whether the request was successful. Only valid after the handler
- * is called to indicate completion of the request.
- *
- * @return {boolean} True if the request succeeded.
- */
- goog.net.ChannelRequest.prototype.getSuccess = function() {
- return this.successful_;
- };
- /**
- * If the request was not successful, returns the reason.
- *
- * @return {?goog.net.ChannelRequest.Error} The last error.
- */
- goog.net.ChannelRequest.prototype.getLastError = function() {
- return this.lastError_;
- };
- /**
- * Returns the status code of the last request.
- * @return {number} The status code of the last request.
- */
- goog.net.ChannelRequest.prototype.getLastStatusCode = function() {
- return this.lastStatusCode_;
- };
- /**
- * Returns the session id for this channel.
- *
- * @return {string|undefined} The session ID.
- */
- goog.net.ChannelRequest.prototype.getSessionId = function() {
- return this.sid_;
- };
- /**
- * Returns the request id for this request. Each request has a unique request
- * id and the request IDs are a sequential increasing count.
- *
- * @return {string|number|undefined} The request ID.
- */
- goog.net.ChannelRequest.prototype.getRequestId = function() {
- return this.rid_;
- };
- /**
- * Returns the data for a post, if this request is a post.
- *
- * @return {?string} The POST data provided by the request initiator.
- */
- goog.net.ChannelRequest.prototype.getPostData = function() {
- return this.postData_;
- };
- /**
- * Returns the time that the request started, if it has started.
- *
- * @return {?number} The time the request started, as returned by goog.now().
- */
- goog.net.ChannelRequest.prototype.getRequestStartTime = function() {
- return this.requestStartTime_;
- };
- /**
- * Helper to call the callback's onRequestData, which catches any
- * exception and cleans up the request.
- * @param {string} data The request data.
- * @private
- */
- goog.net.ChannelRequest.prototype.safeOnRequestData_ = function(data) {
- try {
- this.channel_.onRequestData(this, data);
- /** @suppress {missingRequire} goog.net.BrowserChannel */
- this.channel_.notifyServerReachabilityEvent(
- goog.net.BrowserChannel.ServerReachability.BACK_CHANNEL_ACTIVITY);
- } catch (e) {
- // Dump debug info, but keep going without closing the channel.
- this.channelDebug_.dumpException(e, 'Error in httprequest callback');
- }
- };
|