Upgrade to ExtJS 4.0.2 - Released 06/09/2011
[extjs.git] / src / data / Connection.js
1 /*
2
3 This file is part of Ext JS 4
4
5 Copyright (c) 2011 Sencha Inc
6
7 Contact:  http://www.sencha.com/contact
8
9 GNU General Public License Usage
10 This file may be used under the terms of the GNU General Public License version 3.0 as published by the Free Software Foundation and appearing in the file LICENSE included in the packaging of this file.  Please review the following information to ensure the GNU General Public License version 3.0 requirements will be met: http://www.gnu.org/copyleft/gpl.html.
11
12 If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.
13
14 */
15 /**
16  * @class Ext.data.Connection
17  * The Connection class encapsulates a connection to the page's originating domain, allowing requests to be made either
18  * to a configured URL, or to a URL specified at request time.
19  *
20  * Requests made by this class are asynchronous, and will return immediately. No data from the server will be available
21  * to the statement immediately following the {@link #request} call. To process returned data, use a success callback
22  * in the request options object, or an {@link #requestcomplete event listener}.
23  *
24  * <p><u>File Uploads</u></p>
25  *
26  * File uploads are not performed using normal "Ajax" techniques, that is they are not performed using XMLHttpRequests.
27  * Instead the form is submitted in the standard manner with the DOM &lt;form&gt; element temporarily modified to have its
28  * target set to refer to a dynamically generated, hidden &lt;iframe&gt; which is inserted into the document but removed
29  * after the return data has been gathered.
30  *
31  * The server response is parsed by the browser to create the document for the IFRAME. If the server is using JSON to
32  * send the return object, then the Content-Type header must be set to "text/html" in order to tell the browser to
33  * insert the text unchanged into the document body.
34  *
35  * Characters which are significant to an HTML parser must be sent as HTML entities, so encode "&lt;" as "&amp;lt;", "&amp;" as
36  * "&amp;amp;" etc.
37  *
38  * The response text is retrieved from the document, and a fake XMLHttpRequest object is created containing a
39  * responseText property in order to conform to the requirements of event handlers and callbacks.
40  *
41  * Be aware that file upload packets are sent with the content type multipart/form and some server technologies
42  * (notably JEE) may require some custom processing in order to retrieve parameter names and parameter values from the
43  * packet content.
44  *
45  * Also note that it's not possible to check the response code of the hidden iframe, so the success handler will ALWAYS fire.
46  */
47 Ext.define('Ext.data.Connection', {
48     mixins: {
49         observable: 'Ext.util.Observable'
50     },
51
52     statics: {
53         requestId: 0
54     },
55
56     url: null,
57     async: true,
58     method: null,
59     username: '',
60     password: '',
61
62     /**
63      * @cfg {Boolean} disableCaching (Optional) True to add a unique cache-buster param to GET requests. (defaults to true)
64      */
65     disableCaching: true,
66
67     /**
68      * @cfg {String} disableCachingParam (Optional) Change the parameter which is sent went disabling caching
69      * through a cache buster. Defaults to '_dc'
70      */
71     disableCachingParam: '_dc',
72
73     /**
74      * @cfg {Number} timeout (Optional) The timeout in milliseconds to be used for requests. (defaults to 30000)
75      */
76     timeout : 30000,
77
78     /**
79      * @cfg {Object} extraParams (Optional) Any parameters to be appended to the request.
80      */
81
82     useDefaultHeader : true,
83     defaultPostHeader : 'application/x-www-form-urlencoded; charset=UTF-8',
84     useDefaultXhrHeader : true,
85     defaultXhrHeader : 'XMLHttpRequest',
86
87     constructor : function(config) {
88         config = config || {};
89         Ext.apply(this, config);
90
91         this.addEvents(
92             /**
93              * @event beforerequest
94              * Fires before a network request is made to retrieve a data object.
95              * @param {Connection} conn This Connection object.
96              * @param {Object} options The options config object passed to the {@link #request} method.
97              */
98             'beforerequest',
99             /**
100              * @event requestcomplete
101              * Fires if the request was successfully completed.
102              * @param {Connection} conn This Connection object.
103              * @param {Object} response The XHR object containing the response data.
104              * See <a href="http://www.w3.org/TR/XMLHttpRequest/">The XMLHttpRequest Object</a>
105              * for details.
106              * @param {Object} options The options config object passed to the {@link #request} method.
107              */
108             'requestcomplete',
109             /**
110              * @event requestexception
111              * Fires if an error HTTP status was returned from the server.
112              * See <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html">HTTP Status Code Definitions</a>
113              * for details of HTTP status codes.
114              * @param {Connection} conn This Connection object.
115              * @param {Object} response The XHR object containing the response data.
116              * See <a href="http://www.w3.org/TR/XMLHttpRequest/">The XMLHttpRequest Object</a>
117              * for details.
118              * @param {Object} options The options config object passed to the {@link #request} method.
119              */
120             'requestexception'
121         );
122         this.requests = {};
123         this.mixins.observable.constructor.call(this);
124     },
125
126     /**
127      * <p>Sends an HTTP request to a remote server.</p>
128      * <p><b>Important:</b> Ajax server requests are asynchronous, and this call will
129      * return before the response has been received. Process any returned data
130      * in a callback function.</p>
131      * <pre><code>
132 Ext.Ajax.request({
133 url: 'ajax_demo/sample.json',
134 success: function(response, opts) {
135   var obj = Ext.decode(response.responseText);
136   console.dir(obj);
137 },
138 failure: function(response, opts) {
139   console.log('server-side failure with status code ' + response.status);
140 }
141 });
142      * </code></pre>
143      * <p>To execute a callback function in the correct scope, use the <tt>scope</tt> option.</p>
144      * @param {Object} options An object which may contain the following properties:<ul>
145      * <li><b>url</b> : String/Function (Optional)<div class="sub-desc">The URL to
146      * which to send the request, or a function to call which returns a URL string. The scope of the
147      * function is specified by the <tt>scope</tt> option. Defaults to the configured
148      * <tt>{@link #url}</tt>.</div></li>
149      * <li><b>params</b> : Object/String/Function (Optional)<div class="sub-desc">
150      * An object containing properties which are used as parameters to the
151      * request, a url encoded string or a function to call to get either. The scope of the function
152      * is specified by the <tt>scope</tt> option.</div></li>
153      * <li><b>method</b> : String (Optional)<div class="sub-desc">The HTTP method to use
154      * for the request. Defaults to the configured method, or if no method was configured,
155      * "GET" if no parameters are being sent, and "POST" if parameters are being sent.  Note that
156      * the method name is case-sensitive and should be all caps.</div></li>
157      * <li><b>callback</b> : Function (Optional)<div class="sub-desc">The
158      * function to be called upon receipt of the HTTP response. The callback is
159      * called regardless of success or failure and is passed the following
160      * parameters:<ul>
161      * <li><b>options</b> : Object<div class="sub-desc">The parameter to the request call.</div></li>
162      * <li><b>success</b> : Boolean<div class="sub-desc">True if the request succeeded.</div></li>
163      * <li><b>response</b> : Object<div class="sub-desc">The XMLHttpRequest object containing the response data.
164      * See <a href="http://www.w3.org/TR/XMLHttpRequest/">http://www.w3.org/TR/XMLHttpRequest/</a> for details about
165      * accessing elements of the response.</div></li>
166      * </ul></div></li>
167      * <li><a id="request-option-success"></a><b>success</b> : Function (Optional)<div class="sub-desc">The function
168      * to be called upon success of the request. The callback is passed the following
169      * parameters:<ul>
170      * <li><b>response</b> : Object<div class="sub-desc">The XMLHttpRequest object containing the response data.</div></li>
171      * <li><b>options</b> : Object<div class="sub-desc">The parameter to the request call.</div></li>
172      * </ul></div></li>
173      * <li><b>failure</b> : Function (Optional)<div class="sub-desc">The function
174      * to be called upon failure of the request. The callback is passed the
175      * following parameters:<ul>
176      * <li><b>response</b> : Object<div class="sub-desc">The XMLHttpRequest object containing the response data.</div></li>
177      * <li><b>options</b> : Object<div class="sub-desc">The parameter to the request call.</div></li>
178      * </ul></div></li>
179      * <li><b>scope</b> : Object (Optional)<div class="sub-desc">The scope in
180      * which to execute the callbacks: The "this" object for the callback function. If the <tt>url</tt>, or <tt>params</tt> options were
181      * specified as functions from which to draw values, then this also serves as the scope for those function calls.
182      * Defaults to the browser window.</div></li>
183      * <li><b>timeout</b> : Number (Optional)<div class="sub-desc">The timeout in milliseconds to be used for this request. Defaults to 30 seconds.</div></li>
184      * <li><b>form</b> : Element/HTMLElement/String (Optional)<div class="sub-desc">The <tt>&lt;form&gt;</tt>
185      * Element or the id of the <tt>&lt;form&gt;</tt> to pull parameters from.</div></li>
186      * <li><a id="request-option-isUpload"></a><b>isUpload</b> : Boolean (Optional)<div class="sub-desc"><b>Only meaningful when used
187      * with the <tt>form</tt> option</b>.
188      * <p>True if the form object is a file upload (will be set automatically if the form was
189      * configured with <b><tt>enctype</tt></b> "multipart/form-data").</p>
190      * <p>File uploads are not performed using normal "Ajax" techniques, that is they are <b>not</b>
191      * performed using XMLHttpRequests. Instead the form is submitted in the standard manner with the
192      * DOM <tt>&lt;form></tt> element temporarily modified to have its
193      * <a href="http://www.w3.org/TR/REC-html40/present/frames.html#adef-target">target</a> set to refer
194      * to a dynamically generated, hidden <tt>&lt;iframe></tt> which is inserted into the document
195      * but removed after the return data has been gathered.</p>
196      * <p>The server response is parsed by the browser to create the document for the IFRAME. If the
197      * server is using JSON to send the return object, then the
198      * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17">Content-Type</a> header
199      * must be set to "text/html" in order to tell the browser to insert the text unchanged into the document body.</p>
200      * <p>The response text is retrieved from the document, and a fake XMLHttpRequest object
201      * is created containing a <tt>responseText</tt> property in order to conform to the
202      * requirements of event handlers and callbacks.</p>
203      * <p>Be aware that file upload packets are sent with the content type <a href="http://www.faqs.org/rfcs/rfc2388.html">multipart/form</a>
204      * and some server technologies (notably JEE) may require some custom processing in order to
205      * retrieve parameter names and parameter values from the packet content.</p>
206      * </div></li>
207      * <li><b>headers</b> : Object (Optional)<div class="sub-desc">Request
208      * headers to set for the request.</div></li>
209      * <li><b>xmlData</b> : Object (Optional)<div class="sub-desc">XML document
210      * to use for the post. Note: This will be used instead of params for the post
211      * data. Any params will be appended to the URL.</div></li>
212      * <li><b>jsonData</b> : Object/String (Optional)<div class="sub-desc">JSON
213      * data to use as the post. Note: This will be used instead of params for the post
214      * data. Any params will be appended to the URL.</div></li>
215      * <li><b>disableCaching</b> : Boolean (Optional)<div class="sub-desc">True
216      * to add a unique cache-buster param to GET requests.</div></li>
217      * </ul></p>
218      * <p>The options object may also contain any other property which might be needed to perform
219      * postprocessing in a callback because it is passed to callback functions.</p>
220      * @return {Object} request The request object. This may be used
221      * to cancel the request.
222      */
223     request : function(options) {
224         options = options || {};
225         var me = this,
226             scope = options.scope || window,
227             username = options.username || me.username,
228             password = options.password || me.password || '',
229             async,
230             requestOptions,
231             request,
232             headers,
233             xhr;
234
235         if (me.fireEvent('beforerequest', me, options) !== false) {
236
237             requestOptions = me.setOptions(options, scope);
238
239             if (this.isFormUpload(options) === true) {
240                 this.upload(options.form, requestOptions.url, requestOptions.data, options);
241                 return null;
242             }
243
244             // if autoabort is set, cancel the current transactions
245             if (options.autoAbort === true || me.autoAbort) {
246                 me.abort();
247             }
248
249             // create a connection object
250             xhr = this.getXhrInstance();
251
252             async = options.async !== false ? (options.async || me.async) : false;
253
254             // open the request
255             if (username) {
256                 xhr.open(requestOptions.method, requestOptions.url, async, username, password);
257             } else {
258                 xhr.open(requestOptions.method, requestOptions.url, async);
259             }
260
261             headers = me.setupHeaders(xhr, options, requestOptions.data, requestOptions.params);
262
263             // create the transaction object
264             request = {
265                 id: ++Ext.data.Connection.requestId,
266                 xhr: xhr,
267                 headers: headers,
268                 options: options,
269                 async: async,
270                 timeout: setTimeout(function() {
271                     request.timedout = true;
272                     me.abort(request);
273                 }, options.timeout || me.timeout)
274             };
275             me.requests[request.id] = request;
276
277             // bind our statechange listener
278             if (async) {
279                 xhr.onreadystatechange = Ext.Function.bind(me.onStateChange, me, [request]);
280             }
281
282             // start the request!
283             xhr.send(requestOptions.data);
284             if (!async) {
285                 return this.onComplete(request);
286             }
287             return request;
288         } else {
289             Ext.callback(options.callback, options.scope, [options, undefined, undefined]);
290             return null;
291         }
292     },
293
294     /**
295      * Upload a form using a hidden iframe.
296      * @param {Mixed} form The form to upload
297      * @param {String} url The url to post to
298      * @param {String} params Any extra parameters to pass
299      * @param {Object} options The initial options
300      */
301     upload: function(form, url, params, options){
302         form = Ext.getDom(form);
303         options = options || {};
304
305         var id = Ext.id(),
306                 frame = document.createElement('iframe'),
307                 hiddens = [],
308                 encoding = 'multipart/form-data',
309                 buf = {
310                     target: form.target,
311                     method: form.method,
312                     encoding: form.encoding,
313                     enctype: form.enctype,
314                     action: form.action
315                 }, hiddenItem;
316
317         /*
318          * Originally this behaviour was modified for Opera 10 to apply the secure URL after
319          * the frame had been added to the document. It seems this has since been corrected in
320          * Opera so the behaviour has been reverted, the URL will be set before being added.
321          */
322         Ext.fly(frame).set({
323             id: id,
324             name: id,
325             cls: Ext.baseCSSPrefix + 'hide-display',
326             src: Ext.SSL_SECURE_URL
327         });
328
329         document.body.appendChild(frame);
330
331         // This is required so that IE doesn't pop the response up in a new window.
332         if (document.frames) {
333            document.frames[id].name = id;
334         }
335
336         Ext.fly(form).set({
337             target: id,
338             method: 'POST',
339             enctype: encoding,
340             encoding: encoding,
341             action: url || buf.action
342         });
343
344         // add dynamic params
345         if (params) {
346             Ext.iterate(Ext.Object.fromQueryString(params), function(name, value){
347                 hiddenItem = document.createElement('input');
348                 Ext.fly(hiddenItem).set({
349                     type: 'hidden',
350                     value: value,
351                     name: name
352                 });
353                 form.appendChild(hiddenItem);
354                 hiddens.push(hiddenItem);
355             });
356         }
357
358         Ext.fly(frame).on('load', Ext.Function.bind(this.onUploadComplete, this, [frame, options]), null, {single: true});
359         form.submit();
360
361         Ext.fly(form).set(buf);
362         Ext.each(hiddens, function(h) {
363             Ext.removeNode(h);
364         });
365     },
366
367     onUploadComplete: function(frame, options){
368         var me = this,
369             // bogus response object
370             response = {
371                 responseText: '',
372                 responseXML: null
373             }, doc, firstChild;
374
375         try {
376             doc = frame.contentWindow.document || frame.contentDocument || window.frames[id].document;
377             if (doc) {
378                 if (doc.body) {
379                     if (/textarea/i.test((firstChild = doc.body.firstChild || {}).tagName)) { // json response wrapped in textarea
380                         response.responseText = firstChild.value;
381                     } else {
382                         response.responseText = doc.body.innerHTML;
383                     }
384                 }
385                 //in IE the document may still have a body even if returns XML.
386                 response.responseXML = doc.XMLDocument || doc;
387             }
388         } catch (e) {
389         }
390
391         me.fireEvent('requestcomplete', me, response, options);
392
393         Ext.callback(options.success, options.scope, [response, options]);
394         Ext.callback(options.callback, options.scope, [options, true, response]);
395
396         setTimeout(function(){
397             Ext.removeNode(frame);
398         }, 100);
399     },
400
401     /**
402      * Detect whether the form is intended to be used for an upload.
403      * @private
404      */
405     isFormUpload: function(options){
406         var form = this.getForm(options);
407         if (form) {
408             return (options.isUpload || (/multipart\/form-data/i).test(form.getAttribute('enctype')));
409         }
410         return false;
411     },
412
413     /**
414      * Get the form object from options.
415      * @private
416      * @param {Object} options The request options
417      * @return {HTMLElement} The form, null if not passed
418      */
419     getForm: function(options){
420         return Ext.getDom(options.form) || null;
421     },
422
423     /**
424      * Set various options such as the url, params for the request
425      * @param {Object} options The initial options
426      * @param {Object} scope The scope to execute in
427      * @return {Object} The params for the request
428      */
429     setOptions: function(options, scope){
430         var me =  this,
431             params = options.params || {},
432             extraParams = me.extraParams,
433             urlParams = options.urlParams,
434             url = options.url || me.url,
435             jsonData = options.jsonData,
436             method,
437             disableCache,
438             data;
439
440
441         // allow params to be a method that returns the params object
442         if (Ext.isFunction(params)) {
443             params = params.call(scope, options);
444         }
445
446         // allow url to be a method that returns the actual url
447         if (Ext.isFunction(url)) {
448             url = url.call(scope, options);
449         }
450
451         url = this.setupUrl(options, url);
452
453         //<debug>
454         if (!url) {
455             Ext.Error.raise({
456                 options: options,
457                 msg: 'No URL specified'
458             });
459         }
460         //</debug>
461
462         // check for xml or json data, and make sure json data is encoded
463         data = options.rawData || options.xmlData || jsonData || null;
464         if (jsonData && !Ext.isPrimitive(jsonData)) {
465             data = Ext.encode(data);
466         }
467
468         // make sure params are a url encoded string and include any extraParams if specified
469         if (Ext.isObject(params)) {
470             params = Ext.Object.toQueryString(params);
471         }
472
473         if (Ext.isObject(extraParams)) {
474             extraParams = Ext.Object.toQueryString(extraParams);
475         }
476
477         params = params + ((extraParams) ? ((params) ? '&' : '') + extraParams : '');
478
479         urlParams = Ext.isObject(urlParams) ? Ext.Object.toQueryString(urlParams) : urlParams;
480
481         params = this.setupParams(options, params);
482
483         // decide the proper method for this request
484         method = (options.method || me.method || ((params || data) ? 'POST' : 'GET')).toUpperCase();
485         this.setupMethod(options, method);
486
487
488         disableCache = options.disableCaching !== false ? (options.disableCaching || me.disableCaching) : false;
489         // if the method is get append date to prevent caching
490         if (method === 'GET' && disableCache) {
491             url = Ext.urlAppend(url, (options.disableCachingParam || me.disableCachingParam) + '=' + (new Date().getTime()));
492         }
493
494         // if the method is get or there is json/xml data append the params to the url
495         if ((method == 'GET' || data) && params) {
496             url = Ext.urlAppend(url, params);
497             params = null;
498         }
499
500         // allow params to be forced into the url
501         if (urlParams) {
502             url = Ext.urlAppend(url, urlParams);
503         }
504
505         return {
506             url: url,
507             method: method,
508             data: data || params || null
509         };
510     },
511
512     /**
513      * Template method for overriding url
514      * @private
515      * @param {Object} options
516      * @param {String} url
517      * @return {String} The modified url
518      */
519     setupUrl: function(options, url){
520         var form = this.getForm(options);
521         if (form) {
522             url = url || form.action;
523         }
524         return url;
525     },
526
527
528     /**
529      * Template method for overriding params
530      * @private
531      * @param {Object} options
532      * @param {String} params
533      * @return {String} The modified params
534      */
535     setupParams: function(options, params) {
536         var form = this.getForm(options),
537             serializedForm;
538         if (form && !this.isFormUpload(options)) {
539             serializedForm = Ext.core.Element.serializeForm(form);
540             params = params ? (params + '&' + serializedForm) : serializedForm;
541         }
542         return params;
543     },
544
545     /**
546      * Template method for overriding method
547      * @private
548      * @param {Object} options
549      * @param {String} method
550      * @return {String} The modified method
551      */
552     setupMethod: function(options, method){
553         if (this.isFormUpload(options)) {
554             return 'POST';
555         }
556         return method;
557     },
558
559     /**
560      * Setup all the headers for the request
561      * @private
562      * @param {Object} xhr The xhr object
563      * @param {Object} options The options for the request
564      * @param {Object} data The data for the request
565      * @param {Object} params The params for the request
566      */
567     setupHeaders: function(xhr, options, data, params){
568         var me = this,
569             headers = Ext.apply({}, options.headers || {}, me.defaultHeaders || {}),
570             contentType = me.defaultPostHeader,
571             jsonData = options.jsonData,
572             xmlData = options.xmlData,
573             key,
574             header;
575
576         if (!headers['Content-Type'] && (data || params)) {
577             if (data) {
578                 if (options.rawData) {
579                     contentType = 'text/plain';
580                 } else {
581                     if (xmlData && Ext.isDefined(xmlData)) {
582                         contentType = 'text/xml';
583                     } else if (jsonData && Ext.isDefined(jsonData)) {
584                         contentType = 'application/json';
585                     }
586                 }
587             }
588             headers['Content-Type'] = contentType;
589         }
590
591         if (me.useDefaultXhrHeader && !headers['X-Requested-With']) {
592             headers['X-Requested-With'] = me.defaultXhrHeader;
593         }
594         // set up all the request headers on the xhr object
595         try{
596             for (key in headers) {
597                 if (headers.hasOwnProperty(key)) {
598                     header = headers[key];
599                     xhr.setRequestHeader(key, header);
600                 }
601
602             }
603         } catch(e) {
604             me.fireEvent('exception', key, header);
605         }
606         return headers;
607     },
608
609     /**
610      * Creates the appropriate XHR transport for the browser.
611      * @private
612      */
613     getXhrInstance: (function(){
614         var options = [function(){
615             return new XMLHttpRequest();
616         }, function(){
617             return new ActiveXObject('MSXML2.XMLHTTP.3.0');
618         }, function(){
619             return new ActiveXObject('MSXML2.XMLHTTP');
620         }, function(){
621             return new ActiveXObject('Microsoft.XMLHTTP');
622         }], i = 0,
623             len = options.length,
624             xhr;
625
626         for(; i < len; ++i) {
627             try{
628                 xhr = options[i];
629                 xhr();
630                 break;
631             }catch(e){}
632         }
633         return xhr;
634     })(),
635
636     /**
637      * Determine whether this object has a request outstanding.
638      * @param {Object} request (Optional) defaults to the last transaction
639      * @return {Boolean} True if there is an outstanding request.
640      */
641     isLoading : function(request) {
642         if (!(request && request.xhr)) {
643             return false;
644         }
645         // if there is a connection and readyState is not 0 or 4
646         var state = request.xhr.readyState;
647         return !(state === 0 || state == 4);
648     },
649
650     /**
651      * Aborts any outstanding request.
652      * @param {Object} request (Optional) defaults to the last request
653      */
654     abort : function(request) {
655         var me = this,
656             requests = me.requests,
657             id;
658
659         if (request && me.isLoading(request)) {
660             /*
661              * Clear out the onreadystatechange here, this allows us
662              * greater control, the browser may/may not fire the function
663              * depending on a series of conditions.
664              */
665             request.xhr.onreadystatechange = null;
666             request.xhr.abort();
667             me.clearTimeout(request);
668             if (!request.timedout) {
669                 request.aborted = true;
670             }
671             me.onComplete(request);
672             me.cleanup(request);
673         } else if (!request) {
674             for(id in requests) {
675                 if (requests.hasOwnProperty(id)) {
676                     me.abort(requests[id]);
677                 }
678             }
679         }
680     },
681
682     /**
683      * Fires when the state of the xhr changes
684      * @private
685      * @param {Object} request The request
686      */
687     onStateChange : function(request) {
688         if (request.xhr.readyState == 4) {
689             this.clearTimeout(request);
690             this.onComplete(request);
691             this.cleanup(request);
692         }
693     },
694
695     /**
696      * Clear the timeout on the request
697      * @private
698      * @param {Object} The request
699      */
700     clearTimeout: function(request){
701         clearTimeout(request.timeout);
702         delete request.timeout;
703     },
704
705     /**
706      * Clean up any left over information from the request
707      * @private
708      * @param {Object} The request
709      */
710     cleanup: function(request){
711         request.xhr = null;
712         delete request.xhr;
713     },
714
715     /**
716      * To be called when the request has come back from the server
717      * @private
718      * @param {Object} request
719      * @return {Object} The response
720      */
721     onComplete : function(request) {
722         var me = this,
723             options = request.options,
724             result,
725             success,
726             response;
727             
728         try {
729             result = me.parseStatus(request.xhr.status);
730         } catch (e) {
731             // in some browsers we can't access the status if the readyState is not 4, so the request has failed
732             result = {
733                 success : false, 
734                 isException : false 
735             };
736         }
737         success = result.success;
738
739         if (success) {
740             response = me.createResponse(request);
741             me.fireEvent('requestcomplete', me, response, options);
742             Ext.callback(options.success, options.scope, [response, options]);
743         } else {
744             if (result.isException || request.aborted || request.timedout) {
745                 response = me.createException(request);
746             } else {
747                 response = me.createResponse(request);
748             }
749             me.fireEvent('requestexception', me, response, options);
750             Ext.callback(options.failure, options.scope, [response, options]);
751         }
752         Ext.callback(options.callback, options.scope, [options, success, response]);
753         delete me.requests[request.id];
754         return response;
755     },
756
757     /**
758      * Check if the response status was successful
759      * @param {Number} status The status code
760      * @return {Object} An object containing success/status state
761      */
762     parseStatus: function(status) {
763         // see: https://prototype.lighthouseapp.com/projects/8886/tickets/129-ie-mangles-http-response-status-code-204-to-1223
764         status = status == 1223 ? 204 : status;
765
766         var success = (status >= 200 && status < 300) || status == 304,
767             isException = false;
768
769         if (!success) {
770             switch (status) {
771                 case 12002:
772                 case 12029:
773                 case 12030:
774                 case 12031:
775                 case 12152:
776                 case 13030:
777                     isException = true;
778                     break;
779             }
780         }
781         return {
782             success: success,
783             isException: isException
784         };
785     },
786
787     /**
788      * Create the response object
789      * @private
790      * @param {Object} request
791      */
792     createResponse : function(request) {
793         var xhr = request.xhr,
794             headers = {},
795             lines = xhr.getAllResponseHeaders().replace(/\r\n/g, '\n').split('\n'),
796             count = lines.length,
797             line, index, key, value, response;
798
799         while (count--) {
800             line = lines[count];
801             index = line.indexOf(':');
802             if(index >= 0) {
803                 key = line.substr(0, index).toLowerCase();
804                 if (line.charAt(index + 1) == ' ') {
805                     ++index;
806                 }
807                 headers[key] = line.substr(index + 1);
808             }
809         }
810
811         request.xhr = null;
812         delete request.xhr;
813
814         response = {
815             request: request,
816             requestId : request.id,
817             status : xhr.status,
818             statusText : xhr.statusText,
819             getResponseHeader : function(header){ return headers[header.toLowerCase()]; },
820             getAllResponseHeaders : function(){ return headers; },
821             responseText : xhr.responseText,
822             responseXML : xhr.responseXML
823         };
824
825         // If we don't explicitly tear down the xhr reference, IE6/IE7 will hold this in the closure of the
826         // functions created with getResponseHeader/getAllResponseHeaders
827         xhr = null;
828         return response;
829     },
830
831     /**
832      * Create the exception object
833      * @private
834      * @param {Object} request
835      */
836     createException : function(request) {
837         return {
838             request : request,
839             requestId : request.id,
840             status : request.aborted ? -1 : 0,
841             statusText : request.aborted ? 'transaction aborted' : 'communication failure',
842             aborted: request.aborted,
843             timedout: request.timedout
844         };
845     }
846 });
847