/**
 * Connectionクラスにはページの取得元ドメインへの接続がカプセル化されており、設定したURLまたはリクエスト送信時に指定するURLへのリクエスト作成を可能にしています。
 *
 * このクラスで作成されるリクエストは非同期であり、ただちに返されます。{@link #request}呼び出し直後のステートメントにはサーバーからのデータは提供されません。戻り値の処理には、リクエストのoptionsオブジェクトでコールバック関数successを使用するか、{@link #requestcomplete イベントリスナ}を使用します。
 *
 * # ファイルのアップロード
 *
 * ファイルのアップロードは通常の"Ajax"テクニックでは行われません。これは、この処理がXMLHttpRequestsを使用して実行されるのではないためです。その代わりに、ターゲットとして動的に生成される非表示の`<iframe>`(ドキュメントに挿入されるが戻り値収集後に削除される)を参照するように一時的に変更したDOM`<form>`要素を使用した標準的な方法でフォームが送信されます。
 *
 * サーバー応答は、このIFRAMEドキュメントを作成するためにブラウザによってパースされます。サーバーからオブジェクトを返すためにJSONが使用されている場合、テキストを無変更でドキュメント本体に挿入するようブラウザに指示するため、 Content-Typeヘッダーには"text/html"が設定されていることが必要です。
 *
 * HTMLパーサーにとって重要な文字はHTMLエンティティとして送信する必要があるため、`<`は`&lt;`、`&`は`&amp;`というようにエンコードします。
 *
 * 応答テキストはドキュメントから取得され、イベントハンドラとコールバック関数の要件に適合するように、responseTextプロパティを持つ偽XMLHttpRequestオブジェクトが作成されます。
 *
 * ファイルのアップロードパケットはコンテンツタイプがmultipart/formに設定されており、サーバーのテクノロジーによっては(特にJEE)パケットのコンテンツからパラメータの名前や値を取得するのにカスタム処理を必要とする場合があることに注意してください。
 *
 * __注意:__非表示のiframeのレスポンスコードは確認できないため、成功ハンドラは_常に_発火します。
 */
Ext.define('Ext.data.Connection', {
    mixins: {
        observable: 'Ext.mixin.Observable'
    },

    statics: {
        requestId: 0
    },

    config: {
        /**
         * @cfg {String} url
         * サーバーへのリクエストに使用されるデフォルトのURL。
         * @accessor
         */
        url: null,

        async: true,

        /**
         * @cfg {String} [method=undefined]
         * リクエストに使用するデフォルトのHTTPメソッドです。
         *
         * __注意:__大文字と小文字を区別しますので、すべて大文字で記述してください。
         *
         * デフォルト値は`undefined`です。設定されていなくてもパラメータが存在する場合は「POST」を使用し、そうでない場合は「GET」を使用します。
         */
        method: null,

        username: '',
        password: '',

        /**
         * @cfg {Boolean} disableCaching
         * GETリクエストに一意のcache-busterパラメータを追加する場合は、`true`。
         * @accessor
         */
        disableCaching: true,

        /**
         * @cfg {String} disableCachingParam
         * cache-busterによって、キャッシュを無効にするために送信されるパラメータを変更します。
         * @accessor
         */
        disableCachingParam: '_dc',

        /**
         * @cfg {Number} timeout
         * リクエストで使われるタイムアウトの時間をミリ秒で指定します。
         * @accessor
         */
        timeout : 30000,

        /**
         * @cfg {Object} extraParams
         * リクエストに追加するパラメータ。
         * @accessor
         */
        extraParams: null,

        /**
         * @cfg {Object} defaultHeaders
         * このオブジェクトによって生成された各リクエストが追加されたリクエストヘッダーを含むオブジェクトです。
         * @accessor
         */
        defaultHeaders: null,

        useDefaultHeader : true,
        defaultPostHeader : 'application/x-www-form-urlencoded; charset=UTF-8',

        /**
         * @cfg {Boolean} useDefaultXhrHeader
         * falseを設定すると、リクエストごとにデフォルトのXhrヘッダー(X-Requested-With)を送信しません。CORS(クロスドメイン)リクエストを生成する場合にはfalseを設定するべきです。
         * @accessor
         */
        useDefaultXhrHeader : true,

        /**
         * @cfg {String} defaultXhrHeader
         * デフォルトのXhrヘッダーの値(X-Requested-With)。これは、{@link #useDefaultXhrHeader}が`true`に設定されている場合にのみ使用されます。
         */
        defaultXhrHeader : 'XMLHttpRequest',

        autoAbort: false
    },

    textAreaRe: /textarea/i,
    multiPartRe: /multipart\/form-data/i,
    lineBreakRe: /\r\n/g,

    constructor : function(config) {
        this.initConfig(config);

        /**
         * @event beforerequest
         * データオブジェクトを取り出すためにネットワークリクエストが作られる前に発火します。
         * @param {Ext.data.Connection} conn コネクションオブジェクトです。
         * @param {Object} options {@link #request}メソッドに渡されるオプションのコンフィグオブジェクト。
         */
        /**
         * @event requestcomplete
         * リクエストが正常に終了したときに発火します。
         * @param {Ext.data.Connection} conn コネクションオブジェクトです。
         * @param {Object} response レスポンスデータを含むXHRオブジェクトです。詳細は[The XMLHttpRequest Object](http://www.w3.org/TR/XMLHttpRequest/)を参照してください。
         * @param {Object} options {@link #request}メソッドに渡されるオプションのコンフィグオブジェクト。
         */
        /**
         * @event requestexception
         * サーバーからエラーHTTPステータスが返されたときに発火します。HTTPステータスコードの詳細については[HTTP Status Code Definitions](http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html)を参照してください。
         * @param {Ext.data.Connection} conn コネクションオブジェクトです。
         * @param {Object} response レスポンスデータを含むXHRオブジェクトです。詳細は[The XMLHttpRequest Object](http://www.w3.org/TR/XMLHttpRequest/)を参照してください。
         * @param {Object} options {@link #request}メソッドに渡されるオプションのコンフィグオブジェクト。
         */
        this.requests = {};
    },

    /**
     * リモートサーバーにHTTPリクエストを送信します。
     *
     * **重要:**Ajaxサーバーリクエストは非同期で、この呼び出しはレスポンスを受信する前に戻ります。返されたデータを処理するには、コールバック関数を使用します。
     *
     *     Ext.Ajax.request({
     *         url: 'ajax_demo/sample.json',
     *         success: function(response, opts) {
     *             var obj = Ext.decode(response.responseText);
     *             console.dir(obj);
     *         },
     *         failure: function(response, opts) {
     *             console.log('server-side failure with status code ' + response.status);
     *         }
     *     });
     *
     * 正確なスコープでコールバック関数を実行するには、`scope`オプションを使用します。
     *
     * @param {Object} options オブジェクトには次のプロパティがあります。
     *
     * (オプションオブジェクトはコールバック関数に渡されるので、コールバックでポストプロセッシングを実行するのに必要なプロパティを含みます。
     *
     * @param {String/Function} options.url リクエストを送信するURL、またはURL文字列を返す関数です。関数のスコープは`scope`オプションで指定されます。デフォルトでは`url`の設定値です。
     *
     * @param {Object/String/Function} options.params リクエストのパラメータとして使われるプロパティを持つオブジェクト、urlエンコードされた文字列もしくは代わりに呼び出される関数を指定します。関数のスコープは`scope`オプションで指定されます。
     *
     * @param {String} options.method リクエストに使用するHTTPメソッドです。デフォルトは設定されたメソッドですが、メソッドが設定されていなければ、パラメータが送信されない場合は"GET"が、パラメータが送信される場合は"POST"になります。
     *
     * __注意:__メソッドは、大文字と小文字を区別しますので、すべて大文字で記述してください。
     *
     * @param {Function} options.callback HTTPレスポンスの受信時に呼び出される関数です。このコールバックは成功か失敗かにかかわらず呼び出され、次のパラメータが渡されます:
     * @param {Object} options.callback.options リクエスト呼び出しのパラメータ。
     * @param {Boolean} options.callback.success リクエストが成功した場合は、`true`。
     * @param {Object} options.callback.response レスポンスデータを持つXMLHttpRequestオブジェクトです。レスポンスの要素へのアクセスに関する詳細は[www.w3.org/TR/XMLHttpRequest/](http://www.w3.org/TR/XMLHttpRequest/)を参照してください。
     *
     * @param {Function} options.success 同期終了後呼び出される関数です。コールバックには次のパラメータが渡されます。
     * @param {Object} options.success.response レスポンスデータを持つXMLHttpRequestオブジェクトです。
     * @param {Object} options.success.options リクエスト呼び出しのパラメータ。
     *
     * @param {Function} options.failure リクエストが失敗すると呼び出される関数です。コールバックには次のパラメータが渡されます。
     * @param {Object} options.failure.response レスポンスデータを持つXMLHttpRequestオブジェクトです。
     * @param {Object} options.failure.options リクエスト呼び出しのパラメータ。
     *
     * @param {Object} options.scope コールバック関数が実行される時のスコープです。コールバック関数の"this"オブジェクトです。`url`または`params`オプションが値の取得元の関数として指定されている場合、これも関数呼び出しのスコープとして使用されます。デフォルトはブラウザーウィンドウです。
     *
     * @param {Boolean} options.xhr2 このリクエストがFormDataやUploadStatusなどのようなXHR2機能を、利用可能な場合には使用する必要があるかを決定します。
     *
     * @param {Number} [options.timeout=30000] このリクエストで使用されるタイムアウトの時間をミリ秒で指定します。
     *
     * @param {HTMLElement/HTMLElement/String} options.form `<form>`要素またはパラメータを取得する`<form>`のIDです。
     *
     * @param {Boolean} options.isUpload **`form`オプションと共に使用する場合のみ意味を持ちます。**
     *
     * formオブジェクトがファイル のアップロードとなっている場合はtrue(form が**`enctype`** `"multipart/form-data"`と設定されている場合は自動的にセットされます)。
     *
     * ファイルのアップロードは通常の"Ajax"テクニックでは行われません。これは、この処理がXMLHttpRequestsを使用して**実行されるのではない**ためです。その代わりに、ターゲットとして動的に生成される非表示の`<iframe>`(ドキュメントに挿入されるが戻り値収集後に削除される)を参照するように一時的に変更したDOM`<form>`要素を使用した標準的な方法でフォームが送信されます。
     *
     * サーバー応答は、このIFRAMEドキュメントを作成するためにブラウザによってパースされます。サーバーからオブジェクトを返すためにJSONが使用されている場合、テキストを無変更でドキュメント本体に挿入するようブラウザに指示するため、[Content-Type]ヘッダーには"text/html"が設定されていることが必要です。
     *
     * 応答テキストはドキュメントから取得され、イベントハンドラとコールバック関数の要件に適合するように、`responseText`プロパティを持つ偽XMLHttpRequestオブジェクトが作成されます。
     *
     * ファイルのアップロードパケットはコンテンツタイプが[multipart/form]に設定されており、サーバーのテクノロジーによっては(特にJEE)パケットのコンテンツからパラメータの名前や値を取得するのにカスタム処理を必要とする場合があることに注意してください。
     *
     * [target]: http://www.w3.org/TR/REC-html40/present/frames.html#adef-target [Content-Type]: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17 [multipart/form]: http://www.faqs.org/rfcs/rfc2388.html
     *
     * @param {Object} options.headers リクエストに設定されるリクエストヘッダーです。
     *
     * @param {Object} options.xmlData 投稿に使用するXMLドキュメントです。
     *
     * __注意:__これはポストデータのパラメータの代わりに使用します。パラメータはすべてURLに追加されます。
     *
     * @param {Object/String} options.jsonData 投稿として使用するJSONデータです。
     *
     * __注意:__これはポストデータのパラメータの代わりに使用します。パラメータはすべてURLに追加されます。
     *
     * @param {Array} options.binaryData バイナリ形式で送信されるバイト配列です。パラメータはすべてURLに追加されます。返されたデータはバイト配列またはArrayBufferと仮定され、responseBytesに配置されます。他のレスポンスの種類(xml / jsonなど)は提供されません。
     *
     * __注意:__これはポストデータのパラメータの代わりに使用します。パラメータはすべてURLに追加されます。
     *
     * @param {Boolean} options.disableCaching GETリクエストに一意のcache-busterパラメータを追加するにはtrueを設定します。
     *
     * @return {Object/null} リクエストオブジェクト。リクエストをキャンセルするときに使います。
     */
    request: function(options) {
        options = options || {};
        var me = this,
            scope = options.scope || window,
            username = options.username || me.getUsername(),
            password = options.password || me.getPassword() || '',
            useXhr2 = options.xhr2 === true && Ext.feature.has.XHR2,
            async, requestOptions, request, headers, xhr;

        if(!Ext.isEmpty(username) && !Ext.isEmpty(password, true) && Ext.isEmpty(options.withCredentials)){
            options.withCredentials = true;
        }

        if (me.fireEvent('beforerequest', me, options) !== false) {
            requestOptions = me.setOptions(options, scope);

            if (this.isFormUpload(options) === true) {
                this.upload(options.form, requestOptions.url, requestOptions.data, options);
                return null;
            }

            // if autoabort is set, cancel the current transactions
            if (options.autoAbort === true || me.getAutoAbort()) {
                me.abort();
            }

            // create a connection object
            xhr = this.getXhrInstance();

            async = options.async !== false ? (options.async || me.getAsync()) : false;

            // open the request
            if (username) {
                xhr.open(requestOptions.method, requestOptions.url, async, username, password);
            } else {
                xhr.open(requestOptions.method, requestOptions.url, async);
            }

            headers = me.setupHeaders(xhr, options, requestOptions.data, requestOptions.params);

            // create the transaction object
            request = {
                id: ++Ext.data.Connection.requestId,
                xhr: xhr,
                headers: headers,
                options: options,
                async: async,
                timeout: setTimeout(function() {
                    request.timedout = true;
                    me.abort(request);
                }, options.timeout || me.getTimeout())
            };
            me.requests[request.id] = request;


            // bind our onload/statechange listener
            if (async) {
                xhr[useXhr2 ? 'onload' : 'onreadystatechange'] = Ext.Function.bind(me.onStateChange, me, [request]);
            }

            if(useXhr2) {
                xhr.onerror = Ext.Function.bind(me.onStateChange, me, [request]);
            }

            if(options.progress) {
                xhr.onprogress = function(e) {
                    if(options.progress.isProgressable) {
                        if(e.total === 0 && options.progress.getDynamic()) {
                            Ext.Logger.warn("Server is not configured to properly return Content-Length. Dynamic progress will be disabled");
                            options.progress.setState.call(options.progress, "download");
                            options.progress.setDynamic(false);
                            xhr.onprogress = null;
                            return;
                        }

                        Ext.callback(options.progress.updateProgress, options.progress, [(e.loaded / e.total), "download"]);

                        if(e.total > 0 && !options.progress.getDynamic() && options.progress.getInitialConfig().dynamic) {
                            options.progress.setDynamic(true);
                        }
                    }else if(Ext.isFunction(options.progress)) {
                        Ext.callback(options.progress, options.progressScope || request, [e, "download"])
                    }
                };

                if(Ext.feature.has.XHRUploadProgress) {
                        xhr.upload.onprogress = function (e){
                        me.fireEvent('requestuploadprogress', me, request, e);
                        if(options.progress.isProgressable) {
                            Ext.callback(options.progress.updateProgress, options.progress, [(e.loaded / e.total), "upload"]);
                        }else if(Ext.isFunction(options.progress)) {
                            Ext.callback(options.progress, options.progressScope || request, [e, "upload"])
                        }
                    };
                }

                if(options.progress.isProgressable) {
                    if(!Ext.feature.has.XHRUploadProgress) options.progress.setDynamic(false);
                    Ext.callback(options.progress.startProgress, options.progress);
                }
            }

            // start the request!
            xhr.send(requestOptions.data);

            if (!async) {
                return this.onComplete(request);
            }
            return request;
        } else {
            Ext.callback(options.callback, options.scope, [options, undefined, undefined]);
            return null;
        }
    },

    /**
     * 非表示のiframeを使用してフォームをアップロードします。
     * @param {String/HTMLElement/Ext.Element} form アップロードするフォームです。
     * @param {String} url ポストするURLです。
     * @param {String} params 追加パラメータです。
     * @param {Object} options 初期オプションです。
     */
    upload: function(form, url, params, options) {
        form = Ext.getDom(form);
        options = options || {};

        var id = Ext.id(),
            me = this,
            frame = document.createElement('iframe'),
            hiddens = [],
            encoding = 'multipart/form-data',
            buf = {
                target: form.target,
                method: form.method,
                encoding: form.encoding,
                enctype: form.enctype,
                action: form.action
            }, addField = function(name, value) {
            hiddenItem = document.createElement('input');
            Ext.fly(hiddenItem).set({
                type: 'hidden',
                value: value,
                name: name
            });
            form.appendChild(hiddenItem);
            hiddens.push(hiddenItem);
        }, hiddenItem;

        /*
         * 当初、Opera 10においては、この動作はドキュメントへのフレーム追加後にセキュアURLを適用するように変更されました。この動作はその後、Operaにおいて修正され、元の通りURLが追加処理の前にセットされます。
         */
        Ext.fly(frame).set({
            id: id,
            name: id,
            cls: Ext.baseCSSPrefix + 'hide-display',
            src: Ext.SSL_SECURE_URL
        });

        document.body.appendChild(frame);

        // This is required so that IE doesn't pop the response up in a new window.
        if (document.frames) {
            document.frames[id].name = id;
        }

        Ext.fly(form).set({
            target: id,
            method: 'POST',
            enctype: encoding,
            encoding: encoding,
            action: url || buf.action
        });

        // add dynamic params
        if (params) {
            Ext.iterate(Ext.Object.fromQueryString(params), function(name, value) {
                if (Ext.isArray(value)) {
                    Ext.each(value, function(v) {
                        addField(name, v);
                    });
                } else {
                    addField(name, value);
                }
            });
        }

        frame.addEventListener('load',
            function() {
                Ext.callback(me.onUploadComplete, me, [frame, options, id]);
                frame.removeEventListener('load', arguments.callee)
            }
        );
        form.submit();

        Ext.fly(form).set(buf);
        Ext.each(hiddens, function(h) {
            Ext.removeNode(h);
        });
    },

    onUploadComplete: function(frame, options, id) {
        // bogus response object
        var response = {
                responseText: '',
                responseXML: null
            }, doc, firstChild;

        try {
            doc = frame.contentWindow || frame.contentWindow.document || frame.contentDocument || window.frames[id].document;
            if (doc) {
                if (doc.hasOwnProperty("body") && doc.body) {
                    if (this.textAreaRe.test((firstChild = doc.body.firstChild || {}).tagName)) { // json response wrapped in textarea
                        response.responseText = firstChild.value;
                    } else {
                        response.responseText = doc.body.innerHTML;
                    }
                }
                //in IE the document may still have a body even if returns XML.
                response.responseXML = doc.XMLDocument || doc;
            }
        } catch (e) {
            response.success = false;
            response.message = "Cross-Domain access is not permitted between frames. XHR2 is recommended for this type of request.";
            response.error = e;
        }

        this.onAfterUploadComplete(response, frame, options);
    },

    onAfterUploadComplete: function(response, frame, options) {
        var me = this;

        me.fireEvent('requestcomplete', me, response, options);

        Ext.callback(options.success, options.scope, [response, options]);
        Ext.callback(options.callback, options.scope, [options, true, response]);

        setTimeout(function() {
            Ext.removeNode(frame);
        }, 100);
    },

    /**
     * フォームがアップロードのために使用されるかどうかを検出します。
     * @private
     */
    isFormUpload: function(options) {
        var form = this.getForm(options);
        if (form) {
            return (options.isUpload || (this.multiPartRe).test(form.getAttribute('enctype')));
        }
        return false;
    },

    /**
     * オプションからフォームオブジェクトを取得します。
     * @private
     * @param {Object} options リクエストのオプションです。
     * @return {HTMLElement/null} フォーム、渡されない場合は`null`。
     */
    getForm: function(options) {
        return Ext.getDom(options.form) || null;
    },

    /**
     * URLやリクエストのためのパラメータなどの様々なオプションを設定します。
     * @param {Object} options 初期オプションです。
     * @param {Object} scope 実行するスコープ。
     * @return {Object} リクエストのパラメータ。
     */
    setOptions: function(options, scope) {
        var me = this,
            params = options.params || {},
            extraParams = me.getExtraParams(),
            urlParams = options.urlParams,
            url = options.url || me.getUrl(),
            jsonData = options.jsonData,
            method,
            disableCache,
            data;

        // allow params to be a method that returns the params object
        if (Ext.isFunction(params)) {
            params = params.call(scope, options);
        }

        // allow url to be a method that returns the actual url
        if (Ext.isFunction(url)) {
            url = url.call(scope, options);
        }

        url = this.setupUrl(options, url);

        //<debug>
        if (!url) {
            Ext.Logger.error('No URL specified');
        }
        //</debug>

        // check for xml or json data, and make sure json data is encoded
        data = options.data || options.rawData || options.binaryData || options.xmlData || jsonData || null;
        if (jsonData && !Ext.isPrimitive(jsonData)) {
            data = Ext.encode(data);
        }

        // Check for binary data. Transform if needed
        if (options.binaryData) {
            //<debug>
            if (!Ext.isArray(options.binaryData) && !(options.binaryData instanceof Blob)) {
                Ext.Logger.warn("Binary submission data must be an array of byte values or a Blob! Instead got " + typeof(options.binaryData));
            }
            //</debug>
            if (data instanceof Array) {
                data = (new Uint8Array(options.binaryData));
            }
            if (data instanceof Uint8Array) {
                // Note: Newer chrome version (v22 and up) warn that it is deprecated to send the ArrayBuffer and to send the ArrayBufferView instead. For FF this fails so for now send the ArrayBuffer.
                data = data.buffer; //  send the underlying buffer, not the view, since that's not supported on versions of chrome older than 22
            }
        }

        // make sure params are a url encoded string and include any extraParams if specified
        if (Ext.isObject(params)) {
            params = Ext.Object.toQueryString(params);
        }

        if (Ext.isObject(extraParams)) {
            extraParams = Ext.Object.toQueryString(extraParams);
        }

        params = params + ((extraParams) ? ((params) ? '&' : '') + extraParams : '');

        urlParams = Ext.isObject(urlParams) ? Ext.Object.toQueryString(urlParams) : urlParams;

        params = this.setupParams(options, params);

        // decide the proper method for this request
        method = (options.method || me.getMethod() || ((params || data) ? 'POST' : 'GET')).toUpperCase();
        this.setupMethod(options, method);


        disableCache = options.disableCaching !== false ? (options.disableCaching || me.getDisableCaching()) : false;

        // append date to prevent caching
        if (disableCache) {
            url = Ext.urlAppend(url, (options.disableCachingParam || me.getDisableCachingParam()) + '=' + (new Date().getTime()));
        }

        // if the method is get or there is json/xml data append the params to the url
        if ((method == 'GET' || data) && params) {
            url = Ext.urlAppend(url, params);
            params = null;
        }

        // allow params to be forced into the url
        if (urlParams) {
            url = Ext.urlAppend(url, urlParams);
        }

        return {
            url: url,
            method: method,
            data: data || params || null
        };
    },

    /**
     * URLをオーバーライドするためのテンプレートメソッド。
     * @private
     * @param {Object} options
     * @param {String} url
     * @return {String} 変更されたURLです。
     */
    setupUrl: function(options, url) {
        var form = this.getForm(options);
        if (form) {
            url = url || form.action;
        }
        return url;
    },


    /**
     * パラメータをオーバーライドするためのテンプレートメソッド。
     * @private
     * @param {Object} options
     * @param {String} params
     * @return {String} 変更されたパラメータ。
     */
    setupParams: function(options, params) {
        var form = this.getForm(options),
            serializedForm;
        if (form && !this.isFormUpload(options)) {
            serializedForm = Ext.Element.serializeForm(form);
            params = params ? (params + '&' + serializedForm) : serializedForm;
        }
        return params;
    },

    /**
     * メソッドをオーバーライドするためのテンプレートメソッド。
     * @private
     * @param {Object} options
     * @param {String} method
     * @return {String} 変更されたメソッド。
     */
    setupMethod: function(options, method) {
        if (this.isFormUpload(options)) {
            return 'POST';
        }
        return method;
    },

    /**
     * リクエストのすべてのヘッダーを準備します。
     * @private
     * @param {Object} xhr XHRオブジェクト。
     * @param {Object} options リクエストのオプション。
     * @param {Object} data リクエストのデータ。
     * @param {Object} params リクエストのパラメータ。
     */
    setupHeaders: function(xhr, options, data, params) {
        var me = this,
            headers = Ext.apply({}, options.headers || {}, me.getDefaultHeaders() || {}),
            contentType = me.getDefaultPostHeader(),
            jsonData = options.jsonData,
            xmlData = options.xmlData,
            key,
            header;

        if (!headers['Content-Type'] && (data || params)) {
            if (data) {
                if (options.rawData) {
                    contentType = 'text/plain';
                } else {
                    if (xmlData && Ext.isDefined(xmlData)) {
                        contentType = 'text/xml';
                    } else if (jsonData && Ext.isDefined(jsonData)) {
                        contentType = 'application/json';
                    }
                }
            }
            if (!(Ext.feature.has.XHR2 && data instanceof FormData)) {
                headers['Content-Type'] = contentType;
            }
        }

        if (((me.getUseDefaultXhrHeader() && options.useDefaultXhrHeader !== false) || options.useDefaultXhrHeader) && !headers['X-Requested-With']) {
            headers['X-Requested-With'] = me.getDefaultXhrHeader();
        }

        if(!Ext.isEmpty(options.username) && !Ext.isEmpty(options.password)) {
            headers['Authorization'] = "Basic " + btoa(options.username+":"+options.password);
        }

        // set up all the request headers on the xhr object
        try {
            for (key in headers) {
                if (headers.hasOwnProperty(key)) {
                    header = headers[key];
                    xhr.setRequestHeader(key, header);
                }

            }
        } catch(e) {
            me.fireEvent('exception', key, header);
        }

        if(options.responseType) {
            try {
                xhr.responseType = options.responseType === "blob" && Ext.browser.is.Safari ? "arraybuffer" : options.responseType;
            } catch (e) {
                // nothing to do. We're still continuing with the request.
            }
        }

        if (options.withCredentials) {
            xhr.withCredentials = options.withCredentials;
        }

        return headers;
    },

    /**
     * ブラウザに適しているXHRトランスポートを生成します。
     * @private
     */
    getXhrInstance: (function() {
        var options = [function() {
            return new XMLHttpRequest();
        }, function() {
            return new ActiveXObject('MSXML2.XMLHTTP.3.0');
        }, function() {
            return new ActiveXObject('MSXML2.XMLHTTP');
        }, function() {
            return new ActiveXObject('Microsoft.XMLHTTP');
        }], i = 0,
            len = options.length,
            xhr;

        for (; i < len; ++i) {
            try {
                xhr = options[i];
                xhr();
                break;
            } catch(e) {
            }
        }
        return xhr;
    })(),

    /**
     * このオブジェクトが未処理のリクエストをもっているかどうか特定します。
     * @param {Object} request チェックするリクエスト。
     * @return {Boolean} 未処理のリクエストがある場合は、true。
     */
    isLoading : function(request) {
        if (!(request && request.xhr)) {
            return false;
        }
        // if there is a connection and readyState is not 0 or 4
        var state = request.xhr.readyState;
        return !(state === 0 || state == 4);
    },

    /**
     * 未処理の要求を中止します。
     * @param {Object} request (Optional) デフォルト値は最後のリクエストです。
     */
    abort : function(request) {
        var me = this,
            requests = me.requests,
            id;

        if (request && me.isLoading(request)) {
            /*
             * ここでonreadystatechangeをクリアします。これにより、より幅広いコントロールが可能となり、ブラウザでは一連の条件に応じて関数を発火したりしなかったりします。
             */
            request.xhr.onreadystatechange = null;
            request.xhr.abort();
            me.clearTimeout(request);
            if (!request.timedout) {
                request.aborted = true;
            }
            me.onComplete(request);
            me.cleanup(request);
        } else if (!request) {
            for (id in requests) {
                if (requests.hasOwnProperty(id)) {
                    me.abort(requests[id]);
                }
            }
        }
    },

    /**
     * すべての未処理の要求を中止します。
     */
    abortAll: function() {
        this.abort();
    },

    /**
     * XHRの状態が変化したときに発火します。
     * @private
     * @param {Object} request リクエストです。
     */
    onStateChange : function(request) {
        if (request.xhr.readyState == 4) {
            this.clearTimeout(request);
            this.onComplete(request);
            this.cleanup(request);
        }
    },

    /**
     * リクエストのタイムアウトをクリアします。
     * @private
     * @param {Object} The リクエスト
     */
    clearTimeout: function(request) {
        clearTimeout(request.timeout);
        delete request.timeout;
    },

    /**
     * リクエストの残りの情報を除去します。
     * @private
     * @param {Object} The リクエスト。
     */
    cleanup: function(request) {
        request.xhr = null;
        delete request.xhr;
    },

    /**
     * リクエストがサーバーから返ってきたときに呼び出されます。
     * @private
     * @param {Object} request
     * @return {Object} レスポンス。
     */
    onComplete : function(request) {
        var me = this,
            options = request.options,
            result,
            success,
            response;

        try {
            result = me.parseStatus(request.xhr.status, request.xhr);

            if (request.timedout) {
                result.success = false;
            }
        } catch (e) {
            // in some browsers we can't access the status if the readyState is not 4, so the request has failed
            result = {
                success : false,
                isException : false
            };
        }
        success = result.success;

        if (success) {
            response = me.createResponse(request);
            me.fireEvent('requestcomplete', me, response, options);
            Ext.callback(options.success, options.scope, [response, options]);
        } else {
            if (result.isException || request.aborted || request.timedout) {
                response = me.createException(request);
            } else {
                response = me.createResponse(request);
            }
            me.fireEvent('requestexception', me, response, options);
            Ext.callback(options.failure, options.scope, [response, options]);
        }
        Ext.callback(options.callback, options.scope, [options, success, response]);

        if(options.progress && options.progress.isProgressable) {
            Ext.callback(options.progress.endProgress, options.progress, [result]);
        }

        delete me.requests[request.id];
        return response;
    },

    /**
     * レスポンスステータスが成功したかどうかをチェックします。
     * @param {Number} status ステータスコード。
     * @param {XMLHttpRequest} xhr
     * @return {Object} 成功/ステータス状態を含んだオブジェクト。
     */
    parseStatus: function(status, xhr) {
        // see: https://prototype.lighthouseapp.com/projects/8886/tickets/129-ie-mangles-http-response-status-code-204-to-1223
        status = status == 1223 ? 204 : status;

        var success = (status >= 200 && status < 300) || status == 304 || (status == 0 && xhr.responseText && xhr.responseText.length > 0),
            isException = false;

        if (!success) {
            switch (status) {
                case 12002:
                case 12029:
                case 12030:
                case 12031:
                case 12152:
                case 13030:
                    isException = true;
                    break;
            }
        }
        return {
            success: success,
            isException: isException
        };
    },

    /**
     * レスポンスオブジェクトを生成します。
     * @private
     * @param {Object} request
     */
    createResponse : function(request) {
        var xhr = request.xhr,
            headers = {},
            lines, count, line, index, key, response,

            binaryResponse = xhr.responseType === "blob" || xhr.responseType === "arraybuffer",
            textResponse = xhr.responseType === "text",
            documentResponse = xhr.responseType === "document";

        //we need to make this check here because if a request times out an exception is thrown
        //when calling getAllResponseHeaders() because the response never came back to populate it
        if (request.timedout || request.aborted) {
            request.success = false;
            lines = [];
        } else {
            lines = xhr.getAllResponseHeaders().replace(this.lineBreakRe, '\n').split('\n');
        }

        count = lines.length;

        while (count--) {
            line = lines[count];
            index = line.indexOf(':');
            if (index >= 0) {
                key = line.substr(0, index).toLowerCase();
                if (line.charAt(index + 1) == ' ') {
                    ++index;
                }
                headers[key] = line.substr(index + 1);
            }
        }

        request.xhr = null;
        delete request.xhr;
        response = {
            request: request,
            requestId : request.id,
            status : xhr.status,
            statusText : xhr.statusText,
            getResponseHeader : function(header) {
                return headers[header.toLowerCase()];
            },
            getAllResponseHeaders : function() {
                return headers;
            },
            responseText : binaryResponse ? null : documentResponse ? null : xhr.responseText,
            responseXML : binaryResponse ? null : textResponse ? null : xhr.responseXML,
            responseBytes : binaryResponse ? xhr.response : null
        };

        if(request.options.responseType === "blob" && xhr.responseType === "arraybuffer") {
            response.responseBytes = new Blob([response.responseBytes], {type:xhr.getResponseHeader("Content-Type")})
        }

        // If we don't explicitly tear down the xhr reference, IE6/IE7 will hold this in the closure of the
        // functions created with getResponseHeader/getAllResponseHeaders
        xhr = null;
        return response;
    },

    /**
     * 例外オブジェクトを生成します。
     * @private
     * @param {Object} request
     */
    createException : function(request) {
        return {
            request : request,
            requestId : request.id,
            status : request.aborted ? -1 : 0,
            statusText : request.aborted ? 'transaction aborted' : 'communication failure',
            aborted: request.aborted,
            timedout: request.timedout
        };
    }
});