зеркало из https://github.com/mozilla/gecko-dev.git
Bug 603301 - Add async resource API. [r=mconnor]
The async API is exposed as AsyncResource which is functionally equivalent to Resource. The latter is now a wrapper around AsyncResource.
This commit is contained in:
Родитель
0291e51761
Коммит
772cdce99c
|
@ -20,6 +20,7 @@
|
|||
* Contributor(s):
|
||||
* Dan Mills <thunder@mozilla.com>
|
||||
* Anant Narayanan <anant@kix.in>
|
||||
* Philipp von Weitershausen <philipp@weitershausen.de>
|
||||
*
|
||||
* Alternatively, the contents of this file may be used under the terms of
|
||||
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
||||
|
@ -35,7 +36,7 @@
|
|||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
const EXPORTED_SYMBOLS = ["Resource"];
|
||||
const EXPORTED_SYMBOLS = ["Resource", "AsyncResource"];
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
|
@ -50,24 +51,39 @@ Cu.import("resource://services-sync/ext/Sync.js");
|
|||
Cu.import("resource://services-sync/log4moz.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
|
||||
// = Resource =
|
||||
//
|
||||
// Represents a remote network resource, identified by a URI.
|
||||
function Resource(uri) {
|
||||
/*
|
||||
* AsyncResource represents a remote network resource, identified by a URI.
|
||||
* Create an instance like so:
|
||||
*
|
||||
* let resource = new AsyncResource("http://foobar.com/path/to/resource");
|
||||
*
|
||||
* The 'resource' object has the following methods to issue HTTP requests
|
||||
* of the corresponding HTTP methods:
|
||||
*
|
||||
* get(callback)
|
||||
* put(data, callback)
|
||||
* post(data, callback)
|
||||
* delete(callback)
|
||||
*
|
||||
* 'callback' is a function with the following signature:
|
||||
*
|
||||
* function callback(error, result) {...}
|
||||
*
|
||||
* 'error' will be null on successful requests. Likewise, result will not be
|
||||
* passes (=undefined) when an error occurs. Note that this is independent of
|
||||
* the status of the HTTP response.
|
||||
*/
|
||||
function AsyncResource(uri) {
|
||||
this._log = Log4Moz.repository.getLogger(this._logName);
|
||||
this._log.level =
|
||||
Log4Moz.Level[Utils.prefs.getCharPref("log.logger.network.resources")];
|
||||
this.uri = uri;
|
||||
this._headers = {};
|
||||
this._onComplete = Utils.bind2(this, this._onComplete);
|
||||
}
|
||||
Resource.prototype = {
|
||||
AsyncResource.prototype = {
|
||||
_logName: "Net.Resource",
|
||||
|
||||
// ** {{{ Resource.serverTime }}} **
|
||||
//
|
||||
// Caches the latest server timestamp (X-Weave-Timestamp header).
|
||||
serverTime: null,
|
||||
|
||||
// ** {{{ Resource.authenticator }}} **
|
||||
//
|
||||
// Getter and setter for the authenticator module
|
||||
|
@ -137,7 +153,7 @@ Resource.prototype = {
|
|||
// ** {{{ Resource._createRequest }}} **
|
||||
//
|
||||
// This method returns a new IO Channel for requests to be made
|
||||
// through. It is never called directly, only {{{_request}}} uses it
|
||||
// through. It is never called directly, only {{{_doRequest}}} uses it
|
||||
// to obtain a request channel.
|
||||
//
|
||||
_createRequest: function Res__createRequest() {
|
||||
|
@ -165,14 +181,9 @@ Resource.prototype = {
|
|||
|
||||
_onProgress: function Res__onProgress(channel) {},
|
||||
|
||||
// ** {{{ Resource._request }}} **
|
||||
//
|
||||
// Perform a particular HTTP request on the resource. This method
|
||||
// is never called directly, but is used by the high-level
|
||||
// {{{get}}}, {{{put}}}, {{{post}}} and {{delete}} methods.
|
||||
_request: function Res__request(action, data) {
|
||||
let iter = 0;
|
||||
let channel = this._createRequest();
|
||||
_doRequest: function _doRequest(action, data, callback) {
|
||||
this._callback = callback;
|
||||
let channel = this._channel = this._createRequest();
|
||||
|
||||
if ("undefined" != typeof(data))
|
||||
this._data = data;
|
||||
|
@ -200,29 +211,21 @@ Resource.prototype = {
|
|||
|
||||
// Setup a channel listener so that the actual network operation
|
||||
// is performed asynchronously.
|
||||
let [chanOpen, chanCb] = Sync.withCb(channel.asyncOpen, channel);
|
||||
let listener = new ChannelListener(chanCb, this._onProgress, this._log);
|
||||
let listener = new ChannelListener(this._onComplete, this._onProgress,
|
||||
this._log);
|
||||
channel.requestMethod = action;
|
||||
channel.asyncOpen(listener, null);
|
||||
},
|
||||
|
||||
// The channel listener might get a failure code
|
||||
try {
|
||||
this._data = chanOpen(listener, null);
|
||||
_onComplete: function _onComplete(error, data) {
|
||||
if (error) {
|
||||
this._callback(error);
|
||||
return;
|
||||
}
|
||||
catch(ex) {
|
||||
// Combine the channel stack with this request stack
|
||||
let error = Error(ex.message);
|
||||
let chanStack = [];
|
||||
if (ex.stack)
|
||||
chanStack = ex.stack.trim().split(/\n/).slice(1);
|
||||
let requestStack = error.stack.split(/\n/).slice(1);
|
||||
|
||||
// Strip out the args for the last 2 frames because they're usually HUGE!
|
||||
for (let i = 0; i <= 1; i++)
|
||||
requestStack[i] = requestStack[i].replace(/\(".*"\)@/, "(...)@");
|
||||
|
||||
error.stack = chanStack.concat(requestStack).join("\n");
|
||||
throw error;
|
||||
}
|
||||
this._data = data;
|
||||
let channel = this._channel;
|
||||
let action = channel.requestMethod;
|
||||
|
||||
// Set some default values in-case there's no response header
|
||||
let headers = {};
|
||||
|
@ -246,7 +249,7 @@ Resource.prototype = {
|
|||
this._log.debug(mesg);
|
||||
// Additionally give the full response body when Trace logging
|
||||
if (this._log.level <= Log4Moz.Level.Trace)
|
||||
this._log.trace(action + " body: " + this._data);
|
||||
this._log.trace(action + " body: " + data);
|
||||
|
||||
// This is a server-side safety valve to allow slowing down
|
||||
// clients without hurting performance.
|
||||
|
@ -263,7 +266,7 @@ Resource.prototype = {
|
|||
this._log.debug(action + " cached: " + status);
|
||||
}
|
||||
|
||||
let ret = new String(this._data);
|
||||
let ret = new String(data);
|
||||
ret.headers = headers;
|
||||
ret.status = status;
|
||||
ret.success = success;
|
||||
|
@ -284,22 +287,96 @@ Resource.prototype = {
|
|||
// Do the same type of request but with the new uri
|
||||
if (subject.newUri != "") {
|
||||
this.uri = subject.newUri;
|
||||
return this._request.apply(this, arguments);
|
||||
this._doRequest(action, this._data, this._callback);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
this._callback(null, ret);
|
||||
},
|
||||
|
||||
get: function get(callback) {
|
||||
this._doRequest("GET", undefined, callback);
|
||||
},
|
||||
|
||||
put: function put(data, callback) {
|
||||
if (typeof data == "function")
|
||||
[data, callback] = [undefined, data];
|
||||
this._doRequest("PUT", data, callback);
|
||||
},
|
||||
|
||||
post: function post(data, callback) {
|
||||
if (typeof data == "function")
|
||||
[data, callback] = [undefined, data];
|
||||
this._doRequest("POST", data, callback);
|
||||
},
|
||||
|
||||
delete: function delete(callback) {
|
||||
this._doRequest("DELETE", undefined, callback);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Represent a remote network resource, identified by a URI, with a
|
||||
* synchronous API.
|
||||
*
|
||||
* 'Resource' is not recommended for new code. Use the asynchronous API of
|
||||
* 'AsyncResource' instead.
|
||||
*/
|
||||
function Resource(uri) {
|
||||
AsyncResource.call(this, uri);
|
||||
}
|
||||
Resource.prototype = {
|
||||
|
||||
__proto__: AsyncResource.prototype,
|
||||
|
||||
// ** {{{ Resource.serverTime }}} **
|
||||
//
|
||||
// Caches the latest server timestamp (X-Weave-Timestamp header).
|
||||
serverTime: null,
|
||||
|
||||
// ** {{{ Resource._request }}} **
|
||||
//
|
||||
// Perform a particular HTTP request on the resource. This method
|
||||
// is never called directly, but is used by the high-level
|
||||
// {{{get}}}, {{{put}}}, {{{post}}} and {{delete}} methods.
|
||||
_request: function Res__request(action, data) {
|
||||
let [doRequest, cb] = Sync.withCb(this._doRequest, this);
|
||||
function callback(error, ret) {
|
||||
if (error)
|
||||
cb.throw(error);
|
||||
cb(ret);
|
||||
}
|
||||
|
||||
// The channel listener might get a failure code
|
||||
try {
|
||||
return doRequest(action, data, callback);
|
||||
} catch(ex) {
|
||||
// Combine the channel stack with this request stack
|
||||
let error = Error(ex.message);
|
||||
let chanStack = [];
|
||||
if (ex.stack)
|
||||
chanStack = ex.stack.trim().split(/\n/).slice(1);
|
||||
let requestStack = error.stack.split(/\n/).slice(1);
|
||||
|
||||
// Strip out the args for the last 2 frames because they're usually HUGE!
|
||||
for (let i = 0; i <= 1; i++)
|
||||
requestStack[i] = requestStack[i].replace(/\(".*"\)@/, "(...)@");
|
||||
|
||||
error.stack = chanStack.concat(requestStack).join("\n");
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
// ** {{{ Resource.get }}} **
|
||||
//
|
||||
// Perform an asynchronous HTTP GET for this resource.
|
||||
// onComplete will be called on completion of the request.
|
||||
get: function Res_get() {
|
||||
return this._request("GET");
|
||||
},
|
||||
|
||||
// ** {{{ Resource.get }}} **
|
||||
// ** {{{ Resource.put }}} **
|
||||
//
|
||||
// Perform a HTTP PUT for this resource.
|
||||
put: function Res_put(data) {
|
||||
|
@ -357,10 +434,12 @@ ChannelListener.prototype = {
|
|||
this._data = null;
|
||||
|
||||
// Throw the failure code name (and stop execution)
|
||||
if (!Components.isSuccessCode(status))
|
||||
this._onComplete.throw(Error(Components.Exception("", status).name));
|
||||
if (!Components.isSuccessCode(status)) {
|
||||
this._onComplete(Error(Components.Exception("", status).name));
|
||||
return;
|
||||
}
|
||||
|
||||
this._onComplete(this._data);
|
||||
this._onComplete(null, this._data);
|
||||
},
|
||||
|
||||
onDataAvailable: function Channel_onDataAvail(req, cb, stream, off, count) {
|
||||
|
@ -384,7 +463,7 @@ ChannelListener.prototype = {
|
|||
// Ignore any callbacks if we happen to get any now
|
||||
this.onStopRequest = function() {};
|
||||
this.onDataAvailable = function() {};
|
||||
this._onComplete.throw(Error("Aborting due to channel inactivity."));
|
||||
this._onComplete(Error("Aborting due to channel inactivity."));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче