diff --git a/lib/XMLHttpRequest.js b/lib/XMLHttpRequest.js index bada957..c551111 100644 --- a/lib/XMLHttpRequest.js +++ b/lib/XMLHttpRequest.js @@ -115,6 +115,24 @@ exports.XMLHttpRequest = function() { // Whether cross-site Access-Control requests should be made using // credentials such as cookies or authorization headers this.withCredentials = false; + + // Number of milliseconds a request can take before automatically + // being terminated. Defaults to 0. + this._timeout = 0; + + // Setting getter and setter to "timeout" field to intercept + // attributions. This is needed to treat the case in which + // the timout value is set after opening the connection + Object.defineProperty(this, "timeout", { + set: function(val) { + if (val > 0 && settings.async === false) { + throw new Error('InvalidAccessError: synchronous XMLHttpRequests do not support timeout and responseType.'); + } + + this._timeout = val; + }, + get: function() { return this._timeout; } + }); /** * Private methods @@ -157,6 +175,11 @@ exports.XMLHttpRequest = function() { this.abort(); errorFlag = false; + // timeout cannot be set on synchronous requests + if (!async && this._timeout !== 0) { + throw new Error('InvalidAccessError: synchronous XMLHttpRequests do not support timeout and responseType.'); + } + // Check for valid request method if (!isAllowedHttpMethod(method)) { throw new Error("SecurityError: Request method not allowed"); @@ -382,7 +405,8 @@ exports.XMLHttpRequest = function() { method: settings.method, headers: headers, agent: false, - withCredentials: self.withCredentials + withCredentials: self.withCredentials, + timeout: this._timeout }; // Reset error flag @@ -419,11 +443,14 @@ exports.XMLHttpRequest = function() { path: url.path, method: response.statusCode === 303 ? "GET" : settings.method, headers: headers, - withCredentials: self.withCredentials + withCredentials: self.withCredentials, + timeout: this._timeout }; // Issue the new request - request = doRequest(newOptions, responseHandler).on("error", errorHandler); + request = doRequest(newOptions, responseHandler) + .on("error", errorHandler) + .on("timeout", timeoutHandler); request.end(); // @TODO Check if an XHR event needs to be fired here return; @@ -463,8 +490,17 @@ exports.XMLHttpRequest = function() { self.handleError(error); }; + // Timeout handler for the request + var timeoutHandler = function timeoutHandler() { + // we need to abort the request manually + request.abort(); + self.timeoutHandler(); + } + // Create the request - request = doRequest(options, responseHandler).on("error", errorHandler); + request = doRequest(options, responseHandler) + .on("error", errorHandler) + .on("timeout", timeoutHandler); // Node 0.4 and later won't accept empty data. Make sure it's needed. if (data) { @@ -525,6 +561,18 @@ exports.XMLHttpRequest = function() { } }; + /** + * Called when a timeout event is fired. + */ + this.timeoutHandler = function() { + this.status = 0; + this.statusText = ''; + this.responseText = ''; + errorFlag = true; + setState(this.DONE); + this.dispatchEvent('timeout'); + } + /** * Called when an error is encountered to deal with it. */ diff --git a/tests/test-timeout.js b/tests/test-timeout.js new file mode 100644 index 0000000..d54f798 --- /dev/null +++ b/tests/test-timeout.js @@ -0,0 +1,47 @@ +var sys = require("util") + , assert = require("assert") + , http = require('http') + , XMLHttpRequest = require("../lib/XMLHttpRequest").XMLHttpRequest; + +// Test server +http.createServer(function (req, res) { + setTimeout(() => { + res.end(); + this.close(); + }, 3000); +}).listen(8000); + +var xhr = new XMLHttpRequest(); +// Testing simple timeout case +xhr.open("GET", "http://localhost:8000/nowhere"); +xhr.timeout = 1000; + +xhr.ontimeout = function () { + console.log('Request timed out!'); +} + +xhr.send(null); + +// Testing when timeout is set on a sync request +// Should throw an Error +xhr = new XMLHttpRequest(); +xhr.open("GET", "http://localhost:8000/nowhere", false); + +try { + xhr.timeout = 1000; +} catch (e) { + console.log(e.message); + assert.equal(e.message, 'InvalidAccessError: synchronous XMLHttpRequests do not support timeout and responseType.'); +} + +// Should also throw an Error if the timeout is set +// before xhr.open() on a sync request +xhr = new XMLHttpRequest(); +xhr.timeout = 1000; + +try { + xhr.open("GET", "http://localhost:8000/nowhere", false); +} catch (e) { + console.log(e.message); + assert.equal(e.message, 'InvalidAccessError: synchronous XMLHttpRequests do not support timeout and responseType.'); +}