@qooxdoo/framework
Version:
The JS Framework for Coders
335 lines (294 loc) • 9.94 kB
JavaScript
/* ************************************************************************
qooxdoo - the new era of web development
http://qooxdoo.org
Copyright:
2004-2011 1&1 Internet AG, Germany, http://www.1und1.de
License:
MIT: https://opensource.org/licenses/MIT
See the LICENSE file in the project's top-level directory for details.
Authors:
* Tristan Koch (tristankoch)
************************************************************************ */
/**
* Send HTTP requests and handle responses using the HTTP client API.
*
* Configuration of the request is done with properties. Events are fired for
* various states in the life cycle of a request, such as "success". Request
* data is transparently processed.
*
* Here is how to request a JSON file and listen to the "success" event:
*
* <pre class="javascript">
* var req = new qx.io.request.Xhr("/some/path/file.json");
*
* req.addListener("success", function(e) {
* var req = e.getTarget();
*
* // Response parsed according to the server's
* // response content type, e.g. JSON
* req.getResponse();
* }, this);
*
* // Send request
* req.send();
* </pre>
*
* Some noteable features:
*
* * Abstraction of low-level request
* * Convenient setup using properties
* * Fine-grained events
* * Symbolic phases
* * Transparent processing of request data
* * Stream-lined authentication
* * Automagic parsing of response based on content type
*
* Cross-origin requests are supported, but require browser support
* (see <a href="http://caniuse.com/#search=CORS">caniuse.com</a>) and backend configuration
* (see <a href="https://developer.mozilla.org/en-US/docs/docs/HTTP/Access_control_CORS>MDN</a>).
* Note that IE's <code>XDomainRequest</code> is not currently supported.
* For a cross-browser alternative, consider {@link qx.io.request.Jsonp}.
*
* In order to debug requests, set the environment flag
* <code>qx.debug.io</code>.
*
* Internally uses {@link qx.bom.request.Xhr}.
*/
qx.Class.define("qx.io.request.Xhr",
{
extend: qx.io.request.AbstractRequest,
/**
* @param url {String?} The URL of the resource to request.
* @param method {String?} The HTTP method.
*/
construct: function(url, method) {
if (method !== undefined) {
this.setMethod(method);
}
this.base(arguments, url);
this._parser = this._createResponseParser();
},
// Only document events with transport specific details.
// For a complete list of events, refer to AbstractRequest.
events:
{
/**
* Fired on every change of the transport’s readyState.
*
* See {@link qx.bom.request.Xhr} for available readyStates.
*/
"readyStateChange": "qx.event.type.Event",
/**
* Fired when request completes without error and transport status
* indicates success.
*
* Refer to {@link qx.util.Request#isSuccessful} for a list of HTTP
* status considered successful.
*/
"success": "qx.event.type.Event",
/**
* Fired when request completes without error.
*
* Every request not canceled or aborted completes. This means that
* even requests receiving a response with erroneous HTTP status
* fire a "load" event. If you are only interested in successful
* responses, listen to the {@link #success} event instead.
*/
"load": "qx.event.type.Event",
/**
* Fired when request completes without error but erroneous HTTP status.
*
* Refer to {@link qx.util.Request#isSuccessful} for a list of HTTP
* status considered successful.
*/
"statusError": "qx.event.type.Event"
},
properties:
{
/**
* The HTTP method.
*/
method: {
init: "GET"
},
/**
* Whether the request should be executed asynchronously.
*/
async: {
check: "Boolean",
init: true
},
/**
* The content type to accept. By default, every content type
* is accepted.
*
* Note: Some backends send distinct representations of the same
* resource depending on the content type accepted. For instance,
* a backend may respond with either a JSON (the accept header
* indicates so) or a HTML representation (the default, no accept
* header given).
*/
accept: {
check: "String",
nullable: true
},
/**
* Whether to allow request to be answered from cache.
*
* Allowed values:
*
* * <code>true</code>: Allow caching (Default)
* * <code>false</code>: Prohibit caching. Appends nocache parameter to URL.
* * <code>String</code>: Any Cache-Control request directive
*
* If a string is given, it is inserted in the request's Cache-Control
* header. A request’s Cache-Control header may contain a number of directives
* controlling the behavior of any caches in between client and origin
* server.
*
* * <code>"no-cache"</code>: Force caches to submit request in order to
* validate the freshness of the representation. Note that the requested
* resource may still be served from cache if the representation is
* considered fresh. Use this directive to ensure freshness but save
* bandwidth when possible.
* * <code>"no-store"</code>: Do not keep a copy of the representation under
* any conditions.
*
* See <a href="http://www.mnot.net/cache_docs/#CACHE-CONTROL">
* Caching tutorial</a> for an excellent introduction to Caching in general.
* Refer to the corresponding section in the
* <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9">
* HTTP 1.1 specification</a> for more details and advanced directives.
*
* It is recommended to choose an appropriate Cache-Control directive rather
* than prohibit caching using the nocache parameter.
*/
cache: {
check: function(value) {
return qx.lang.Type.isBoolean(value) ||
qx.lang.Type.isString(value);
},
init: true
}
},
members:
{
/**
* @type {Function} Parser.
*/
_parser: null,
/*
---------------------------------------------------------------------------
CONFIGURE TRANSPORT
---------------------------------------------------------------------------
*/
/**
* Create XHR transport.
*
* @return {qx.bom.request.Xhr} Transport.
*/
_createTransport: function() {
return new qx.bom.request.Xhr();
},
/**
* Get configured URL.
*
* Append request data to URL if HTTP method is GET. Append random
* string to URL if required by value of {@link #cache}.
*
* @return {String} The configured URL.
*/
_getConfiguredUrl: function() {
var url = this.getUrl(),
serializedData;
if (this.getMethod() === "GET" && this.getRequestData()) {
serializedData = this._serializeData(this.getRequestData());
url = qx.util.Uri.appendParamsToUrl(url, serializedData);
}
if (this.getCache() === false) {
// Make sure URL cannot be served from cache and new request is made
url = qx.util.Uri.appendParamsToUrl(url, {nocache: new Date().valueOf()});
}
return url;
},
// overridden
_getConfiguredRequestHeaders: function() {
var headers = {},
isAllowsBody = qx.util.Request.methodAllowsRequestBody(this.getMethod());
// Follow convention to include X-Requested-With header when same origin
if (!qx.util.Request.isCrossDomain(this.getUrl())) {
headers["X-Requested-With"] = "XMLHttpRequest";
}
// Include Cache-Control header if configured
if (qx.lang.Type.isString(this.getCache())) {
headers["Cache-Control"] = this.getCache();
}
// By default, set content-type urlencoded for requests with body
if (this.getRequestData() && isAllowsBody) {
headers["Content-Type"] = "application/x-www-form-urlencoded";
}
// What representations to accept
if (this.getAccept()) {
if (qx.core.Environment.get("qx.debug.io")) {
this.debug("Accepting: '" + this.getAccept() + "'");
}
headers["Accept"] = this.getAccept();
}
return headers;
},
// overridden
_getMethod: function() {
return this.getMethod();
},
// overridden
_isAsync: function() {
return this.isAsync();
},
/*
---------------------------------------------------------------------------
PARSING
---------------------------------------------------------------------------
*/
/**
* Create response parser.
*
* @return {qx.util.ResponseParser} parser.
*/
_createResponseParser: function() {
return new qx.util.ResponseParser();
},
/**
* Returns response parsed with parser determined by content type.
*
* @return {String|Object} The parsed response of the request.
*/
_getParsedResponse: function() {
var response = this._transport.responseType === 'blob' ? this._transport.response : this._transport.responseText,
contentType = this.getResponseContentType() || "",
parsedResponse = "";
try {
parsedResponse = this._parser.parse(response, contentType);
this._parserFailed = false
} catch(e) {
this._parserFailed = true
this.fireDataEvent("parseError", {
error: e,
response: response
});
}
return parsedResponse;
},
/**
* Set parser used to parse response once request has
* completed successfully.
*
* @see qx.util.ResponseParser#setParser
*
* @param parser {String|Function}
* @return {Function} The parser function
*/
setParser: function(parser) {
return this._parser.setParser(parser);
}
}
});