зеркало из https://github.com/mozilla/gecko-dev.git
Bug 669547 - Implement RESTRequest and related components to replace [Async]Resource. r=rnewman
This commit is contained in:
Родитель
fb9b961821
Коммит
821df8b6e7
|
@ -0,0 +1,633 @@
|
|||
/* ***** BEGIN LICENSE BLOCK *****
|
||||
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
||||
*
|
||||
* The contents of this file are subject to the Mozilla Public License Version
|
||||
* 1.1 (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
* http://www.mozilla.org/MPL/
|
||||
*
|
||||
* Software distributed under the License is distributed on an "AS IS" basis,
|
||||
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
||||
* for the specific language governing rights and limitations under the
|
||||
* License.
|
||||
*
|
||||
* The Original Code is Firefox Sync.
|
||||
*
|
||||
* The Initial Developer of the Original Code is
|
||||
* the Mozilla Foundation.
|
||||
* Portions created by the Initial Developer are Copyright (C) 2011
|
||||
* the Initial Developer. All Rights Reserved.
|
||||
*
|
||||
* Contributor(s):
|
||||
* Philipp von Weitershausen <philipp@weitershausen.de>
|
||||
* Richard Newman <rnewman@mozilla.com>
|
||||
* Dan Mills <thunder@mozilla.com>
|
||||
* Anant Narayanan <anant@kix.in>
|
||||
*
|
||||
* 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
|
||||
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
||||
* in which case the provisions of the GPL or the LGPL are applicable instead
|
||||
* of those above. If you wish to allow use of your version of this file only
|
||||
* under the terms of either the GPL or the LGPL, and not to allow others to
|
||||
* use your version of this file under the terms of the MPL, indicate your
|
||||
* decision by deleting the provisions above and replace them with the notice
|
||||
* and other provisions required by the GPL or the LGPL. If you do not delete
|
||||
* the provisions above, a recipient may use your version of this file under
|
||||
* the terms of any one of the MPL, the GPL or the LGPL.
|
||||
*
|
||||
* ***** END LICENSE BLOCK ***** */
|
||||
|
||||
const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
|
||||
|
||||
Cu.import("resource://services-sync/log4moz.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
Cu.import("resource://services-sync/identity.js");
|
||||
Cu.import("resource://services-sync/constants.js");
|
||||
|
||||
const EXPORTED_SYMBOLS = ["RESTRequest", "SyncStorageRequest"];
|
||||
|
||||
const STORAGE_REQUEST_TIMEOUT = 5 * 60; // 5 minutes
|
||||
|
||||
/**
|
||||
* Single use HTTP requests to RESTish resources.
|
||||
*
|
||||
* @param uri
|
||||
* URI for the request. This can be an nsIURI object or a string
|
||||
* that can be used to create one. An exception will be thrown if
|
||||
* the string is not a valid URI.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* (1) Quick GET request:
|
||||
*
|
||||
* new RESTRequest("http://server/rest/resource").get(function (error) {
|
||||
* if (error) {
|
||||
* // Deal with a network error.
|
||||
* processNetworkErrorCode(error.result);
|
||||
* return;
|
||||
* }
|
||||
* if (!this.response.success) {
|
||||
|
||||
* * // Bail out if we're not getting an HTTP 2xx code.
|
||||
* processHTTPError(this.response.status);
|
||||
* return;
|
||||
* }
|
||||
* processData(this.response.body);
|
||||
* });
|
||||
*
|
||||
* (2) Quick PUT request (non-string data is automatically JSONified)
|
||||
*
|
||||
* new RESTRequest("http://server/rest/resource").put(data, function (error) {
|
||||
* ...
|
||||
* });
|
||||
*
|
||||
* (3) Streaming GET
|
||||
*
|
||||
* let request = new RESTRequest("http://server/rest/resource");
|
||||
* request.setHeader("Accept", "application/newlines");
|
||||
* request.onComplete = function (error) {
|
||||
* if (error) {
|
||||
* // Deal with a network error.
|
||||
* processNetworkErrorCode(error.result);
|
||||
* return;
|
||||
* }
|
||||
* callbackAfterRequestHasCompleted()
|
||||
* });
|
||||
* request.onProgress = function () {
|
||||
* if (!this.response.success) {
|
||||
* // Bail out if we're not getting an HTTP 2xx code.
|
||||
* return;
|
||||
* }
|
||||
* // Process body data and reset it so we don't process the same data twice.
|
||||
* processIncrementalData(this.response.body);
|
||||
* this.response.body = "";
|
||||
* });
|
||||
* request.get();
|
||||
*/
|
||||
function RESTRequest(uri) {
|
||||
this.status = this.NOT_SENT;
|
||||
|
||||
// If we don't have an nsIURI object yet, make one. This will throw if
|
||||
// 'uri' isn't a valid URI string.
|
||||
if (!(uri instanceof Ci.nsIURI)) {
|
||||
uri = Services.io.newURI(uri, null, null);
|
||||
}
|
||||
this.uri = uri;
|
||||
|
||||
this._headers = {};
|
||||
this._log = Log4Moz.repository.getLogger(this._logName);
|
||||
this._log.level =
|
||||
Log4Moz.Level[Svc.Prefs.get("log.logger.network.resources")];
|
||||
}
|
||||
RESTRequest.prototype = {
|
||||
|
||||
_logName: "Sync.RESTRequest",
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([
|
||||
Ci.nsIBadCertListener2,
|
||||
Ci.nsIInterfaceRequestor
|
||||
]),
|
||||
|
||||
/*** Public API: ***/
|
||||
|
||||
/**
|
||||
* URI for the request (an nsIURI object).
|
||||
*/
|
||||
uri: null,
|
||||
|
||||
/**
|
||||
* HTTP method (e.g. "GET")
|
||||
*/
|
||||
method: null,
|
||||
|
||||
/**
|
||||
* RESTResponse object
|
||||
*/
|
||||
response: null,
|
||||
|
||||
/**
|
||||
* nsIRequest load flags. Don't do any caching by default.
|
||||
*/
|
||||
loadFlags: Ci.nsIRequest.LOAD_BYPASS_CACHE | Ci.nsIRequest.INHIBIT_CACHING,
|
||||
|
||||
/**
|
||||
* nsIHttpChannel
|
||||
*/
|
||||
channel: null,
|
||||
|
||||
/**
|
||||
* Flag to indicate the status of the request.
|
||||
*
|
||||
* One of NOT_SENT, SENT, IN_PROGRESS, COMPLETED, ABORTED.
|
||||
*/
|
||||
status: null,
|
||||
|
||||
NOT_SENT: 0,
|
||||
SENT: 1,
|
||||
IN_PROGRESS: 2,
|
||||
COMPLETED: 4,
|
||||
ABORTED: 8,
|
||||
|
||||
/**
|
||||
* Request timeout (in seconds, though decimal values can be used for
|
||||
* up to millisecond granularity.)
|
||||
*
|
||||
* 0 for no timeout.
|
||||
*/
|
||||
timeout: null,
|
||||
|
||||
/**
|
||||
* Called when the request has been completed, including failures and
|
||||
* timeouts.
|
||||
*
|
||||
* @param error
|
||||
* Error that occurred while making the request, null if there
|
||||
* was no error.
|
||||
*/
|
||||
onComplete: function onComplete(error) {
|
||||
},
|
||||
|
||||
/**
|
||||
* Called whenever data is being received on the channel. If this throws an
|
||||
* exception, the request is aborted and the exception is passed as the
|
||||
* error to onComplete().
|
||||
*/
|
||||
onProgress: function onProgress() {
|
||||
},
|
||||
|
||||
/**
|
||||
* Set a request header.
|
||||
*/
|
||||
setHeader: function setHeader(name, value) {
|
||||
this._headers[name.toLowerCase()] = value;
|
||||
},
|
||||
|
||||
/**
|
||||
* Perform an HTTP GET.
|
||||
*
|
||||
* @param onComplete
|
||||
* Short-circuit way to set the 'onComplete' method. Optional.
|
||||
* @param onProgress
|
||||
* Short-circuit way to set the 'onProgress' method. Optional.
|
||||
*
|
||||
* @return the request object.
|
||||
*/
|
||||
get: function get(onComplete, onProgress) {
|
||||
return this.dispatch("GET", null, onComplete, onProgress);
|
||||
},
|
||||
|
||||
/**
|
||||
* Perform an HTTP PUT.
|
||||
*
|
||||
* @param data
|
||||
* Data to be used as the request body. If this isn't a string
|
||||
* it will be JSONified automatically.
|
||||
* @param onComplete
|
||||
* Short-circuit way to set the 'onComplete' method. Optional.
|
||||
* @param onProgress
|
||||
* Short-circuit way to set the 'onProgress' method. Optional.
|
||||
*
|
||||
* @return the request object.
|
||||
*/
|
||||
put: function put(data, onComplete, onProgress) {
|
||||
return this.dispatch("PUT", data, onComplete, onProgress);
|
||||
},
|
||||
|
||||
/**
|
||||
* Perform an HTTP POST.
|
||||
*
|
||||
* @param data
|
||||
* Data to be used as the request body. If this isn't a string
|
||||
* it will be JSONified automatically.
|
||||
* @param onComplete
|
||||
* Short-circuit way to set the 'onComplete' method. Optional.
|
||||
* @param onProgress
|
||||
* Short-circuit way to set the 'onProgress' method. Optional.
|
||||
*
|
||||
* @return the request object.
|
||||
*/
|
||||
post: function post(data, onComplete, onProgress) {
|
||||
return this.dispatch("POST", data, onComplete, onProgress);
|
||||
},
|
||||
|
||||
/**
|
||||
* Perform an HTTP DELETE.
|
||||
*
|
||||
* @param onComplete
|
||||
* Short-circuit way to set the 'onComplete' method. Optional.
|
||||
* @param onProgress
|
||||
* Short-circuit way to set the 'onProgress' method. Optional.
|
||||
*
|
||||
* @return the request object.
|
||||
*/
|
||||
delete: function delete_(onComplete, onProgress) {
|
||||
return this.dispatch("DELETE", null, onComplete, onProgress);
|
||||
},
|
||||
|
||||
/**
|
||||
* Abort an active request.
|
||||
*/
|
||||
abort: function abort() {
|
||||
if (this.status != this.SENT && this.status != this.IN_PROGRESS) {
|
||||
throw "Can only abort a request that has been sent.";
|
||||
}
|
||||
|
||||
this.status = this.ABORTED;
|
||||
this.channel.cancel(Cr.NS_BINDING_ABORTED);
|
||||
|
||||
if (this.timeoutTimer) {
|
||||
// Clear the abort timer now that the channel is done.
|
||||
this.timeoutTimer.clear();
|
||||
}
|
||||
},
|
||||
|
||||
/*** Implementation stuff ***/
|
||||
|
||||
dispatch: function dispatch(method, data, onComplete, onProgress) {
|
||||
if (this.status != this.NOT_SENT) {
|
||||
throw "Request has already been sent!";
|
||||
}
|
||||
|
||||
this.method = method;
|
||||
if (onComplete) {
|
||||
this.onComplete = onComplete;
|
||||
}
|
||||
if (onProgress) {
|
||||
this.onProgress = onProgress;
|
||||
}
|
||||
|
||||
// Create and initialize HTTP channel.
|
||||
let channel = Services.io.newChannelFromURI(this.uri, null, null)
|
||||
.QueryInterface(Ci.nsIRequest)
|
||||
.QueryInterface(Ci.nsIHttpChannel);
|
||||
this.channel = channel;
|
||||
channel.loadFlags |= this.loadFlags;
|
||||
channel.notificationCallbacks = this;
|
||||
|
||||
// Set request headers.
|
||||
let headers = this._headers;
|
||||
for (let key in headers) {
|
||||
if (key == 'authorization') {
|
||||
this._log.trace("HTTP Header " + key + ": ***** (suppressed)");
|
||||
} else {
|
||||
this._log.trace("HTTP Header " + key + ": " + headers[key]);
|
||||
}
|
||||
channel.setRequestHeader(key, headers[key], false);
|
||||
}
|
||||
|
||||
// Set HTTP request body.
|
||||
if (method == "PUT" || method == "POST") {
|
||||
// Convert non-string bodies into JSON.
|
||||
if (typeof data != "string") {
|
||||
data = JSON.stringify(data);
|
||||
}
|
||||
|
||||
this._log.debug(method + " Length: " + data.length);
|
||||
if (this._log.level <= Log4Moz.Level.Trace) {
|
||||
this._log.trace(method + " Body: " + data);
|
||||
}
|
||||
|
||||
let stream = Cc["@mozilla.org/io/string-input-stream;1"]
|
||||
.createInstance(Ci.nsIStringInputStream);
|
||||
stream.setData(data, data.length);
|
||||
|
||||
let type = headers["content-type"] || "text/plain";
|
||||
channel.QueryInterface(Ci.nsIUploadChannel);
|
||||
channel.setUploadStream(stream, type, data.length);
|
||||
}
|
||||
// We must set this after setting the upload stream, otherwise it
|
||||
// will always be 'PUT'. Yeah, I know.
|
||||
channel.requestMethod = method;
|
||||
|
||||
// Blast off!
|
||||
channel.asyncOpen(this, null);
|
||||
this.status = this.SENT;
|
||||
this.delayTimeout();
|
||||
return this;
|
||||
},
|
||||
|
||||
/**
|
||||
* Create or push back the abort timer that kills this request.
|
||||
*/
|
||||
delayTimeout: function delayTimeout() {
|
||||
if (this.timeout) {
|
||||
Utils.namedTimer(this.abortTimeout, this.timeout * 1000, this,
|
||||
"timeoutTimer");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Abort the request based on a timeout.
|
||||
*/
|
||||
abortTimeout: function abortTimeout() {
|
||||
this.abort();
|
||||
let error = Components.Exception("Aborting due to channel inactivity.",
|
||||
Cr.NS_ERROR_NET_TIMEOUT);
|
||||
this.onComplete(error);
|
||||
},
|
||||
|
||||
/*** nsIStreamListener ***/
|
||||
|
||||
onStartRequest: function onStartRequest(channel) {
|
||||
if (this.status == this.ABORTED) {
|
||||
this._log.trace("Not proceeding with onStartRequest, request was aborted.");
|
||||
return;
|
||||
}
|
||||
this.status = this.IN_PROGRESS;
|
||||
|
||||
channel.QueryInterface(Ci.nsIHttpChannel);
|
||||
this._log.trace("onStartRequest: " + channel.requestMethod + " " +
|
||||
channel.URI.spec);
|
||||
|
||||
// Create a response object and fill it with some data.
|
||||
let response = this.response = new RESTResponse();
|
||||
response.request = this;
|
||||
response.body = "";
|
||||
|
||||
// Define this here so that we don't have make a new one each time
|
||||
// onDataAvailable() gets called.
|
||||
this._inputStream = Cc["@mozilla.org/scriptableinputstream;1"]
|
||||
.createInstance(Ci.nsIScriptableInputStream);
|
||||
|
||||
this.delayTimeout();
|
||||
},
|
||||
|
||||
onStopRequest: function onStopRequest(channel, context, statusCode) {
|
||||
if (this.timeoutTimer) {
|
||||
// Clear the abort timer now that the channel is done.
|
||||
this.timeoutTimer.clear();
|
||||
}
|
||||
|
||||
// We don't want to do anything for a request that's already been aborted.
|
||||
if (this.status == this.ABORTED) {
|
||||
this._log.trace("Not proceeding with onStopRequest, request was aborted.");
|
||||
return;
|
||||
}
|
||||
this.status = this.COMPLETED;
|
||||
|
||||
let uri = channel && channel.URI && channel.URI.spec || "<unknown>";
|
||||
this._log.trace("Channel for " + channel.requestMethod + " " + uri +
|
||||
" returned status code " + statusCode);
|
||||
|
||||
// Throw the failure code and stop execution. Use Components.Exception()
|
||||
// instead of Error() so the exception is QI-able and can be passed across
|
||||
// XPCOM borders while preserving the status code.
|
||||
if (!Components.isSuccessCode(statusCode)) {
|
||||
let message = Components.Exception("", statusCode).name;
|
||||
let error = Components.Exception(message, statusCode);
|
||||
this.onComplete(error);
|
||||
this.onComplete = this.onProgress = null;
|
||||
return;
|
||||
}
|
||||
|
||||
this._log.debug(this.method + " " + uri + " " + this.response.status);
|
||||
|
||||
// Additionally give the full response body when Trace logging.
|
||||
if (this._log.level <= Log4Moz.Level.Trace) {
|
||||
this._log.trace(this.method + " body: " + this.response.body);
|
||||
}
|
||||
|
||||
delete this._inputStream;
|
||||
|
||||
this.onComplete(null);
|
||||
this.onComplete = this.onProgress = null;
|
||||
},
|
||||
|
||||
onDataAvailable: function onDataAvailable(req, cb, stream, off, count) {
|
||||
this._inputStream.init(stream);
|
||||
try {
|
||||
this.response.body += this._inputStream.read(count);
|
||||
} catch (ex) {
|
||||
this._log.warn("Exception thrown reading " + count +
|
||||
" bytes from the channel.");
|
||||
this._log.debug(Utils.exceptionStr(ex));
|
||||
throw ex;
|
||||
}
|
||||
|
||||
try {
|
||||
this.onProgress();
|
||||
} catch (ex) {
|
||||
this._log.warn("Got exception calling onProgress handler, aborting " +
|
||||
this.method + " " + req.URI.spec);
|
||||
this._log.debug("Exception: " + Utils.exceptionStr(ex));
|
||||
this.abort();
|
||||
this.onComplete(ex);
|
||||
this.onComplete = this.onProgress = null;
|
||||
return;
|
||||
}
|
||||
|
||||
this.delayTimeout();
|
||||
},
|
||||
|
||||
/*** nsIInterfaceRequestor ***/
|
||||
|
||||
getInterface: function(aIID) {
|
||||
return this.QueryInterface(aIID);
|
||||
},
|
||||
|
||||
/*** nsIBadCertListener2 ***/
|
||||
|
||||
notifyCertProblem: function notifyCertProblem(socketInfo, sslStatus, targetHost) {
|
||||
this._log.warn("Invalid HTTPS certificate encountered!");
|
||||
// Suppress invalid HTTPS certificate warnings in the UI.
|
||||
// (The request will still fail.)
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Response object for a RESTRequest. This will be created automatically by
|
||||
* the RESTRequest.
|
||||
*/
|
||||
function RESTResponse() {
|
||||
this._log = Log4Moz.repository.getLogger(this._logName);
|
||||
this._log.level =
|
||||
Log4Moz.Level[Svc.Prefs.get("log.logger.network.resources")];
|
||||
}
|
||||
RESTResponse.prototype = {
|
||||
|
||||
_logName: "Sync.RESTResponse",
|
||||
|
||||
/**
|
||||
* Corresponding REST request
|
||||
*/
|
||||
request: null,
|
||||
|
||||
/**
|
||||
* HTTP status code
|
||||
*/
|
||||
get status() {
|
||||
let status;
|
||||
try {
|
||||
let channel = this.request.channel.QueryInterface(Ci.nsIHttpChannel);
|
||||
status = channel.responseStatus;
|
||||
} catch (ex) {
|
||||
this._log.debug("Caught exception fetching HTTP status code:" +
|
||||
Utils.exceptionStr(ex));
|
||||
return null;
|
||||
}
|
||||
delete this.status;
|
||||
return this.status = status;
|
||||
},
|
||||
|
||||
/**
|
||||
* Boolean flag that indicates whether the HTTP status code is 2xx or not.
|
||||
*/
|
||||
get success() {
|
||||
let success;
|
||||
try {
|
||||
let channel = this.request.channel.QueryInterface(Ci.nsIHttpChannel);
|
||||
success = channel.requestSucceeded;
|
||||
} catch (ex) {
|
||||
this._log.debug("Caught exception fetching HTTP success flag:" +
|
||||
Utils.exceptionStr(ex));
|
||||
return null;
|
||||
}
|
||||
delete this.success;
|
||||
return this.success = success;
|
||||
},
|
||||
|
||||
/**
|
||||
* Object containing HTTP headers (keyed as lower case)
|
||||
*/
|
||||
get headers() {
|
||||
let headers = {};
|
||||
try {
|
||||
this._log.trace("Processing response headers.");
|
||||
let channel = this.request.channel.QueryInterface(Ci.nsIHttpChannel);
|
||||
channel.visitResponseHeaders(function (header, value) {
|
||||
headers[header.toLowerCase()] = value;
|
||||
});
|
||||
} catch (ex) {
|
||||
this._log.debug("Caught exception processing response headers:" +
|
||||
Utils.exceptionStr(ex));
|
||||
return null;
|
||||
}
|
||||
|
||||
delete this.headers;
|
||||
return this.headers = headers;
|
||||
},
|
||||
|
||||
/**
|
||||
* HTTP body (string)
|
||||
*/
|
||||
body: null
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* RESTRequest variant for use against a Sync storage server.
|
||||
*/
|
||||
function SyncStorageRequest(uri) {
|
||||
RESTRequest.call(this, uri);
|
||||
}
|
||||
SyncStorageRequest.prototype = {
|
||||
|
||||
__proto__: RESTRequest.prototype,
|
||||
|
||||
_logName: "Sync.StorageRequest",
|
||||
|
||||
/**
|
||||
* The string to use as the base User-Agent in Sync requests.
|
||||
* These strings will look something like
|
||||
*
|
||||
* Firefox/4.0 FxSync/1.8.0.20100101.mobile
|
||||
*
|
||||
* or
|
||||
*
|
||||
* Firefox Aurora/5.0a1 FxSync/1.9.0.20110409.desktop
|
||||
*/
|
||||
userAgent:
|
||||
Services.appinfo.name + "/" + Services.appinfo.version + // Product.
|
||||
" FxSync/" + WEAVE_VERSION + "." + // Sync.
|
||||
Services.appinfo.appBuildID + ".", // Build.
|
||||
|
||||
/**
|
||||
* Wait 5 minutes before killing a request.
|
||||
*/
|
||||
timeout: STORAGE_REQUEST_TIMEOUT,
|
||||
|
||||
dispatch: function dispatch(method, data, onComplete, onProgress) {
|
||||
// Compose a UA string fragment from the various available identifiers.
|
||||
if (Svc.Prefs.get("sendVersionInfo", true)) {
|
||||
let ua = this.userAgent + Svc.Prefs.get("client.type", "desktop");
|
||||
this.setHeader("user-agent", ua);
|
||||
}
|
||||
|
||||
// Set the BasicAuth header.
|
||||
let id = ID.get("WeaveID");
|
||||
if (id) {
|
||||
let auth_header = "Basic " + btoa(id.username + ':' + id.passwordUTF8);
|
||||
this.setHeader("authorization", auth_header);
|
||||
} else {
|
||||
this._log.debug("Couldn't set Authentication header: WeaveID not found.");
|
||||
}
|
||||
|
||||
RESTRequest.prototype.dispatch.apply(this, arguments);
|
||||
},
|
||||
|
||||
onStartRequest: function onStartRequest(channel) {
|
||||
RESTRequest.prototype.onStartRequest.call(this, channel);
|
||||
|
||||
let headers = this.response.headers;
|
||||
// Save the latest server timestamp when possible.
|
||||
if (headers["x-weave-timestamp"]) {
|
||||
SyncStorageRequest.serverTime = parseFloat(headers["x-weave-timestamp"]);
|
||||
}
|
||||
|
||||
// This is a server-side safety valve to allow slowing down
|
||||
// clients without hurting performance.
|
||||
if (headers["x-weave-backoff"]) {
|
||||
Svc.Obs.notify("weave:service:backoff:interval",
|
||||
parseInt(headers["x-weave-backoff"], 10));
|
||||
}
|
||||
|
||||
if (this.response.success && headers["x-weave-quota-remaining"]) {
|
||||
Svc.Obs.notify("weave:service:quota:remaining",
|
||||
parseInt(headers["x-weave-quota-remaining"], 10));
|
||||
}
|
||||
}
|
||||
};
|
|
@ -208,15 +208,6 @@ function generateNewKeys(collections) {
|
|||
CollectionKeys.setContents(wbo.cleartext, modified);
|
||||
}
|
||||
|
||||
function basic_auth_header(user, password) {
|
||||
return "Basic " + btoa(user + ":" + Utils.encodeUTF8(password));
|
||||
}
|
||||
|
||||
function basic_auth_matches(req, user, password) {
|
||||
return req.hasHeader("Authorization") &&
|
||||
(req.getHeader("Authorization") == basic_auth_header(user, password));
|
||||
}
|
||||
|
||||
function do_check_throws(aFunc, aResult, aStack)
|
||||
{
|
||||
if (!aStack) {
|
||||
|
|
|
@ -15,12 +15,27 @@ function httpd_setup (handlers) {
|
|||
}
|
||||
|
||||
function httpd_handler(statusCode, status, body) {
|
||||
return function(request, response) {
|
||||
return function handler(request, response) {
|
||||
// Allow test functions to inspect the request.
|
||||
request.body = readBytesFromInputStream(request.bodyInputStream);
|
||||
handler.request = request;
|
||||
|
||||
response.setStatusLine(request.httpVersion, statusCode, status);
|
||||
response.bodyOutputStream.write(body, body.length);
|
||||
if (body) {
|
||||
response.bodyOutputStream.write(body, body.length);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function basic_auth_header(user, password) {
|
||||
return "Basic " + btoa(user + ":" + Utils.encodeUTF8(password));
|
||||
}
|
||||
|
||||
function basic_auth_matches(req, user, password) {
|
||||
return req.hasHeader("Authorization") &&
|
||||
(req.getHeader("Authorization") == basic_auth_header(user, password));
|
||||
}
|
||||
|
||||
function httpd_basic_auth_handler(body, metadata, response) {
|
||||
if (basic_auth_matches(metadata, "guest", "guest")) {
|
||||
response.setStatusLine(metadata.httpVersion, 200, "OK, authorized");
|
||||
|
|
|
@ -0,0 +1,581 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
Cu.import("resource://services-sync/rest.js");
|
||||
Cu.import("resource://services-sync/log4moz.js");
|
||||
Cu.import("resource://gre/modules/NetUtil.jsm");
|
||||
|
||||
function run_test() {
|
||||
Log4Moz.repository.getLogger("Sync.RESTRequest").level = Log4Moz.Level.Trace;
|
||||
initTestLogging();
|
||||
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializing a RESTRequest with an invalid URI throws
|
||||
* NS_ERROR_MALFORMED_URI.
|
||||
*/
|
||||
add_test(function test_invalid_uri() {
|
||||
do_check_throws(function() {
|
||||
new RESTRequest("an invalid URI");
|
||||
}, Cr.NS_ERROR_MALFORMED_URI);
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
/**
|
||||
* Verify initial values for attributes.
|
||||
*/
|
||||
add_test(function test_attributes() {
|
||||
let uri = "http://foo.com/bar/baz";
|
||||
let request = new RESTRequest(uri);
|
||||
|
||||
do_check_true(request.uri instanceof Ci.nsIURI);
|
||||
do_check_eq(request.uri.spec, uri);
|
||||
do_check_eq(request.response, null);
|
||||
do_check_eq(request.status, request.NOT_SENT);
|
||||
let expectedLoadFlags = Ci.nsIRequest.LOAD_BYPASS_CACHE |
|
||||
Ci.nsIRequest.INHIBIT_CACHING;
|
||||
do_check_eq(request.loadFlags, expectedLoadFlags);
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
/**
|
||||
* Demonstrate API short-hand: create a request and dispatch it immediately.
|
||||
*/
|
||||
add_test(function test_simple_get() {
|
||||
let handler = httpd_handler(200, "OK", "Huzzah!");
|
||||
let server = httpd_setup({"/resource": handler});
|
||||
|
||||
let uri = "http://localhost:8080/resource";
|
||||
let request = new RESTRequest(uri).get(function (error) {
|
||||
do_check_eq(error, null);
|
||||
|
||||
do_check_eq(this.status, this.COMPLETED);
|
||||
do_check_true(this.response.success);
|
||||
do_check_eq(this.response.status, 200);
|
||||
do_check_eq(this.response.body, "Huzzah!");
|
||||
|
||||
server.stop(run_next_test);
|
||||
});
|
||||
do_check_eq(request.status, request.SENT);
|
||||
do_check_eq(request.method, "GET");
|
||||
});
|
||||
|
||||
/**
|
||||
* Test HTTP GET with all bells and whistles.
|
||||
*/
|
||||
add_test(function test_get() {
|
||||
let handler = httpd_handler(200, "OK", "Huzzah!");
|
||||
let server = httpd_setup({"/resource": handler});
|
||||
|
||||
let request = new RESTRequest("http://localhost:8080/resource");
|
||||
do_check_eq(request.status, request.NOT_SENT);
|
||||
|
||||
request.onProgress = request.onComplete = function () {
|
||||
do_throw("This function should have been overwritten!");
|
||||
};
|
||||
|
||||
let onProgress_called = false;
|
||||
function onProgress() {
|
||||
onProgress_called = true;
|
||||
do_check_eq(this.status, request.IN_PROGRESS);
|
||||
do_check_true(this.response.body.length > 0);
|
||||
|
||||
do_check_true(!!(this.channel.loadFlags & Ci.nsIRequest.LOAD_BYPASS_CACHE));
|
||||
do_check_true(!!(this.channel.loadFlags & Ci.nsIRequest.INHIBIT_CACHING));
|
||||
};
|
||||
|
||||
function onComplete(error) {
|
||||
do_check_eq(error, null);
|
||||
|
||||
do_check_eq(this.status, this.COMPLETED);
|
||||
do_check_true(this.response.success);
|
||||
do_check_eq(this.response.status, 200);
|
||||
do_check_eq(this.response.body, "Huzzah!");
|
||||
do_check_eq(handler.request.method, "GET");
|
||||
|
||||
do_check_true(onProgress_called);
|
||||
Utils.nextTick(function () {
|
||||
do_check_eq(request.onComplete, null);
|
||||
do_check_eq(request.onProgress, null);
|
||||
server.stop(run_next_test);
|
||||
});
|
||||
};
|
||||
|
||||
do_check_eq(request.get(onComplete, onProgress), request);
|
||||
do_check_eq(request.status, request.SENT);
|
||||
do_check_eq(request.method, "GET");
|
||||
do_check_throws(function () {
|
||||
request.get();
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Test HTTP PUT with a simple string argument and default Content-Type.
|
||||
*/
|
||||
add_test(function test_put() {
|
||||
let handler = httpd_handler(200, "OK", "Got it!");
|
||||
let server = httpd_setup({"/resource": handler});
|
||||
|
||||
let request = new RESTRequest("http://localhost:8080/resource");
|
||||
do_check_eq(request.status, request.NOT_SENT);
|
||||
|
||||
request.onProgress = request.onComplete = function () {
|
||||
do_throw("This function should have been overwritten!");
|
||||
};
|
||||
|
||||
let onProgress_called = false;
|
||||
function onProgress() {
|
||||
onProgress_called = true;
|
||||
do_check_eq(this.status, request.IN_PROGRESS);
|
||||
do_check_true(this.response.body.length > 0);
|
||||
};
|
||||
|
||||
function onComplete(error) {
|
||||
do_check_eq(error, null);
|
||||
|
||||
do_check_eq(this.status, this.COMPLETED);
|
||||
do_check_true(this.response.success);
|
||||
do_check_eq(this.response.status, 200);
|
||||
do_check_eq(this.response.body, "Got it!");
|
||||
|
||||
do_check_eq(handler.request.method, "PUT");
|
||||
do_check_eq(handler.request.body, "Hullo?");
|
||||
do_check_eq(handler.request.getHeader("Content-Type"), "text/plain");
|
||||
|
||||
do_check_true(onProgress_called);
|
||||
Utils.nextTick(function () {
|
||||
do_check_eq(request.onComplete, null);
|
||||
do_check_eq(request.onProgress, null);
|
||||
server.stop(run_next_test);
|
||||
});
|
||||
};
|
||||
|
||||
do_check_eq(request.put("Hullo?", onComplete, onProgress), request);
|
||||
do_check_eq(request.status, request.SENT);
|
||||
do_check_eq(request.method, "PUT");
|
||||
do_check_throws(function () {
|
||||
request.put("Hai!");
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Test HTTP POST with a simple string argument and default Content-Type.
|
||||
*/
|
||||
add_test(function test_post() {
|
||||
let handler = httpd_handler(200, "OK", "Got it!");
|
||||
let server = httpd_setup({"/resource": handler});
|
||||
|
||||
let request = new RESTRequest("http://localhost:8080/resource");
|
||||
do_check_eq(request.status, request.NOT_SENT);
|
||||
|
||||
request.onProgress = request.onComplete = function () {
|
||||
do_throw("This function should have been overwritten!");
|
||||
};
|
||||
|
||||
let onProgress_called = false;
|
||||
function onProgress() {
|
||||
onProgress_called = true;
|
||||
do_check_eq(this.status, request.IN_PROGRESS);
|
||||
do_check_true(this.response.body.length > 0);
|
||||
};
|
||||
|
||||
function onComplete(error) {
|
||||
do_check_eq(error, null);
|
||||
|
||||
do_check_eq(this.status, this.COMPLETED);
|
||||
do_check_true(this.response.success);
|
||||
do_check_eq(this.response.status, 200);
|
||||
do_check_eq(this.response.body, "Got it!");
|
||||
|
||||
do_check_eq(handler.request.method, "POST");
|
||||
do_check_eq(handler.request.body, "Hullo?");
|
||||
do_check_eq(handler.request.getHeader("Content-Type"), "text/plain");
|
||||
|
||||
do_check_true(onProgress_called);
|
||||
Utils.nextTick(function () {
|
||||
do_check_eq(request.onComplete, null);
|
||||
do_check_eq(request.onProgress, null);
|
||||
server.stop(run_next_test);
|
||||
});
|
||||
};
|
||||
|
||||
do_check_eq(request.post("Hullo?", onComplete, onProgress), request);
|
||||
do_check_eq(request.status, request.SENT);
|
||||
do_check_eq(request.method, "POST");
|
||||
do_check_throws(function () {
|
||||
request.post("Hai!");
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Test HTTP DELETE.
|
||||
*/
|
||||
add_test(function test_delete() {
|
||||
let handler = httpd_handler(200, "OK", "Got it!");
|
||||
let server = httpd_setup({"/resource": handler});
|
||||
|
||||
let request = new RESTRequest("http://localhost:8080/resource");
|
||||
do_check_eq(request.status, request.NOT_SENT);
|
||||
|
||||
request.onProgress = request.onComplete = function () {
|
||||
do_throw("This function should have been overwritten!");
|
||||
};
|
||||
|
||||
let onProgress_called = false;
|
||||
function onProgress() {
|
||||
onProgress_called = true;
|
||||
do_check_eq(this.status, request.IN_PROGRESS);
|
||||
do_check_true(this.response.body.length > 0);
|
||||
};
|
||||
|
||||
function onComplete(error) {
|
||||
do_check_eq(error, null);
|
||||
|
||||
do_check_eq(this.status, this.COMPLETED);
|
||||
do_check_true(this.response.success);
|
||||
do_check_eq(this.response.status, 200);
|
||||
do_check_eq(this.response.body, "Got it!");
|
||||
do_check_eq(handler.request.method, "DELETE");
|
||||
|
||||
do_check_true(onProgress_called);
|
||||
Utils.nextTick(function () {
|
||||
do_check_eq(request.onComplete, null);
|
||||
do_check_eq(request.onProgress, null);
|
||||
server.stop(run_next_test);
|
||||
});
|
||||
};
|
||||
|
||||
do_check_eq(request.delete(onComplete, onProgress), request);
|
||||
do_check_eq(request.status, request.SENT);
|
||||
do_check_eq(request.method, "DELETE");
|
||||
do_check_throws(function () {
|
||||
request.delete();
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Test an HTTP response with a non-200 status code.
|
||||
*/
|
||||
add_test(function test_get_404() {
|
||||
let handler = httpd_handler(404, "Not Found", "Cannae find it!");
|
||||
let server = httpd_setup({"/resource": handler});
|
||||
|
||||
let request = new RESTRequest("http://localhost:8080/resource");
|
||||
request.get(function (error) {
|
||||
do_check_eq(error, null);
|
||||
|
||||
do_check_eq(this.status, this.COMPLETED);
|
||||
do_check_false(this.response.success);
|
||||
do_check_eq(this.response.status, 404);
|
||||
do_check_eq(this.response.body, "Cannae find it!");
|
||||
|
||||
server.stop(run_next_test);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* The 'data' argument to PUT, if not a string already, is automatically
|
||||
* stringified as JSON.
|
||||
*/
|
||||
add_test(function test_put_json() {
|
||||
let handler = httpd_handler(200, "OK");
|
||||
let server = httpd_setup({"/resource": handler});
|
||||
|
||||
let sample_data = {
|
||||
some: "sample_data",
|
||||
injson: "format",
|
||||
number: 42
|
||||
};
|
||||
let request = new RESTRequest("http://localhost:8080/resource");
|
||||
request.put(sample_data, function (error) {
|
||||
do_check_eq(error, null);
|
||||
|
||||
do_check_eq(this.status, this.COMPLETED);
|
||||
do_check_true(this.response.success);
|
||||
do_check_eq(this.response.status, 200);
|
||||
do_check_eq(this.response.body, "");
|
||||
|
||||
do_check_eq(handler.request.method, "PUT");
|
||||
do_check_eq(handler.request.body, JSON.stringify(sample_data));
|
||||
do_check_eq(handler.request.getHeader("Content-Type"), "text/plain");
|
||||
|
||||
server.stop(run_next_test);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* The 'data' argument to POST, if not a string already, is automatically
|
||||
* stringified as JSON.
|
||||
*/
|
||||
add_test(function test_post_json() {
|
||||
let handler = httpd_handler(200, "OK");
|
||||
let server = httpd_setup({"/resource": handler});
|
||||
|
||||
let sample_data = {
|
||||
some: "sample_data",
|
||||
injson: "format",
|
||||
number: 42
|
||||
};
|
||||
let request = new RESTRequest("http://localhost:8080/resource");
|
||||
request.post(sample_data, function (error) {
|
||||
do_check_eq(error, null);
|
||||
|
||||
do_check_eq(this.status, this.COMPLETED);
|
||||
do_check_true(this.response.success);
|
||||
do_check_eq(this.response.status, 200);
|
||||
do_check_eq(this.response.body, "");
|
||||
|
||||
do_check_eq(handler.request.method, "POST");
|
||||
do_check_eq(handler.request.body, JSON.stringify(sample_data));
|
||||
do_check_eq(handler.request.getHeader("Content-Type"), "text/plain");
|
||||
|
||||
server.stop(run_next_test);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* HTTP PUT with a custom Content-Type header.
|
||||
*/
|
||||
add_test(function test_put_override_content_type() {
|
||||
let handler = httpd_handler(200, "OK");
|
||||
let server = httpd_setup({"/resource": handler});
|
||||
|
||||
let request = new RESTRequest("http://localhost:8080/resource");
|
||||
request.setHeader("Content-Type", "application/lolcat");
|
||||
request.put("O HAI!!1!", function (error) {
|
||||
do_check_eq(error, null);
|
||||
|
||||
do_check_eq(this.status, this.COMPLETED);
|
||||
do_check_true(this.response.success);
|
||||
do_check_eq(this.response.status, 200);
|
||||
do_check_eq(this.response.body, "");
|
||||
|
||||
do_check_eq(handler.request.method, "PUT");
|
||||
do_check_eq(handler.request.body, "O HAI!!1!");
|
||||
do_check_eq(handler.request.getHeader("Content-Type"), "application/lolcat");
|
||||
|
||||
server.stop(run_next_test);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* HTTP POST with a custom Content-Type header.
|
||||
*/
|
||||
add_test(function test_post_override_content_type() {
|
||||
let handler = httpd_handler(200, "OK");
|
||||
let server = httpd_setup({"/resource": handler});
|
||||
|
||||
let request = new RESTRequest("http://localhost:8080/resource");
|
||||
request.setHeader("Content-Type", "application/lolcat");
|
||||
request.post("O HAI!!1!", function (error) {
|
||||
do_check_eq(error, null);
|
||||
|
||||
do_check_eq(this.status, this.COMPLETED);
|
||||
do_check_true(this.response.success);
|
||||
do_check_eq(this.response.status, 200);
|
||||
do_check_eq(this.response.body, "");
|
||||
|
||||
do_check_eq(handler.request.method, "POST");
|
||||
do_check_eq(handler.request.body, "O HAI!!1!");
|
||||
do_check_eq(handler.request.getHeader("Content-Type"), "application/lolcat");
|
||||
|
||||
server.stop(run_next_test);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* No special headers are sent by default on a GET request.
|
||||
*/
|
||||
add_test(function test_get_no_headers() {
|
||||
let handler = httpd_handler(200, "OK");
|
||||
let server = httpd_setup({"/resource": handler});
|
||||
|
||||
let ignore_headers = ["host", "user-agent", "accept", "accept-language",
|
||||
"accept-encoding", "accept-charset", "keep-alive",
|
||||
"connection", "pragma", "cache-control",
|
||||
"content-length"];
|
||||
|
||||
new RESTRequest("http://localhost:8080/resource").get(function (error) {
|
||||
do_check_eq(error, null);
|
||||
|
||||
do_check_eq(this.response.status, 200);
|
||||
do_check_eq(this.response.body, "");
|
||||
|
||||
let server_headers = handler.request.headers;
|
||||
while (server_headers.hasMoreElements()) {
|
||||
let header = server_headers.getNext().toString();
|
||||
if (ignore_headers.indexOf(header) == -1) {
|
||||
do_throw("Got unexpected header!");
|
||||
}
|
||||
}
|
||||
|
||||
server.stop(run_next_test);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Test changing the URI after having created the request.
|
||||
*/
|
||||
add_test(function test_changing_uri() {
|
||||
let handler = httpd_handler(200, "OK");
|
||||
let server = httpd_setup({"/resource": handler});
|
||||
|
||||
let request = new RESTRequest("http://localhost:8080/the-wrong-resource");
|
||||
request.uri = Utils.makeURI("http://localhost:8080/resource");
|
||||
request.get(function (error) {
|
||||
do_check_eq(error, null);
|
||||
do_check_eq(this.response.status, 200);
|
||||
server.stop(run_next_test);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Test setting HTTP request headers.
|
||||
*/
|
||||
add_test(function test_request_setHeader() {
|
||||
let handler = httpd_handler(200, "OK");
|
||||
let server = httpd_setup({"/resource": handler});
|
||||
|
||||
let request = new RESTRequest("http://localhost:8080/resource");
|
||||
|
||||
request.setHeader("X-What-Is-Weave", "awesome");
|
||||
request.setHeader("X-WHAT-is-Weave", "more awesomer");
|
||||
request.setHeader("Another-Header", "Hello World");
|
||||
|
||||
request.get(function (error) {
|
||||
do_check_eq(error, null);
|
||||
|
||||
do_check_eq(this.response.status, 200);
|
||||
do_check_eq(this.response.body, "");
|
||||
|
||||
do_check_eq(handler.request.getHeader("X-What-Is-Weave"), "more awesomer");
|
||||
do_check_eq(handler.request.getHeader("another-header"), "Hello World");
|
||||
|
||||
server.stop(run_next_test);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Test receiving HTTP response headers.
|
||||
*/
|
||||
add_test(function test_response_headers() {
|
||||
function handler(request, response) {
|
||||
response.setHeader("X-What-Is-Weave", "awesome");
|
||||
response.setHeader("Another-Header", "Hello World");
|
||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
||||
}
|
||||
let server = httpd_setup({"/resource": handler});
|
||||
let request = new RESTRequest("http://localhost:8080/resource");
|
||||
|
||||
request.get(function (error) {
|
||||
do_check_eq(error, null);
|
||||
|
||||
do_check_eq(this.response.status, 200);
|
||||
do_check_eq(this.response.body, "");
|
||||
|
||||
do_check_eq(this.response.headers["x-what-is-weave"], "awesome");
|
||||
do_check_eq(this.response.headers["another-header"], "Hello World");
|
||||
|
||||
server.stop(run_next_test);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* The onComplete() handler gets called in case of any network errors
|
||||
* (e.g. NS_ERROR_CONNECTION_REFUSED).
|
||||
*/
|
||||
add_test(function test_connection_refused() {
|
||||
let request = new RESTRequest("http://localhost:8080/resource");
|
||||
request.onProgress = function onProgress() {
|
||||
do_throw("Shouldn't have called request.onProgress()!");
|
||||
};
|
||||
request.get(function (error) {
|
||||
do_check_eq(error.result, Cr.NS_ERROR_CONNECTION_REFUSED);
|
||||
do_check_eq(error.message, "NS_ERROR_CONNECTION_REFUSED");
|
||||
do_check_eq(this.status, this.COMPLETED);
|
||||
run_next_test();
|
||||
});
|
||||
do_check_eq(request.status, request.SENT);
|
||||
});
|
||||
|
||||
/**
|
||||
* Abort a request that just sent off.
|
||||
*/
|
||||
add_test(function test_abort() {
|
||||
function handler() {
|
||||
do_throw("Shouldn't have gotten here!");
|
||||
}
|
||||
let server = httpd_setup({"/resource": handler});
|
||||
|
||||
let request = new RESTRequest("http://localhost:8080/resource");
|
||||
|
||||
// Aborting a request that hasn't been sent yet is pointless and will throw.
|
||||
do_check_throws(function () {
|
||||
request.abort();
|
||||
});
|
||||
|
||||
request.onProgress = request.onComplete = function () {
|
||||
do_throw("Shouldn't have gotten here!");
|
||||
};
|
||||
request.get();
|
||||
request.abort();
|
||||
|
||||
// Aborting an already aborted request is pointless and will throw.
|
||||
do_check_throws(function () {
|
||||
request.abort();
|
||||
});
|
||||
|
||||
do_check_eq(request.status, request.ABORTED);
|
||||
Utils.nextTick(function () {
|
||||
server.stop(run_next_test);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* A non-zero 'timeout' property specifies the amount of seconds to wait after
|
||||
* channel activity until the request is automatically canceled.
|
||||
*/
|
||||
add_test(function test_timeout() {
|
||||
let server = new nsHttpServer();
|
||||
let server_connection;
|
||||
server._handler.handleResponse = function(connection) {
|
||||
// This is a handler that doesn't do anything, just keeps the connection
|
||||
// open, thereby mimicking a timing out connection. We keep a reference to
|
||||
// the open connection for later so it can be properly disposed of. That's
|
||||
// why you really only want to make one HTTP request to this server ever.
|
||||
server_connection = connection;
|
||||
};
|
||||
server.start(8080);
|
||||
|
||||
let request = new RESTRequest("http://localhost:8080/resource");
|
||||
request.timeout = 0.1; // 100 milliseconds
|
||||
request.get(function (error) {
|
||||
do_check_eq(error.result, Cr.NS_ERROR_NET_TIMEOUT);
|
||||
do_check_eq(this.status, this.ABORTED);
|
||||
|
||||
server_connection.close();
|
||||
server.stop(run_next_test);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* An exception thrown in 'onProgress' propagates to the 'onComplete' handler.
|
||||
*/
|
||||
add_test(function test_exception_in_onProgress() {
|
||||
let handler = httpd_handler(200, "OK", "Foobar");
|
||||
let server = httpd_setup({"/resource": handler});
|
||||
|
||||
let request = new RESTRequest("http://localhost:8080/resource");
|
||||
request.onProgress = function onProgress() {
|
||||
it.does.not.exist();
|
||||
};
|
||||
request.get(function (error) {
|
||||
do_check_eq(error, "ReferenceError: it is not defined");
|
||||
do_check_eq(this.status, this.ABORTED);
|
||||
|
||||
server.stop(run_next_test);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,167 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
Cu.import("resource://services-sync/rest.js");
|
||||
Cu.import("resource://services-sync/util.js");
|
||||
Cu.import("resource://services-sync/identity.js");
|
||||
Cu.import("resource://services-sync/constants.js");
|
||||
Cu.import("resource://services-sync/log4moz.js");
|
||||
|
||||
function run_test() {
|
||||
Log4Moz.repository.getLogger("Sync.RESTRequest").level = Log4Moz.Level.Trace;
|
||||
initTestLogging();
|
||||
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
add_test(function test_user_agent_desktop() {
|
||||
let handler = httpd_handler(200, "OK");
|
||||
let server = httpd_setup({"/resource": handler});
|
||||
|
||||
let expectedUA = Services.appinfo.name + "/" + Services.appinfo.version +
|
||||
" FxSync/" + WEAVE_VERSION + "." +
|
||||
Services.appinfo.appBuildID + ".desktop";
|
||||
|
||||
let request = new SyncStorageRequest("http://localhost:8080/resource");
|
||||
request.get(function (error) {
|
||||
do_check_eq(error, null);
|
||||
do_check_eq(this.response.status, 200);
|
||||
do_check_eq(handler.request.getHeader("User-Agent"), expectedUA);
|
||||
server.stop(run_next_test);
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function test_user_agent_mobile() {
|
||||
let handler = httpd_handler(200, "OK");
|
||||
let server = httpd_setup({"/resource": handler});
|
||||
|
||||
Svc.Prefs.set("client.type", "mobile");
|
||||
let expectedUA = Services.appinfo.name + "/" + Services.appinfo.version +
|
||||
" FxSync/" + WEAVE_VERSION + "." +
|
||||
Services.appinfo.appBuildID + ".mobile";
|
||||
|
||||
let request = new SyncStorageRequest("http://localhost:8080/resource");
|
||||
request.get(function (error) {
|
||||
do_check_eq(error, null);
|
||||
do_check_eq(this.response.status, 200);
|
||||
do_check_eq(handler.request.getHeader("User-Agent"), expectedUA);
|
||||
Svc.Prefs.resetBranch("");
|
||||
server.stop(run_next_test);
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function test_auth() {
|
||||
let handler = httpd_handler(200, "OK");
|
||||
let server = httpd_setup({"/resource": handler});
|
||||
|
||||
let id = new Identity(PWDMGR_PASSWORD_REALM, "johndoe");
|
||||
id.password = "ilovejane";
|
||||
ID.set("WeaveID", id);
|
||||
|
||||
let request = new SyncStorageRequest("http://localhost:8080/resource");
|
||||
request.get(function (error) {
|
||||
do_check_eq(error, null);
|
||||
do_check_eq(this.response.status, 200);
|
||||
do_check_true(basic_auth_matches(handler.request, "johndoe", "ilovejane"));
|
||||
|
||||
ID.del("WeaveID");
|
||||
server.stop(run_next_test);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* The X-Weave-Timestamp header updates SyncStorageRequest.serverTime.
|
||||
*/
|
||||
add_test(function test_weave_timestamp() {
|
||||
const TIMESTAMP = 1274380461;
|
||||
function handler(request, response) {
|
||||
response.setHeader("X-Weave-Timestamp", "" + TIMESTAMP, false);
|
||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
||||
}
|
||||
let server = httpd_setup({"/resource": handler});
|
||||
|
||||
do_check_eq(SyncStorageRequest.serverTime, undefined);
|
||||
let request = new SyncStorageRequest("http://localhost:8080/resource");
|
||||
request.get(function (error) {
|
||||
do_check_eq(error, null);
|
||||
do_check_eq(this.response.status, 200);
|
||||
do_check_eq(SyncStorageRequest.serverTime, TIMESTAMP);
|
||||
server.stop(run_next_test);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* The X-Weave-Backoff header notifies an observer.
|
||||
*/
|
||||
add_test(function test_weave_backoff() {
|
||||
function handler(request, response) {
|
||||
response.setHeader("X-Weave-Backoff", '600', false);
|
||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
||||
}
|
||||
let server = httpd_setup({"/resource": handler});
|
||||
|
||||
let backoffInterval;
|
||||
Svc.Obs.add("weave:service:backoff:interval", function onBackoff(subject) {
|
||||
Svc.Obs.remove("weave:service:backoff:interval", onBackoff);
|
||||
backoffInterval = subject;
|
||||
});
|
||||
|
||||
let request = new SyncStorageRequest("http://localhost:8080/resource");
|
||||
request.get(function (error) {
|
||||
do_check_eq(error, null);
|
||||
do_check_eq(this.response.status, 200);
|
||||
do_check_eq(backoffInterval, 600);
|
||||
server.stop(run_next_test);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* X-Weave-Quota-Remaining header notifies observer on successful requests.
|
||||
*/
|
||||
add_test(function test_weave_quota_notice() {
|
||||
function handler(request, response) {
|
||||
response.setHeader("X-Weave-Quota-Remaining", '1048576', false);
|
||||
response.setStatusLine(request.httpVersion, 200, "OK");
|
||||
}
|
||||
let server = httpd_setup({"/resource": handler});
|
||||
|
||||
let quotaValue;
|
||||
Svc.Obs.add("weave:service:quota:remaining", function onQuota(subject) {
|
||||
Svc.Obs.remove("weave:service:quota:remaining", onQuota);
|
||||
quotaValue = subject;
|
||||
});
|
||||
|
||||
let request = new SyncStorageRequest("http://localhost:8080/resource");
|
||||
request.get(function (error) {
|
||||
do_check_eq(error, null);
|
||||
do_check_eq(this.response.status, 200);
|
||||
do_check_eq(quotaValue, 1048576);
|
||||
server.stop(run_next_test);
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* X-Weave-Quota-Remaining header doesn't notify observer on failed requests.
|
||||
*/
|
||||
add_test(function test_weave_quota_error() {
|
||||
function handler(request, response) {
|
||||
response.setHeader("X-Weave-Quota-Remaining", '1048576', false);
|
||||
response.setStatusLine(request.httpVersion, 400, "Bad Request");
|
||||
}
|
||||
let server = httpd_setup({"/resource": handler});
|
||||
|
||||
let quotaValue;
|
||||
function onQuota(subject) {
|
||||
quotaValue = subject;
|
||||
}
|
||||
Svc.Obs.add("weave:service:quota:remaining", onQuota);
|
||||
|
||||
let request = new SyncStorageRequest("http://localhost:8080/resource");
|
||||
request.get(function (error) {
|
||||
do_check_eq(error, null);
|
||||
do_check_eq(this.response.status, 400);
|
||||
do_check_eq(quotaValue, undefined);
|
||||
Svc.Obs.remove("weave:service:quota:remaining", onQuota);
|
||||
server.stop(run_next_test);
|
||||
});
|
||||
});
|
|
@ -48,6 +48,7 @@ tail =
|
|||
[test_resource.js]
|
||||
[test_resource_async.js]
|
||||
[test_resource_ua.js]
|
||||
[test_restrequest.js]
|
||||
[test_score_triggers.js]
|
||||
[test_service_attributes.js]
|
||||
[test_service_changePassword.js]
|
||||
|
@ -76,6 +77,7 @@ tail =
|
|||
[test_syncengine.js]
|
||||
[test_syncengine_sync.js]
|
||||
[test_syncscheduler.js]
|
||||
[test_syncstoragerequest.js]
|
||||
[test_tab_engine.js]
|
||||
[test_tab_store.js]
|
||||
[test_tab_tracker.js]
|
||||
|
|
Загрузка…
Ссылка в новой задаче