Path: blob/trunk/third_party/closure/goog/net/xhrmanager.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 Manages a pool of XhrIo's. This handles all the details of16* dealing with the XhrPool and provides a simple interface for sending requests17* and managing events.18*19* This class supports queueing & prioritization of requests (XhrIoPool20* handles this) and retrying of requests.21*22* The events fired by the XhrManager are an aggregation of the events of23* each of its XhrIo objects (with some filtering, i.e., ERROR only called24* when there are no more retries left). For this reason, all send requests have25* to have an id, so that the user of this object can know which event is for26* which request.27*28*/2930goog.provide('goog.net.XhrManager');31goog.provide('goog.net.XhrManager.Event');32goog.provide('goog.net.XhrManager.Request');3334goog.require('goog.events');35goog.require('goog.events.Event');36goog.require('goog.events.EventHandler');37goog.require('goog.events.EventTarget');38goog.require('goog.net.ErrorCode');39goog.require('goog.net.EventType');40goog.require('goog.net.XhrIo');41goog.require('goog.net.XhrIoPool');42goog.require('goog.structs.Map');4344// TODO(user): Add some time in between retries.45464748/**49* A manager of an XhrIoPool.50* @param {number=} opt_maxRetries Max. number of retries (Default: 1).51* @param {goog.structs.Map=} opt_headers Map of default headers to add to every52* request.53* @param {number=} opt_minCount Min. number of objects (Default: 0).54* @param {number=} opt_maxCount Max. number of objects (Default: 10).55* @param {number=} opt_timeoutInterval Timeout (in ms) before aborting an56* attempt (Default: 0ms).57* @param {boolean=} opt_withCredentials Add credentials to every request58* (Default: false).59* @constructor60* @extends {goog.events.EventTarget}61*/62goog.net.XhrManager = function(63opt_maxRetries, opt_headers, opt_minCount, opt_maxCount,64opt_timeoutInterval, opt_withCredentials) {65goog.net.XhrManager.base(this, 'constructor');6667/**68* Maximum number of retries for a given request69* @type {number}70* @private71*/72this.maxRetries_ = goog.isDef(opt_maxRetries) ? opt_maxRetries : 1;7374/**75* Timeout interval for an attempt of a given request.76* @type {number}77* @private78*/79this.timeoutInterval_ =80goog.isDef(opt_timeoutInterval) ? Math.max(0, opt_timeoutInterval) : 0;8182/**83* Add credentials to every request.84* @private {boolean}85*/86this.withCredentials_ = !!opt_withCredentials;8788/**89* The pool of XhrIo's to use.90* @type {goog.net.XhrIoPool}91* @private92*/93this.xhrPool_ = new goog.net.XhrIoPool(94opt_headers, opt_minCount, opt_maxCount, opt_withCredentials);9596/**97* Map of ID's to requests.98* @type {goog.structs.Map<string, !goog.net.XhrManager.Request>}99* @private100*/101this.requests_ = new goog.structs.Map();102103/**104* The event handler.105* @type {goog.events.EventHandler<!goog.net.XhrManager>}106* @private107*/108this.eventHandler_ = new goog.events.EventHandler(this);109};110goog.inherits(goog.net.XhrManager, goog.events.EventTarget);111112113/**114* Error to throw when a send is attempted with an ID that the manager already115* has registered for another request.116* @type {string}117* @private118*/119goog.net.XhrManager.ERROR_ID_IN_USE_ = '[goog.net.XhrManager] ID in use';120121122/**123* The goog.net.EventType's to listen/unlisten for on the XhrIo object.124* @type {Array<goog.net.EventType>}125* @private126*/127goog.net.XhrManager.XHR_EVENT_TYPES_ = [128goog.net.EventType.READY, goog.net.EventType.COMPLETE,129goog.net.EventType.SUCCESS, goog.net.EventType.ERROR,130goog.net.EventType.ABORT, goog.net.EventType.TIMEOUT131];132133134/**135* Sets the number of milliseconds after which an incomplete request will be136* aborted. Zero means no timeout is set.137* @param {number} ms Timeout interval in milliseconds; 0 means none.138*/139goog.net.XhrManager.prototype.setTimeoutInterval = function(ms) {140this.timeoutInterval_ = Math.max(0, ms);141};142143144/**145* Returns the number of requests either in flight, or waiting to be sent.146* The count will include the current request if used within a COMPLETE event147* handler or callback.148* @return {number} The number of requests in flight or pending send.149*/150goog.net.XhrManager.prototype.getOutstandingCount = function() {151return this.requests_.getCount();152};153154155/**156* Returns an array of request ids that are either in flight, or waiting to157* be sent. The id of the current request will be included if used within a158* COMPLETE event handler or callback.159* @return {!Array<string>} Request ids in flight or pending send.160*/161goog.net.XhrManager.prototype.getOutstandingRequestIds = function() {162return this.requests_.getKeys();163};164165166/**167* Registers the given request to be sent. Throws an error if a request168* already exists with the given ID.169* NOTE: It is not sent immediately. It is buffered and will be sent when an170* XhrIo object becomes available, taking into account the request's171* priority. Note also that requests of equal priority are sent in an172* implementation specific order - to get FIFO queue semantics use a173* monotonically increasing priority for successive requests.174* @param {string} id The id of the request.175* @param {string} url Uri to make the request to.176* @param {string=} opt_method Send method, default: GET.177* @param {ArrayBuffer|ArrayBufferView|Blob|Document|FormData|string=}178* opt_content Post data.179* @param {Object|goog.structs.Map=} opt_headers Map of headers to add to the180* request.181* @param {number=} opt_priority The priority of the request. A smaller value182* means a higher priority.183* @param {Function=} opt_callback Callback function for when request is184* complete. The only param is the event object from the COMPLETE event.185* @param {number=} opt_maxRetries The maximum number of times the request186* should be retried.187* @param {goog.net.XhrIo.ResponseType=} opt_responseType The response type of188* this request; defaults to goog.net.XhrIo.ResponseType.DEFAULT.189* @param {boolean=} opt_withCredentials Add credentials to this request,190* default: false.191* @return {!goog.net.XhrManager.Request} The queued request object.192*/193goog.net.XhrManager.prototype.send = function(194id, url, opt_method, opt_content, opt_headers, opt_priority, opt_callback,195opt_maxRetries, opt_responseType, opt_withCredentials) {196var requests = this.requests_;197// Check if there is already a request with the given id.198if (requests.get(id)) {199throw Error(goog.net.XhrManager.ERROR_ID_IN_USE_);200}201202// Make the Request object.203var request = new goog.net.XhrManager.Request(204url, goog.bind(this.handleEvent_, this, id), opt_method, opt_content,205opt_headers, opt_callback,206goog.isDef(opt_maxRetries) ? opt_maxRetries : this.maxRetries_,207opt_responseType,208goog.isDef(opt_withCredentials) ? opt_withCredentials :209this.withCredentials_);210this.requests_.set(id, request);211212// Setup the callback for the pool.213var callback = goog.bind(this.handleAvailableXhr_, this, id);214this.xhrPool_.getObject(callback, opt_priority);215216return request;217};218219220/**221* Aborts the request associated with id.222* @param {string} id The id of the request to abort.223* @param {boolean=} opt_force If true, remove the id now so it can be reused.224* No events are fired and the callback is not called when forced.225*/226goog.net.XhrManager.prototype.abort = function(id, opt_force) {227var request = this.requests_.get(id);228if (request) {229var xhrIo = request.xhrIo;230request.setAborted(true);231if (opt_force) {232if (xhrIo) {233// We remove listeners to make sure nothing gets called if a new request234// with the same id is made.235this.removeXhrListener_(xhrIo, request.getXhrEventCallback());236goog.events.listenOnce(xhrIo, goog.net.EventType.READY, function() {237this.xhrPool_.releaseObject(xhrIo);238}, false, this);239}240this.requests_.remove(id);241}242if (xhrIo) {243xhrIo.abort();244}245}246};247248249/**250* Handles when an XhrIo object becomes available. Sets up the events, fires251* the READY event, and starts the process to send the request.252* @param {string} id The id of the request the XhrIo is for.253* @param {goog.net.XhrIo} xhrIo The available XhrIo object.254* @private255*/256goog.net.XhrManager.prototype.handleAvailableXhr_ = function(id, xhrIo) {257var request = this.requests_.get(id);258// Make sure the request doesn't already have an XhrIo attached. This can259// happen if a forced abort occurs before an XhrIo is available, and a new260// request with the same id is made.261if (request && !request.xhrIo) {262this.addXhrListener_(xhrIo, request.getXhrEventCallback());263264// Set properties for the XhrIo.265xhrIo.setTimeoutInterval(this.timeoutInterval_);266xhrIo.setResponseType(request.getResponseType());267xhrIo.setWithCredentials(request.getWithCredentials());268269// Add a reference to the XhrIo object to the request.270request.xhrIo = xhrIo;271272// Notify the listeners.273this.dispatchEvent(274new goog.net.XhrManager.Event(275goog.net.EventType.READY, this, id, xhrIo));276277// Send the request.278this.retry_(id, xhrIo);279280// If the request was aborted before it got an XhrIo object, abort it now.281if (request.getAborted()) {282xhrIo.abort();283}284} else {285// If the request has an XhrIo object already, or no request exists, just286// return the XhrIo back to the pool.287this.xhrPool_.releaseObject(xhrIo);288}289};290291292/**293* Handles all events fired by the XhrIo object for a given request.294* @param {string} id The id of the request.295* @param {goog.events.Event} e The event.296* @return {Object} The return value from the handler, if any.297* @private298*/299goog.net.XhrManager.prototype.handleEvent_ = function(id, e) {300var xhrIo = /** @type {goog.net.XhrIo} */ (e.target);301switch (e.type) {302case goog.net.EventType.READY:303this.retry_(id, xhrIo);304break;305306case goog.net.EventType.COMPLETE:307return this.handleComplete_(id, xhrIo, e);308309case goog.net.EventType.SUCCESS:310this.handleSuccess_(id, xhrIo);311break;312313// A timeout is handled like an error.314case goog.net.EventType.TIMEOUT:315case goog.net.EventType.ERROR:316this.handleError_(id, xhrIo);317break;318319case goog.net.EventType.ABORT:320this.handleAbort_(id, xhrIo);321break;322}323return null;324};325326327/**328* Attempts to retry the given request. If the request has already attempted329* the maximum number of retries, then it removes the request and releases330* the XhrIo object back into the pool.331* @param {string} id The id of the request.332* @param {goog.net.XhrIo} xhrIo The XhrIo object.333* @private334*/335goog.net.XhrManager.prototype.retry_ = function(id, xhrIo) {336var request = this.requests_.get(id);337338// If the request has not completed and it is below its max. retries.339if (request && !request.getCompleted() && !request.hasReachedMaxRetries()) {340request.increaseAttemptCount();341xhrIo.send(342request.getUrl(), request.getMethod(), request.getContent(),343request.getHeaders());344} else {345if (request) {346// Remove the events on the XhrIo objects.347this.removeXhrListener_(xhrIo, request.getXhrEventCallback());348349// Remove the request.350this.requests_.remove(id);351}352// Release the XhrIo object back into the pool.353this.xhrPool_.releaseObject(xhrIo);354}355};356357358/**359* Handles the complete of a request. Dispatches the COMPLETE event and sets the360* the request as completed if the request has succeeded, or is done retrying.361* @param {string} id The id of the request.362* @param {goog.net.XhrIo} xhrIo The XhrIo object.363* @param {goog.events.Event} e The original event.364* @return {Object} The return value from the callback, if any.365* @private366*/367goog.net.XhrManager.prototype.handleComplete_ = function(id, xhrIo, e) {368// Only if the request is done processing should a COMPLETE event be fired.369var request = this.requests_.get(id);370if (xhrIo.getLastErrorCode() == goog.net.ErrorCode.ABORT ||371xhrIo.isSuccess() || request.hasReachedMaxRetries()) {372this.dispatchEvent(373new goog.net.XhrManager.Event(374goog.net.EventType.COMPLETE, this, id, xhrIo));375376// If the request exists, we mark it as completed and call the callback377if (request) {378request.setCompleted(true);379// Call the complete callback as if it was set as a COMPLETE event on the380// XhrIo directly.381if (request.getCompleteCallback()) {382return request.getCompleteCallback().call(xhrIo, e);383}384}385}386return null;387};388389390/**391* Handles the abort of an underlying XhrIo object.392* @param {string} id The id of the request.393* @param {goog.net.XhrIo} xhrIo The XhrIo object.394* @private395*/396goog.net.XhrManager.prototype.handleAbort_ = function(id, xhrIo) {397// Fire event.398// NOTE: The complete event should always be fired before the abort event, so399// the bulk of the work is done in handleComplete.400this.dispatchEvent(401new goog.net.XhrManager.Event(goog.net.EventType.ABORT, this, id, xhrIo));402};403404405/**406* Handles the success of a request. Dispatches the SUCCESS event and sets the407* the request as completed.408* @param {string} id The id of the request.409* @param {goog.net.XhrIo} xhrIo The XhrIo object.410* @private411*/412goog.net.XhrManager.prototype.handleSuccess_ = function(id, xhrIo) {413// Fire event.414// NOTE: We don't release the XhrIo object from the pool here.415// It is released in the retry method, when we know it is back in the416// ready state.417this.dispatchEvent(418new goog.net.XhrManager.Event(419goog.net.EventType.SUCCESS, this, id, xhrIo));420};421422423/**424* Handles the error of a request. If the request has not reach its maximum425* number of retries, then it lets the request retry naturally (will let the426* request hit the READY state). Else, it dispatches the ERROR event.427* @param {string} id The id of the request.428* @param {goog.net.XhrIo} xhrIo The XhrIo object.429* @private430*/431goog.net.XhrManager.prototype.handleError_ = function(id, xhrIo) {432var request = this.requests_.get(id);433434// If the maximum number of retries has been reached.435if (request.hasReachedMaxRetries()) {436// Fire event.437// NOTE: We don't release the XhrIo object from the pool here.438// It is released in the retry method, when we know it is back in the439// ready state.440this.dispatchEvent(441new goog.net.XhrManager.Event(442goog.net.EventType.ERROR, this, id, xhrIo));443}444};445446447/**448* Remove listeners for XHR events on an XhrIo object.449* @param {goog.net.XhrIo} xhrIo The object to stop listenening to events on.450* @param {Function} func The callback to remove from event handling.451* @param {string|Array<string>=} opt_types Event types to remove listeners452* for. Defaults to XHR_EVENT_TYPES_.453* @private454*/455goog.net.XhrManager.prototype.removeXhrListener_ = function(456xhrIo, func, opt_types) {457var types = opt_types || goog.net.XhrManager.XHR_EVENT_TYPES_;458this.eventHandler_.unlisten(xhrIo, types, func);459};460461462/**463* Adds a listener for XHR events on an XhrIo object.464* @param {goog.net.XhrIo} xhrIo The object listen to events on.465* @param {Function} func The callback when the event occurs.466* @param {string|Array<string>=} opt_types Event types to attach listeners to.467* Defaults to XHR_EVENT_TYPES_.468* @private469*/470goog.net.XhrManager.prototype.addXhrListener_ = function(471xhrIo, func, opt_types) {472var types = opt_types || goog.net.XhrManager.XHR_EVENT_TYPES_;473this.eventHandler_.listen(xhrIo, types, func);474};475476477/** @override */478goog.net.XhrManager.prototype.disposeInternal = function() {479goog.net.XhrManager.superClass_.disposeInternal.call(this);480481this.xhrPool_.dispose();482this.xhrPool_ = null;483484this.eventHandler_.dispose();485this.eventHandler_ = null;486487this.requests_.clear();488this.requests_ = null;489};490491492493/**494* An event dispatched by XhrManager.495*496* @param {goog.net.EventType} type Event Type.497* @param {goog.net.XhrManager} target Reference to the object that is the498* target of this event.499* @param {string} id The id of the request this event is for.500* @param {goog.net.XhrIo} xhrIo The XhrIo object of the request.501* @constructor502* @extends {goog.events.Event}503* @final504*/505goog.net.XhrManager.Event = function(type, target, id, xhrIo) {506goog.events.Event.call(this, type, target);507508/**509* The id of the request this event is for.510* @type {string}511*/512this.id = id;513514/**515* The XhrIo object of the request.516* @type {goog.net.XhrIo}517*/518this.xhrIo = xhrIo;519};520goog.inherits(goog.net.XhrManager.Event, goog.events.Event);521522523524/**525* An encapsulation of everything needed to make a Xhr request.526* NOTE: This is used internal to the XhrManager.527*528* @param {string} url Uri to make the request too.529* @param {Function} xhrEventCallback Callback attached to the events of the530* XhrIo object of the request.531* @param {string=} opt_method Send method, default: GET.532* @param {ArrayBuffer|ArrayBufferView|Blob|Document|FormData|string=}533* opt_content Post data.534* @param {Object|goog.structs.Map=} opt_headers Map of headers to add to the535* request.536* @param {Function=} opt_callback Callback function for when request is537* complete. NOTE: Only 1 callback supported across all events.538* @param {number=} opt_maxRetries The maximum number of times the request539* should be retried (Default: 1).540* @param {goog.net.XhrIo.ResponseType=} opt_responseType The response type of541* this request; defaults to goog.net.XhrIo.ResponseType.DEFAULT.542* @param {boolean=} opt_withCredentials Add credentials to this request,543* default: false.544*545* @constructor546* @final547*/548goog.net.XhrManager.Request = function(549url, xhrEventCallback, opt_method, opt_content, opt_headers, opt_callback,550opt_maxRetries, opt_responseType, opt_withCredentials) {551/**552* Uri to make the request too.553* @type {string}554* @private555*/556this.url_ = url;557558/**559* Send method.560* @type {string}561* @private562*/563this.method_ = opt_method || 'GET';564565/**566* Post data.567* @type {ArrayBuffer|ArrayBufferView|Blob|Document|FormData|string|undefined}568* @private569*/570this.content_ = opt_content;571572/**573* Map of headers574* @type {Object|goog.structs.Map|null}575* @private576*/577this.headers_ = opt_headers || null;578579/**580* The maximum number of times the request should be retried.581* @type {number}582* @private583*/584this.maxRetries_ = goog.isDef(opt_maxRetries) ? opt_maxRetries : 1;585586/**587* The number of attempts so far.588* @type {number}589* @private590*/591this.attemptCount_ = 0;592593/**594* Whether the request has been completed.595* @type {boolean}596* @private597*/598this.completed_ = false;599600/**601* Whether the request has been aborted.602* @type {boolean}603* @private604*/605this.aborted_ = false;606607/**608* Callback attached to the events of the XhrIo object.609* @type {Function}610* @private611*/612this.xhrEventCallback_ = xhrEventCallback;613614/**615* Callback function called when request is complete.616* @type {Function|undefined}617* @private618*/619this.completeCallback_ = opt_callback;620621/**622* A response type to set on this.xhrIo when it's populated.623* @type {!goog.net.XhrIo.ResponseType}624* @private625*/626this.responseType_ = opt_responseType || goog.net.XhrIo.ResponseType.DEFAULT;627628/**629* Send credentials with this request, or not.630* @private {boolean}631*/632this.withCredentials_ = !!opt_withCredentials;633634/**635* The XhrIo instance handling this request. Set in handleAvailableXhr.636* @type {goog.net.XhrIo}637*/638this.xhrIo = null;639640};641642643/**644* Gets the uri.645* @return {string} The uri to make the request to.646*/647goog.net.XhrManager.Request.prototype.getUrl = function() {648return this.url_;649};650651652/**653* Gets the send method.654* @return {string} The send method.655*/656goog.net.XhrManager.Request.prototype.getMethod = function() {657return this.method_;658};659660661/**662* Gets the post data.663* @return {ArrayBuffer|ArrayBufferView|Blob|Document|FormData|string|undefined}664* The post data.665*/666goog.net.XhrManager.Request.prototype.getContent = function() {667return this.content_;668};669670671/**672* Gets the map of headers.673* @return {Object|goog.structs.Map} The map of headers.674*/675goog.net.XhrManager.Request.prototype.getHeaders = function() {676return this.headers_;677};678679680/**681* Gets the withCredentials flag.682* @return {boolean} Add credentials, or not.683*/684goog.net.XhrManager.Request.prototype.getWithCredentials = function() {685return this.withCredentials_;686};687688689/**690* Gets the maximum number of times the request should be retried.691* @return {number} The maximum number of times the request should be retried.692*/693goog.net.XhrManager.Request.prototype.getMaxRetries = function() {694return this.maxRetries_;695};696697698/**699* Gets the number of attempts so far.700* @return {number} The number of attempts so far.701*/702goog.net.XhrManager.Request.prototype.getAttemptCount = function() {703return this.attemptCount_;704};705706707/**708* Increases the number of attempts so far.709*/710goog.net.XhrManager.Request.prototype.increaseAttemptCount = function() {711this.attemptCount_++;712};713714715/**716* Returns whether the request has reached the maximum number of retries.717* @return {boolean} Whether the request has reached the maximum number of718* retries.719*/720goog.net.XhrManager.Request.prototype.hasReachedMaxRetries = function() {721return this.attemptCount_ > this.maxRetries_;722};723724725/**726* Sets the completed status.727* @param {boolean} complete The completed status.728*/729goog.net.XhrManager.Request.prototype.setCompleted = function(complete) {730this.completed_ = complete;731};732733734/**735* Gets the completed status.736* @return {boolean} The completed status.737*/738goog.net.XhrManager.Request.prototype.getCompleted = function() {739return this.completed_;740};741742743/**744* Sets the aborted status.745* @param {boolean} aborted True if the request was aborted, otherwise False.746*/747goog.net.XhrManager.Request.prototype.setAborted = function(aborted) {748this.aborted_ = aborted;749};750751752/**753* Gets the aborted status.754* @return {boolean} True if request was aborted, otherwise False.755*/756goog.net.XhrManager.Request.prototype.getAborted = function() {757return this.aborted_;758};759760761/**762* Gets the callback attached to the events of the XhrIo object.763* @return {Function} The callback attached to the events of the764* XhrIo object.765*/766goog.net.XhrManager.Request.prototype.getXhrEventCallback = function() {767return this.xhrEventCallback_;768};769770771/**772* Gets the callback for when the request is complete.773* @return {Function|undefined} The callback for when the request is complete.774*/775goog.net.XhrManager.Request.prototype.getCompleteCallback = function() {776return this.completeCallback_;777};778779780/**781* Gets the response type that will be set on this request's XhrIo when it's782* available.783* @return {!goog.net.XhrIo.ResponseType} The response type to be set784* when an XhrIo becomes available to this request.785*/786goog.net.XhrManager.Request.prototype.getResponseType = function() {787return this.responseType_;788};789790791