@qooxdoo/framework
Version:
The JS Framework for Coders
1,024 lines (832 loc) • 26.9 kB
JavaScript
/* ************************************************************************
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)
************************************************************************ */
/**
* This class is used to send HTTP requests to the server.
*
* NOTE: Instances of this class must be disposed of after use
*
* Note: This class will be deprecated in a future release. Instead,
* please use classes found in {@link qx.io.request}.
*/
qx.Class.define("qx.io.remote.Request",
{
extend : qx.core.Object,
implement : [ qx.core.IDisposable ],
/*
*****************************************************************************
CONSTRUCTOR
*****************************************************************************
*/
/**
* @param vUrl {String}
* Target url to issue the request to.
*
* @param vMethod {String}
* Determines http method (GET, POST, PUT, etc.) to use. See "method" property
* for valid values and default value.
*
* @param vResponseType {String}
* The mime type of the response. Default is text/plain.
*/
construct : function(vUrl, vMethod, vResponseType)
{
this.base(arguments);
this.__requestHeaders = {};
this.__urlParameters = {};
this.__dataParameters = {};
this.__formFields = {};
if (vUrl !== undefined) {
this.setUrl(vUrl);
}
if (vMethod !== undefined) {
this.setMethod(vMethod);
}
if (vResponseType !== undefined) {
this.setResponseType(vResponseType);
}
this.setProhibitCaching(true);
// Get the next sequence number for this request
this.__seqNum = ++qx.io.remote.Request.__seqNum;
},
/*
*****************************************************************************
EVENTS
*****************************************************************************
*/
events : {
/** Fired when the Request object changes its state to 'created' */
"created" : "qx.event.type.Event",
/** Fired when the Request object changes its state to 'configured' */
"configured" : "qx.event.type.Event",
/** Fired when the Request object changes its state to 'sending' */
"sending" : "qx.event.type.Event",
/** Fired when the Request object changes its state to 'receiving' */
"receiving" : "qx.event.type.Event",
/**
* Fired once the request has finished successfully. The event object
* can be used to read the transferred data.
*/
"completed" : "qx.io.remote.Response",
/** Fired when the pending request has been aborted. */
"aborted" : "qx.event.type.Event",
/** Fired when the pending request fails. */
"failed" : "qx.io.remote.Response",
/** Fired when the pending request times out. */
"timeout" : "qx.io.remote.Response"
},
/*
*****************************************************************************
STATICS
*****************************************************************************
*/
statics :
{
/*
---------------------------------------------------------------------------
SEQUENCE NUMBER
---------------------------------------------------------------------------
*/
/**
* Sequence (id) number of a request, used to associate a response or error
* with its initiating request.
*/
__seqNum : 0,
/**
* Returns true if the given HTTP method allows a request body being transferred to the server.
* This is currently POST and PUT. Other methods require their data being encoded into
* the URL
*
* @param httpMethod {String} one of the values of the method property
* @return {Boolean}
*/
methodAllowsRequestBody : function(httpMethod) {
return (httpMethod == "POST") || (httpMethod == "PUT");
}
},
/*
*****************************************************************************
PROPERTIES
*****************************************************************************
*/
properties :
{
/**
* Target url to issue the request to.
*/
url :
{
check : "String",
init : ""
},
/**
* Determines what type of request to issue (GET, POST, PUT, HEAD, DELETE).
*/
method :
{
check : [ "GET", "POST", "PUT", "HEAD", "DELETE" ],
apply : "_applyMethod",
init : "GET"
},
/**
* Set the request to asynchronous.
*/
asynchronous :
{
check : "Boolean",
init : true
},
/**
* Set the data to be sent via this request
*/
data :
{
check : "String",
nullable : true
},
/**
* Username to use for HTTP authentication.
* Set to NULL if HTTP authentication is not used.
*/
username :
{
check : "String",
nullable : true
},
/**
* Password to use for HTTP authentication.
* Set to NULL if HTTP authentication is not used.
*/
password :
{
check : "String",
nullable : true
},
/**
* The state that the request is in, while being processed.
*/
state :
{
check : [ "configured", "queued", "sending", "receiving", "completed", "aborted", "timeout", "failed" ],
init : "configured",
apply : "_applyState",
event : "changeState"
},
/**
* Response type of request.
*
* The response type is a MIME type, default is text/plain. Other supported
* MIME types are text/javascript, text/html, application/json,
* application/xml.
*/
responseType :
{
check : [ "text/plain", "text/javascript", "application/json", "application/xml", "text/html" ],
init : "text/plain",
apply : "_applyResponseType"
},
/**
* Number of milliseconds before the request is being timed out.
*
* If this property is null, the timeout for the request comes is the
* qx.io.remote.RequestQueue's property defaultTimeout.
*/
timeout :
{
check : "Integer",
nullable : true
},
/**
* Prohibit request from being cached.
*
* Setting the value to <i>true</i> adds a parameter "nocache" to the
* request URL with a value of the current time, as well as adding request
* headers Pragma:no-cache and Cache-Control:no-cache.
*
* Setting the value to <i>false</i> removes the parameter and request
* headers.
*
* As a special case, this property may be set to the string value
* "no-url-params-on-post" which will prevent the nocache parameter from
* being added to the URL if the POST method is used but will still add
* the Pragma and Cache-Control headers. This is useful if your backend
* does nasty things like mixing parameters specified in the URL into
* form fields in the POST request. (One example of this nasty behavior
* is known as "mixed mode" in Oracle, as described here:
* http://docs.oracle.com/cd/B32110_01/web.1013/b28963/concept.htm#i1005684)
*/
prohibitCaching :
{
check : function(v)
{
return typeof v == "boolean" || v === "no-url-params-on-post";
},
init : true,
apply : "_applyProhibitCaching"
},
/**
* Indicate that the request is cross domain.
*
* A request is cross domain if the request's URL points to a host other than
* the local host. This switches the concrete implementation that is used for
* sending the request from qx.io.remote.transport.XmlHttp to
* qx.io.remote.transport.Script, because only the latter can handle cross
* domain requests.
*/
crossDomain :
{
check : "Boolean",
init : false
},
/**
* Indicate that the request will be used for a file upload.
*
* The request will be used for a file upload. This switches the concrete
* implementation that is used for sending the request from
* qx.io.remote.transport.XmlHttp to qx.io.remote.IFrameTransport, because only
* the latter can handle file uploads.
*/
fileUpload :
{
check : "Boolean",
init : false
},
/**
* The transport instance used for the request.
*
* This is necessary to be able to abort an asynchronous request.
*/
transport :
{
check : "qx.io.remote.Exchange",
nullable : true
},
/**
* Use Basic HTTP Authentication.
*/
useBasicHttpAuth :
{
check : "Boolean",
init : false
},
/**
* If true and the responseType property is set to "application/json", getContent() 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.
*
* Note that this is currently only respected by qx.io.remote.transport.XmlHttp, i. e.
* if the transport used is the one using XMLHttpRequests. The other transports
* do not support JSON parsing, so this property has no effect.
*/
parseJson :
{
check : "Boolean",
init : true
}
},
/*
*****************************************************************************
MEMBERS
*****************************************************************************
*/
members :
{
__requestHeaders : null,
__urlParameters : null,
__dataParameters : null,
__formFields : null,
__seqNum : null,
/*
---------------------------------------------------------------------------
CORE METHODS
---------------------------------------------------------------------------
*/
/**
* Schedule this request for transport to server.
*
* The request is added to the singleton class qx.io.remote.RequestQueue's
* list of pending requests.
*
*/
send : function() {
qx.io.remote.RequestQueue.getInstance().add(this);
},
/**
* Abort sending this request.
*
* The request is removed from the singleton class qx.io.remote.RequestQueue's
* list of pending events. If the request haven't been scheduled this
* method is a noop.
*
*/
abort : function() {
qx.io.remote.RequestQueue.getInstance().abort(this);
},
/**
* Abort sending this request if it has not already been aborted.
*
*/
reset : function()
{
switch(this.getState())
{
case "sending":
case "receiving":
this.error("Aborting already sent request!");
// no break
case "queued":
this.abort();
break;
}
},
/*
---------------------------------------------------------------------------
STATE ALIASES
---------------------------------------------------------------------------
*/
/**
* Determine if this request is in the configured state.
*
* @return {Boolean} <true> if the request is in the configured state; <false> otherwise.
*/
isConfigured : function() {
return this.getState() === "configured";
},
/**
* Determine if this request is in the queued state.
*
* @return {Boolean} <true> if the request is in the queued state; <false> otherwise.
*/
isQueued : function() {
return this.getState() === "queued";
},
/**
* Determine if this request is in the sending state.
*
* @return {Boolean} <true> if the request is in the sending state; <false> otherwise.
*/
isSending : function() {
return this.getState() === "sending";
},
/**
* Determine if this request is in the receiving state.
*
* @return {Boolean} <true> if the request is in the receiving state; <false> otherwise.
*/
isReceiving : function() {
return this.getState() === "receiving";
},
/**
* Determine if this request is in the completed state.
*
* @return {Boolean} <true> if the request is in the completed state; <false> otherwise.
*/
isCompleted : function() {
return this.getState() === "completed";
},
/**
* Determine if this request is in the aborted state.
*
* @return {Boolean} <true> if the request is in the aborted state; <false> otherwise.
*/
isAborted : function() {
return this.getState() === "aborted";
},
/**
* Determine if this request is in the timeout state.
*
* @return {Boolean} <true> if the request is in the timeout state; <false> otherwise.
*/
isTimeout : function() {
return this.getState() === "timeout";
},
/**
* Determine if this request is in the failed state.
*
* @return {Boolean} <true> if the request is in the failed state; <false> otherwise.
*/
isFailed : function() {
return this.getState() === "failed";
},
/*
---------------------------------------------------------------------------
EVENT HANDLER
---------------------------------------------------------------------------
*/
/**
* Dispatches a clone of the given event on this instance
*
* @param e {qx.event.type.Event} The original event
*/
__forwardEvent : qx.event.GlobalError.observeMethod(function(e)
{
var clonedEvent = e.clone();
clonedEvent.setTarget(this);
this.dispatchEvent(clonedEvent);
}),
/**
* Event handler called when the request enters the queued state.
*
* @param e {qx.event.type.Event} Event indicating state change
*/
_onqueued : function(e)
{
// Modify internal state
this.setState("queued");
// Bubbling up
this.__forwardEvent(e);
},
/**
* Event handler called when the request enters the sending state.
*
* @param e {qx.event.type.Event} Event indicating state change
*/
_onsending : function(e)
{
// Modify internal state
this.setState("sending");
// Bubbling up
this.__forwardEvent(e);
},
/**
* Event handler called when the request enters the receiving state.
*
* @param e {qx.event.type.Event} Event indicating state change
*/
_onreceiving : function(e)
{
// Modify internal state
this.setState("receiving");
// Bubbling up
this.__forwardEvent(e);
},
/**
* Event handler called when the request enters the completed state.
*
* @param e {qx.event.type.Event} Event indicating state change
*/
_oncompleted : function(e)
{
// Modify internal state
this.setState("completed");
// Bubbling up
this.__forwardEvent(e);
// Automatically dispose after event completion
this.dispose();
},
/**
* Event handler called when the request enters the aborted state.
*
* @param e {qx.event.type.Event} Event indicating state change
*/
_onaborted : function(e)
{
// Modify internal state
this.setState("aborted");
// Bubbling up
this.__forwardEvent(e);
// Automatically dispose after event completion
this.dispose();
},
/**
* Event handler called when the request enters the timeout state.
*
* @param e {qx.event.type.Event} Event indicating state change
*/
_ontimeout : function(e)
{
/*
// User's handler can block until timeout.
switch(this.getState())
{
// If we're no longer running...
case "completed":
case "timeout":
case "aborted":
case "failed":
// then don't bubble up the timeout event
return;
}
*/ // Modify internal state
this.setState("timeout");
// Bubbling up
this.__forwardEvent(e);
// Automatically dispose after event completion
this.dispose();
},
/**
* Event handler called when the request enters the failed state.
*
* @param e {qx.event.type.Event} Event indicating state change
*/
_onfailed : function(e)
{
// Modify internal state
this.setState("failed");
// Bubbling up
this.__forwardEvent(e);
// Automatically dispose after event completion
this.dispose();
},
/*
---------------------------------------------------------------------------
APPLY ROUTINES
---------------------------------------------------------------------------
*/
// property apply
_applyState : function(value, old)
{
if (qx.core.Environment.get("qx.debug"))
{
if (qx.core.Environment.get("qx.debug.io.remote")) {
this.debug("State: " + value);
}
}
},
// property apply
_applyProhibitCaching : function(value, old)
{
if (! value)
{
this.removeParameter("nocache");
this.removeRequestHeader("Pragma");
this.removeRequestHeader("Cache-Control");
return;
}
// If value isn't "no-url-params-on-post" or this isn't a POST request
if (value !== "no-url-params-on-post" ||
this.getMethod() != "POST")
{
// ... then add a parameter to the URL to make it unique on each
// request. The actual id, "nocache" is irrelevant; it's the fact
// that a (usually) different date is added to the URL on each request
// that prevents caching.
this.setParameter("nocache", new Date().valueOf());
}
else
{
// Otherwise, we don't want the nocache parameter in the URL.
this.removeParameter("nocache");
}
// Add the HTTP 1.0 request to avoid use of a cache
this.setRequestHeader("Pragma", "no-cache");
// Add the HTTP 1.1 request to avoid use of a cache
this.setRequestHeader("Cache-Control", "no-cache");
},
// property apply
_applyMethod : function(value, old)
{
if (qx.io.remote.Request.methodAllowsRequestBody(value)) {
this.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
} else {
this.removeRequestHeader("Content-Type");
}
// Re-test the prohibit caching property. We may need to add or remove
// the "nocache" parameter. We explicitly call the _apply method since
// it wouldn't be called normally when setting the value to its already
// existant value.
var prohibitCaching = this.getProhibitCaching();
this._applyProhibitCaching(prohibitCaching, prohibitCaching);
},
// property apply
_applyResponseType : function(value, old) {
this.setRequestHeader("X-Qooxdoo-Response-Type", value);
},
/*
---------------------------------------------------------------------------
REQUEST HEADER
---------------------------------------------------------------------------
*/
/**
* Add a request header to the request.
*
* Example: request.setRequestHeader("Content-Type", "text/html")
*
* Please note: Some browsers, such as Safari 3 and 4, will capitalize
* header field names. This is in accordance with RFC 2616[1], which states
* that HTTP 1.1 header names are case-insensitive, so your server backend
* should be case-agnostic when dealing with request headers.
*
* [1]<a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2">RFC 2616: HTTP Message Headers</a>
*
* @param vId {String} The identifier to use for this added header
* @param vValue {String} The value to use for this added header
*/
setRequestHeader : function(vId, vValue) {
this.__requestHeaders[vId] = vValue;
},
/**
* Remove a previously-added request header
*
* @param vId {String} The id of the header to be removed
*/
removeRequestHeader : function(vId) {
delete this.__requestHeaders[vId];
},
/**
* Retrieve the value of a header which was previously set
*
* @param vId {String} The id of the header value being requested
* @return {String} The value of the header with the specified id
*/
getRequestHeader : function(vId) {
return this.__requestHeaders[vId] || null;
},
/**
* Return the object containing all of the headers which have been added.
*
* @return {Object} The returned object has as its property names each of the ids of headers
* which have been added, and as each property value, the value of the
* property corresponding to that id.
*/
getRequestHeaders : function() {
return this.__requestHeaders;
},
/*
---------------------------------------------------------------------------
PARAMETERS
---------------------------------------------------------------------------
*/
/**
* Add a parameter to the request.
*
* @param vId {String}
* String identifier of the parameter to add.
*
* @param vValue {var}
* Value of parameter. May be a string (for one parameter) or an array
* of strings (for setting multiple parameter values with the same
* parameter name).
*
* @param bAsData {Boolean}
* If <i>false</i>, add the parameter to the URL. If <i>true</i> then
* instead the parameters added by calls to this method will be combined
* into a string added as the request data, as if the entire set of
* parameters had been pre-build and passed to setData().
*
* Note: Parameters requested to be sent as data will be silently dropped
* if data is manually added via a call to setData().
*
* Note: Some transports, e.g. Script, do not support passing parameters
* as data.
*
*/
setParameter : function(vId, vValue, bAsData)
{
if (bAsData)
{
this.__dataParameters[vId] = vValue;
}
else
{
this.__urlParameters[vId] = vValue;
}
},
/**
* Remove a parameter from the request.
*
* @param vId {String}
* Identifier of the parameter to remove.
*
* @param bFromData {Boolean}
* If <i>false</i> then remove the parameter of the URL parameter list.
* If <i>true</i> then remove it from the list of parameters to be sent
* as request data.
*
*/
removeParameter : function(vId, bFromData)
{
if (bFromData)
{
delete this.__dataParameters[vId];
}
else
{
delete this.__urlParameters[vId];
}
},
/**
* Get a parameter in the request.
*
* @param vId {String}
* Identifier of the parameter to get.
*
* @param bFromData {Boolean}
* If <i>false</i> then retrieve the parameter from the URL parameter
* list. If <i>true</i> then retrieve it from the list of parameters to
* be sent as request data.
*
* @return {var}
* The requested parameter value
*
*/
getParameter : function(vId, bFromData)
{
if (bFromData)
{
return this.__dataParameters[vId] || null;
}
else
{
return this.__urlParameters[vId] || null;
}
},
/**
* Returns the object containing all parameters for the request.
*
* @param bFromData {Boolean}
* If <i>false</i> then retrieve the URL parameter list.
* If <i>true</i> then retrieve the data parameter list.
*
* @return {Object}
* The returned object has as its property names each of the ids of
* parameters which have been added, and as each property value, the
* value of the property corresponding to that id.
*/
getParameters : function(bFromData)
{
return (bFromData ? this.__dataParameters : this.__urlParameters);
},
/*
---------------------------------------------------------------------------
FORM FIELDS
---------------------------------------------------------------------------
*/
/**
* Add a form field to the POST request.
*
* NOTE: Adding any programmatic form fields using this method will switch the
* Transport implementation to IframeTransport.
*
* NOTE: Use of these programmatic form fields disallow use of synchronous
* requests and cross-domain requests. Be sure that you do not need
* those features when setting these programmatic form fields.
*
* @param vId {String} String identifier of the form field to add.
* @param vValue {String} Value of form field
*/
setFormField : function(vId, vValue) {
this.__formFields[vId] = vValue;
},
/**
* Remove a form field from the POST request.
*
* @param vId {String} Identifier of the form field to remove.
*/
removeFormField : function(vId) {
delete this.__formFields[vId];
},
/**
* Get a form field in the POST request.
*
* @param vId {String} Identifier of the form field to get.
* @return {String|null} Value of form field or <code>null</code> if no value
* exists for the passed identifier.
*/
getFormField : function(vId) {
return this.__formFields[vId] || null;
},
/**
* Returns the object containing all form fields for the POST request.
*
* @return {Object} The returned object has as its property names each of the ids of
* form fields which have been added, and as each property value, the value
* of the property corresponding to that id.
*/
getFormFields : function() {
return this.__formFields;
},
/**
* Obtain the sequence (id) number used for this request
*
* @return {Integer} The sequence number of this request
*/
getSequenceNumber : function() {
return this.__seqNum;
}
},
/*
*****************************************************************************
DESTRUCTOR
*****************************************************************************
*/
destruct : function()
{
this.setTransport(null);
this.__requestHeaders = this.__urlParameters = this.__dataParameters =
this.__formFields = null;
}
});