/framework/source/class/qx/io/remote/transport/XmlHttp.js
JavaScript | 1016 lines | 544 code | 172 blank | 300 comment | 92 complexity | 1ec2ec06482e1ef137122aa18229db33 MD5 | raw file
Possible License(s): CC-BY-SA-3.0, LGPL-3.0, MIT
- /* ************************************************************************
- qooxdoo - the new era of web development
- http://qooxdoo.org
- Copyright:
- 2004-2008 1&1 Internet AG, Germany, http://www.1und1.de
- 2006 Derrell Lipman
- License:
- MIT: https://opensource.org/licenses/MIT
- See the LICENSE file in the project's top-level directory for details.
- Authors:
- * Sebastian Werner (wpbasti)
- * Andreas Ecker (ecker)
- * Derrell Lipman (derrell)
- ************************************************************************ */
- /**
- * Transports requests to a server using the native XmlHttpRequest object.
- *
- * This class should not be used directly by client programmers.
- */
- qx.Class.define("qx.io.remote.transport.XmlHttp",
- {
- extend : qx.io.remote.transport.Abstract,
- implement: [ qx.core.IDisposable ],
- /*
- *****************************************************************************
- STATICS
- *****************************************************************************
- */
- statics :
- {
- /**
- * Capabilities of this transport type.
- *
- * @internal
- */
- handles :
- {
- synchronous : true,
- asynchronous : true,
- crossDomain : false,
- fileUpload : false,
- programmaticFormFields : false,
- responseTypes : [ "text/plain", "text/javascript", "application/json", "application/xml", "text/html" ]
- },
- /**
- * Return a new XMLHttpRequest object suitable for the client browser.
- *
- * @return {Object} native XMLHttpRequest object
- * @signature function()
- */
- createRequestObject : qx.core.Environment.select("engine.name",
- {
- "default" : function() {
- return new XMLHttpRequest;
- },
- // IE7's native XmlHttp does not care about trusted zones. To make this
- // work in the localhost scenario, you can use the following registry setting:
- //
- // [HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\Main\
- // FeatureControl\FEATURE_XMLHTTP_RESPECT_ZONEPOLICY]
- // "Iexplore.exe"=dword:00000001
- //
- // Generally it seems that the ActiveXObject is more stable. jQuery
- // seems to use it always. We prefer the ActiveXObject for the moment, but allow
- // fallback to XMLHTTP if ActiveX is disabled.
- "mshtml" : function()
- {
- if (window.ActiveXObject && qx.xml.Document.XMLHTTP) {
- return new ActiveXObject(qx.xml.Document.XMLHTTP);
- }
- if (window.XMLHttpRequest) {
- return new XMLHttpRequest;
- }
- }
- }),
- /**
- * Whether the transport type is supported by the client.
- *
- * @return {Boolean} supported or not
- */
- isSupported : function() {
- return !!this.createRequestObject();
- },
-
-
- /** The timeout for Xhr requests */
- __timeout: 0,
-
-
- /**
- * Sets the timeout for requests
- * @deprecated {6.0} This method is deprecated from the start because synchronous I/O itself is deprecated
- * in the W3C spec {@link https://xhr.spec.whatwg.org/} and timeouts are indicative of synchronous I/O and/or
- * other server issues. However, this API is still supported by many browsers and this API is useful
- * for code which has not made the transition to asynchronous I/O
- */
- setTimeout: function(timeout) {
- this.__timeout = timeout;
- },
-
-
- /**
- * Returns the timeout for requests
- */
- getTimeout: function() {
- return this.__timeout;
- }
- },
- /*
- *****************************************************************************
- PROPERTIES
- *****************************************************************************
- */
- properties :
- {
- /**
- * If true and the responseType property is set to "application/json", getResponseContent() will
- * return a Javascript map containing the JSON contents, i. e. the result qx.lang.Json.parse().
- * If false, the raw string data will be returned and the parsing must be done manually.
- * This is useful for special JSON dialects / extensions which are not supported by
- * qx.lang.Json.
- */
- parseJson :
- {
- check : "Boolean",
- init : true
- }
- },
- /*
- *****************************************************************************
- MEMBERS
- *****************************************************************************
- */
- members :
- {
- /*
- ---------------------------------------------------------------------------
- CORE METHODS
- ---------------------------------------------------------------------------
- */
- __localRequest : false,
- __lastReadyState : 0,
- __request : null,
- /**
- * Returns the native request object
- *
- * @return {Object} native XmlHTTPRequest object
- */
- getRequest : function()
- {
- if (this.__request === null)
- {
- this.__request = qx.io.remote.transport.XmlHttp.createRequestObject();
- this.__request.onreadystatechange = qx.lang.Function.bind(this._onreadystatechange, this);
- }
- return this.__request;
- },
- /*
- ---------------------------------------------------------------------------
- USER METHODS
- ---------------------------------------------------------------------------
- */
- /**
- * Implementation for sending the request
- *
- */
- send : function()
- {
- this.__lastReadyState = 0;
- var vRequest = this.getRequest();
- var vMethod = this.getMethod();
- var vAsynchronous = this.getAsynchronous();
- var vUrl = this.getUrl();
- // --------------------------------------
- // Local handling
- // --------------------------------------
- var vLocalRequest = (window.location.protocol === "file:" && !(/^http(s){0,1}\:/.test(vUrl)));
- this.__localRequest = vLocalRequest;
- // --------------------------------------
- // Adding URL parameters
- // --------------------------------------
- var vParameters = this.getParameters(false);
- var vParametersList = [];
- for (var vId in vParameters)
- {
- var value = vParameters[vId];
- if (value instanceof Array)
- {
- for (var i=0; i<value.length; i++) {
- vParametersList.push(encodeURIComponent(vId) + "=" + encodeURIComponent(value[i]));
- }
- }
- else
- {
- vParametersList.push(encodeURIComponent(vId) + "=" + encodeURIComponent(value));
- }
- }
- if (vParametersList.length > 0) {
- vUrl += (vUrl.indexOf("?") >= 0 ? "&" : "?") + vParametersList.join("&");
- }
- // --------------------------------------------------------
- // Adding data parameters (if no data is already present)
- // --------------------------------------------------------
- if (this.getData() === null)
- {
- var vParameters = this.getParameters(true);
- var vParametersList = [];
- for (var vId in vParameters)
- {
- var value = vParameters[vId];
- if (value instanceof Array)
- {
- for (var i=0; i<value.length; i++)
- {
- vParametersList.push(encodeURIComponent(vId) +
- "=" +
- encodeURIComponent(value[i]));
- }
- }
- else
- {
- vParametersList.push(encodeURIComponent(vId) +
- "=" +
- encodeURIComponent(value));
- }
- }
- if (vParametersList.length > 0)
- {
- this.setData(vParametersList.join("&"));
- }
- }
- var encode64 = function(input)
- {
- var keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
- var output = "";
- var chr1, chr2, chr3;
- var enc1, enc2, enc3, enc4;
- var i = 0;
- do
- {
- chr1 = input.charCodeAt(i++);
- chr2 = input.charCodeAt(i++);
- chr3 = input.charCodeAt(i++);
- enc1 = chr1 >> 2;
- enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
- enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
- enc4 = chr3 & 63;
- if (isNaN(chr2)) {
- enc3 = enc4 = 64;
- } else if (isNaN(chr3)) {
- enc4 = 64;
- }
- output += keyStr.charAt(enc1) + keyStr.charAt(enc2) + keyStr.charAt(enc3) + keyStr.charAt(enc4);
- }
- while (i < input.length);
- return output;
- };
- // --------------------------------------
- // Opening connection
- // --------------------------------------
- try
- {
- if (this.getUsername())
- {
- if (this.getUseBasicHttpAuth())
- {
- vRequest.open(vMethod, vUrl, vAsynchronous);
- vRequest.setRequestHeader('Authorization', 'Basic ' + encode64(this.getUsername() + ':' + this.getPassword()));
- }
- else
- {
- vRequest.open(vMethod, vUrl, vAsynchronous, this.getUsername(), this.getPassword());
- }
- }
- else
- {
- vRequest.open(vMethod, vUrl, vAsynchronous);
- }
- }
- catch(ex)
- {
- this.error("Failed with exception: " + ex);
- this.failed();
- return;
- }
- // Apply timeout
- var timeout = qx.io.remote.transport.XmlHttp.getTimeout();
- if (timeout && vAsynchronous) {
- vRequest.timeout = timeout;
- }
- // --------------------------------------
- // Applying request header
- // --------------------------------------
- // Removed adding a referer header as this is not allowed anymore on most
- // browsers
- // See issue https://github.com/qooxdoo/qooxdoo/issues/9298
- var vRequestHeaders = this.getRequestHeaders();
- for (var vId in vRequestHeaders) {
- vRequest.setRequestHeader(vId, vRequestHeaders[vId]);
- }
- // --------------------------------------
- // Sending data
- // --------------------------------------
- try {
- if (qx.core.Environment.get("qx.debug"))
- {
- if (qx.core.Environment.get("qx.debug.io.remote.data"))
- {
- this.debug("Request: " + this.getData());
- }
- }
- // IE9 executes the call synchronous when the call is to file protocol
- // See [BUG #4762] for details
- if (
- vLocalRequest && vAsynchronous &&
- qx.core.Environment.get("engine.name") == "mshtml" &&
- (qx.core.Environment.get("engine.version") == 9 &&
- qx.core.Environment.get("browser.documentmode") == 9)
- ) {
- qx.event.Timer.once(function() {
- vRequest.send(this.getData());
- }, this, 0);
- } else {
- vRequest.send(this.getData());
- }
- }
- catch(ex)
- {
- if (vLocalRequest) {
- this.failedLocally();
- }
- else
- {
- this.error("Failed to send data to URL '" + vUrl + "': " + ex, "send");
- this.failed();
- }
- return;
- }
- // --------------------------------------
- // Readystate for sync requests
- // --------------------------------------
- if (!vAsynchronous) {
- this._onreadystatechange();
- }
- },
- /**
- * Force the transport into the failed state ("failed").
- *
- * This method should be used only if the requests URI was local
- * access. I.e. it started with "file://".
- *
- */
- failedLocally : function()
- {
- if (this.getState() === "failed") {
- return;
- }
- // should only occur on "file://" access
- this.warn("Could not load from file: " + this.getUrl());
- this.failed();
- },
- /*
- ---------------------------------------------------------------------------
- EVENT HANDLER
- ---------------------------------------------------------------------------
- */
- /**
- * Listener method for change of the "readystate".
- * Sets the internal state and informs the transport layer.
- *
- * @signature function(e)
- * @param e {Event} native event
- */
- _onreadystatechange : qx.event.GlobalError.observeMethod(function(e)
- {
- // Ignoring already stopped requests
- switch(this.getState())
- {
- case "completed":
- case "aborted":
- case "failed":
- case "timeout":
- if (qx.core.Environment.get("qx.debug"))
- {
- if (qx.core.Environment.get("qx.debug.io.remote")) {
- this.warn("Ignore Ready State Change");
- }
- }
- return;
- }
- // Checking status code
- var vReadyState = this.getReadyState();
- if (vReadyState == 4)
- {
- // The status code is only meaningful when we reach ready state 4.
- // (Important for Opera since it goes through other states before
- // reaching 4, and the status code is not valid before 4 is reached.)
- if (!qx.io.remote.Exchange.wasSuccessful(this.getStatusCode(), vReadyState, this.__localRequest)) {
- // Fix for bug #2272
- // The IE doesn't set the state to 'sending' even though the send method
- // is called. This only occurs if the server (which is called) goes
- // down or a network failure occurs.
- if (this.getState() === "configured") {
- this.setState("sending");
- }
- this.failed();
- return;
- }
- }
- // Sometimes the xhr call skips the send state
- if (vReadyState == 3 && this.__lastReadyState == 1) {
- this.setState(qx.io.remote.Exchange._nativeMap[++this.__lastReadyState]);
- }
- // Updating internal state
- while (this.__lastReadyState < vReadyState) {
- this.setState(qx.io.remote.Exchange._nativeMap[++this.__lastReadyState]);
- }
- }),
- /*
- ---------------------------------------------------------------------------
- READY STATE
- ---------------------------------------------------------------------------
- */
- /**
- * Get the ready state of this transports request.
- *
- * For qx.io.remote.transport.XmlHttp, ready state is a number between 1 to 4.
- *
- * @return {Integer} ready state number
- */
- getReadyState : function()
- {
- var vReadyState = null;
- try {
- vReadyState = this.getRequest().readyState;
- } catch(ex) {}
- return vReadyState;
- },
- /*
- ---------------------------------------------------------------------------
- REQUEST HEADER SUPPORT
- ---------------------------------------------------------------------------
- */
- /**
- * Set a request header to this transports request.
- *
- * @param vLabel {String} Request header name
- * @param vValue {var} Request header value
- */
- setRequestHeader : function(vLabel, vValue) {
- this.getRequestHeaders()[vLabel] = vValue;
- },
- /*
- ---------------------------------------------------------------------------
- RESPONSE HEADER SUPPORT
- ---------------------------------------------------------------------------
- */
- /**
- * Returns a specific header provided by the server upon sending a request,
- * with header name determined by the argument headerName.
- *
- * Only available at readyState 3 and 4 universally and in readyState 2
- * in Gecko.
- *
- * Please note: Some servers/proxies (such as Selenium RC) will capitalize
- * response header names. This is in accordance with RFC 2616[1], which
- * states that HTTP 1.1 header names are case-insensitive, so your
- * application should be case-agnostic when dealing with response headers.
- *
- * [1]<a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2">RFC 2616: HTTP Message Headers</a>
- *
- * @param vLabel {String} Response header name
- * @return {String|null} Response header value
- */
- getResponseHeader : function(vLabel)
- {
- var vResponseHeader = null;
- try {
- vResponseHeader = this.getRequest().getResponseHeader(vLabel) || null;
- } catch(ex) {}
- return vResponseHeader;
- },
- /**
- * Returns all response headers of the request.
- *
- * @return {var} response headers
- */
- getStringResponseHeaders : function()
- {
- var vSourceHeader = null;
- try
- {
- var vLoadHeader = this.getRequest().getAllResponseHeaders();
- if (vLoadHeader) {
- vSourceHeader = vLoadHeader;
- }
- }
- catch(ex) {}
- return vSourceHeader;
- },
- /**
- * Provides a hash of all response headers.
- *
- * @return {var} hash of all response headers
- */
- getResponseHeaders : function()
- {
- var vSourceHeader = this.getStringResponseHeaders();
- var vHeader = {};
- if (vSourceHeader)
- {
- var vValues = vSourceHeader.split(/[\r\n]+/g);
- for (var i=0, l=vValues.length; i<l; i++)
- {
- var vPair = vValues[i].match(/^([^:]+)\s*:\s*(.+)$/i);
- if (vPair) {
- vHeader[vPair[1]] = vPair[2];
- }
- }
- }
- return vHeader;
- },
- /*
- ---------------------------------------------------------------------------
- STATUS SUPPORT
- ---------------------------------------------------------------------------
- */
- /**
- * Returns the current status code of the request if available or -1 if not.
- *
- * @return {Integer} current status code
- */
- getStatusCode : function()
- {
- var vStatusCode = -1;
- try {
- vStatusCode = this.getRequest().status;
- // [BUG #4476]
- // IE sometimes tells 1223 when it should be 204
- if (vStatusCode === 1223) {
- vStatusCode = 204;
- }
- } catch(ex) {}
- return vStatusCode;
- },
- /**
- * Provides the status text for the current request if available and null
- * otherwise.
- *
- * @return {String} current status code text
- */
- getStatusText : function()
- {
- var vStatusText = "";
- try {
- vStatusText = this.getRequest().statusText;
- } catch(ex) {}
- return vStatusText;
- },
- /*
- ---------------------------------------------------------------------------
- RESPONSE DATA SUPPORT
- ---------------------------------------------------------------------------
- */
- /**
- * Provides the response text from the request when available and null
- * otherwise. By passing true as the "partial" parameter of this method,
- * incomplete data will be made available to the caller.
- *
- * @return {String} Content of the response as string
- */
- getResponseText : function()
- {
- var vResponseText = null;
- try
- {
- vResponseText = this.getRequest().responseText;
- }
- catch(ex)
- {
- vResponseText = null;
- }
- return vResponseText;
- },
- /**
- * Provides the XML provided by the response if any and null otherwise. By
- * passing true as the "partial" parameter of this method, incomplete data will
- * be made available to the caller.
- *
- * @return {String} Content of the response as XML
- * @throws {Error} If an error within the response occurs.
- */
- getResponseXml : function()
- {
- var vResponseXML = null;
- var vStatus = this.getStatusCode();
- var vReadyState = this.getReadyState();
- if (qx.io.remote.Exchange.wasSuccessful(vStatus, vReadyState, this.__localRequest))
- {
- try {
- vResponseXML = this.getRequest().responseXML;
- } catch(ex) {}
- }
- // Typical behaviour on file:// on mshtml
- // Could we check this with something like: /^file\:/.test(path); ?
- // No browser check here, because it doesn't seem to break other browsers
- // * test for this.req.responseXML's objecthood added by *
- // * FRM, 20050816 *
- if (typeof vResponseXML == "object" && vResponseXML != null)
- {
- if (!vResponseXML.documentElement)
- {
- // Clear xml file declaration, this breaks non unicode files (like ones with Umlauts)
- var s = String(this.getRequest().responseText).replace(/<\?xml[^\?]*\?>/, "");
- vResponseXML.loadXML(s);
- }
- // Re-check if fixed...
- if (!vResponseXML.documentElement) {
- throw new Error("Missing Document Element!");
- }
- if (vResponseXML.documentElement.tagName == "parseerror") {
- throw new Error("XML-File is not well-formed!");
- }
- }
- else
- {
- throw new Error("Response was not a valid xml document [" + this.getRequest().responseText + "]");
- }
- return vResponseXML;
- },
- /**
- * Returns the length of the content as fetched thus far
- *
- * @return {Integer} Length of the response text.
- */
- getFetchedLength : function()
- {
- var vText = this.getResponseText();
- return typeof vText == "string" ? vText.length : 0;
- },
- /**
- * Returns the content of the response
- *
- * @return {null | String} Response content if available
- */
- getResponseContent : function()
- {
- var state = this.getState();
- if (state !== "completed" && state != "failed")
- {
- if (qx.core.Environment.get("qx.debug"))
- {
- if (qx.core.Environment.get("qx.debug.io.remote")) {
- this.warn("Transfer not complete or failed, ignoring content!");
- }
- }
- return null;
- }
- if (qx.core.Environment.get("qx.debug"))
- {
- if (qx.core.Environment.get("qx.debug.io.remote")) {
- this.debug("Returning content for responseType: " + this.getResponseType());
- }
- }
- var vText = this.getResponseText();
- if (state == "failed")
- {
- if (qx.core.Environment.get("qx.debug"))
- {
- if (qx.core.Environment.get("qx.debug.io.remote.data"))
- {
- this.debug("Failed: " + vText);
- }
- }
- return vText;
- }
- switch(this.getResponseType())
- {
- case "text/plain":
- case "text/html":
- if (qx.core.Environment.get("qx.debug"))
- {
- if (qx.core.Environment.get("qx.debug.io.remote.data"))
- {
- this.debug("Response: " + vText);
- }
- }
- return vText;
- case "application/json":
- if (qx.core.Environment.get("qx.debug"))
- {
- if (qx.core.Environment.get("qx.debug.io.remote.data"))
- {
- this.debug("Response: " + vText);
- }
- }
- try {
- if (vText && vText.length > 0)
- {
- var ret;
- if (this.getParseJson()){
- ret = qx.lang.Json.parse(vText);
- ret = (ret === 0 ? 0 : (ret || null));
- } else {
- ret = vText;
- }
- return ret;
- }
- else
- {
- return null;
- }
- }
- catch(ex)
- {
- this.error("Could not execute json: [" + vText + "]", ex);
- return "<pre>Could not execute json: \n" + vText + "\n</pre>";
- }
- case "text/javascript":
- if (qx.core.Environment.get("qx.debug"))
- {
- if (qx.core.Environment.get("qx.debug.io.remote.data"))
- {
- this.debug("Response: " + vText);
- }
- }
- try {
- if(vText && vText.length > 0)
- {
- var ret = window.eval(vText);
- return (ret === 0 ? 0 : (ret || null));
- }
- else
- {
- return null;
- }
- } catch(ex) {
- this.error("Could not execute javascript: [" + vText + "]", ex);
- return null;
- }
- case "application/xml":
- vText = this.getResponseXml();
- if (qx.core.Environment.get("qx.debug"))
- {
- if (qx.core.Environment.get("qx.debug.io.remote.data"))
- {
- this.debug("Response: " + vText);
- }
- }
- return (vText === 0 ? 0 : (vText || null));
- default:
- this.warn("No valid responseType specified (" + this.getResponseType() + ")!");
- return null;
- }
- },
- /*
- ---------------------------------------------------------------------------
- APPLY ROUTINES
- ---------------------------------------------------------------------------
- */
- /**
- * Apply method for the "state" property.
- * Fires an event for each state value to inform the listeners.
- *
- * @param value {var} Current value
- * @param old {var} Previous value
- */
- _applyState : function(value, old)
- {
- if (qx.core.Environment.get("qx.debug"))
- {
- if (qx.core.Environment.get("qx.debug.io.remote")) {
- this.debug("State: " + value);
- }
- }
- switch(value)
- {
- case "created":
- this.fireEvent("created");
- break;
- case "configured":
- this.fireEvent("configured");
- break;
- case "sending":
- this.fireEvent("sending");
- break;
- case "receiving":
- this.fireEvent("receiving");
- break;
- case "completed":
- this.fireEvent("completed");
- break;
- case "failed":
- this.fireEvent("failed");
- break;
- case "aborted":
- this.getRequest().abort();
- this.fireEvent("aborted");
- break;
- case "timeout":
- this.getRequest().abort();
- this.fireEvent("timeout");
- break;
- }
- }
- },
- /*
- *****************************************************************************
- DEFER
- *****************************************************************************
- */
- defer : function()
- {
- // basic registration to qx.io.remote.Exchange
- // the real availability check (activeX stuff and so on) follows at the first real request
- qx.io.remote.Exchange.registerType(qx.io.remote.transport.XmlHttp, "qx.io.remote.transport.XmlHttp");
- },
- /*
- *****************************************************************************
- DESTRUCTOR
- *****************************************************************************
- */
- destruct : function()
- {
- var vRequest = this.getRequest();
- if (vRequest)
- {
- // Clean up state change handler
- // Note that for IE the proper way to do this is to set it to a
- // dummy function, not null (Google on "onreadystatechange dummy IE unhook")
- // http://groups.google.com/group/Google-Web-Toolkit-Contributors/browse_thread/thread/7e7ee67c191a6324
- vRequest.onreadystatechange = (function() {});
- // Aborting
- switch(vRequest.readyState)
- {
- case 1:
- case 2:
- case 3:
- vRequest.abort();
- }
- }
- this.__request = null;
- }
- });