diff --git a/angularFiles.js b/angularFiles.js
index 3b4d91ee952a..fcbad6bac035 100644
--- a/angularFiles.js
+++ b/angularFiles.js
@@ -12,6 +12,7 @@ angularFiles = {
'src/jqLite.js',
'src/apis.js',
'src/filters.js',
+ 'src/service/cacheFactory.js',
'src/service/cookieStore.js',
'src/service/cookies.js',
'src/service/defer.js',
@@ -25,10 +26,9 @@ angularFiles = {
'src/service/routeParams.js',
'src/service/sniffer.js',
'src/service/window.js',
- 'src/service/xhr.bulk.js',
- 'src/service/xhr.cache.js',
- 'src/service/xhr.error.js',
- 'src/service/xhr.js',
+ 'src/service/http.js',
+ 'src/service/httpBackend.js',
+ 'src/service/httpBulk.js',
'src/service/locale.js',
'src/directives.js',
'src/markups.js',
diff --git a/docs/content/cookbook/buzz.ngdoc b/docs/content/cookbook/buzz.ngdoc
index c4e5ae371e4d..ca6a22b49a26 100644
--- a/docs/content/cookbook/buzz.ngdoc
+++ b/docs/content/cookbook/buzz.ngdoc
@@ -18,8 +18,8 @@ to retrieve Buzz activity and comments.
this.Activity = $resource(
'https://www.googleapis.com/buzz/v1/activities/:userId/:visibility/:activityId/:comments',
{alt: 'json', callback: 'JSON_CALLBACK'},
- { get: {method: 'JSON', params: {visibility: '@self'}},
- replies: {method: 'JSON', params: {visibility: '@self', comments: '@comments'}}
+ { get: {method: 'JSONP', params: {visibility: '@self'}},
+ replies: {method: 'JSONP', params: {visibility: '@self', comments: '@comments'}}
});
}
BuzzController.prototype = {
diff --git a/src/AngularPublic.js b/src/AngularPublic.js
index fc8a90fdf978..0331873f76a5 100644
--- a/src/AngularPublic.js
+++ b/src/AngularPublic.js
@@ -5,7 +5,7 @@ var browserSingleton;
angularService('$browser', function($log, $sniffer) {
if (!browserSingleton) {
browserSingleton = new Browser(window, jqLite(window.document), jqLite(window.document.body),
- XHR, $log, $sniffer);
+ $log, $sniffer);
}
return browserSingleton;
}, {$inject: ['$log', '$sniffer']});
diff --git a/src/Browser.js b/src/Browser.js
index 71252050e68b..50acab4ddfc3 100644
--- a/src/Browser.js
+++ b/src/Browser.js
@@ -1,16 +1,5 @@
'use strict';
-//////////////////////////////
-// Browser
-//////////////////////////////
-var XHR = window.XMLHttpRequest || function() {
- try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e1) {}
- try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (e2) {}
- try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e3) {}
- throw new Error("This browser does not support XMLHttpRequest.");
-};
-
-
/**
* @ngdoc service
* @name angular.service.$browser
@@ -36,7 +25,7 @@ var XHR = window.XMLHttpRequest || function() {
* @param {object} $log console.log or an object with the same interface.
* @param {object} $sniffer $sniffer service
*/
-function Browser(window, document, body, XHR, $log, $sniffer) {
+function Browser(window, document, body, $log, $sniffer) {
var self = this,
rawDocument = document[0],
location = window.location,
@@ -47,13 +36,12 @@ function Browser(window, document, body, XHR, $log, $sniffer) {
self.isMock = false;
- //////////////////////////////////////////////////////////////
- // XHR API
- //////////////////////////////////////////////////////////////
- var idCounter = 0;
var outstandingRequestCount = 0;
var outstandingRequestCallbacks = [];
+ // TODO(vojta): remove this temporary api
+ self.$$completeOutstandingRequest = completeOutstandingRequest;
+ self.$$incOutstandingRequestCount = function() { outstandingRequestCount++; };
/**
* Executes the `fn` function(supports currying) and decrements the `outstandingRequestCallbacks`
@@ -76,60 +64,6 @@ function Browser(window, document, body, XHR, $log, $sniffer) {
}
}
- /**
- * @ngdoc method
- * @name angular.service.$browser#xhr
- * @methodOf angular.service.$browser
- *
- * @param {string} method Requested method (get|post|put|delete|head|json)
- * @param {string} url Requested url
- * @param {?string} post Post data to send (null if nothing to post)
- * @param {function(number, string)} callback Function that will be called on response
- * @param {object=} header additional HTTP headers to send with XHR.
- * Standard headers are:
- *
- *
Content-Type: application/x-www-form-urlencoded
- *
Accept: application/json, text/plain, */*
- *
X-Requested-With: XMLHttpRequest
- *
- *
- * @description
- * Send ajax request
- */
- self.xhr = function(method, url, post, callback, headers) {
- outstandingRequestCount ++;
- if (lowercase(method) == 'json') {
- var callbackId = ("angular_" + Math.random() + '_' + (idCounter++)).replace(/\d\./, '');
- window[callbackId] = function(data) {
- window[callbackId].data = data;
- };
-
- var script = self.addJs(url.replace('JSON_CALLBACK', callbackId), function() {
- if (window[callbackId].data) {
- completeOutstandingRequest(callback, 200, window[callbackId].data);
- } else {
- completeOutstandingRequest(callback);
- }
- delete window[callbackId];
- body[0].removeChild(script);
- });
- } else {
- var xhr = new XHR();
- xhr.open(method, url, true);
- forEach(headers, function(value, key) {
- if (value) xhr.setRequestHeader(key, value);
- });
- xhr.onreadystatechange = function() {
- if (xhr.readyState == 4) {
- // normalize IE bug (http://bugs.jquery.com/ticket/1450)
- var status = xhr.status == 1223 ? 204 : xhr.status;
- completeOutstandingRequest(callback, status, xhr.responseText);
- }
- };
- xhr.send(post || '');
- }
- };
-
/**
* @private
* Note: this method is used only by scenario runner
diff --git a/src/Resource.js b/src/Resource.js
index 959561e498ec..4bec60f9e3f1 100644
--- a/src/Resource.js
+++ b/src/Resource.js
@@ -36,8 +36,8 @@ Route.prototype = {
}
};
-function ResourceFactory(xhr) {
- this.xhr = xhr;
+function ResourceFactory($http) {
+ this.$http = $http;
}
ResourceFactory.DEFAULT_ACTIONS = {
@@ -107,11 +107,11 @@ ResourceFactory.prototype = {
}
var value = this instanceof Resource ? this : (action.isArray ? [] : new Resource(data));
- self.xhr(
- action.method,
- route.url(extend({}, extractParams(data), action.params || {}, params)),
- data,
- function(status, response) {
+ var future = self.$http({
+ method: action.method,
+ url: route.url(extend({}, extractParams(data), action.params || {}, params)),
+ data: data
+ }).on('success', function(response, status) {
if (response) {
if (action.isArray) {
value.length = 0;
@@ -123,9 +123,10 @@ ResourceFactory.prototype = {
}
}
(success||noop)(value);
- },
- error || action.verifyCache,
- action.verifyCache);
+ });
+
+ if (error) future.on('error', error);
+
return value;
};
diff --git a/src/angular-mocks.js b/src/angular-mocks.js
index 679a78a38808..0b0eb8b332c3 100644
--- a/src/angular-mocks.js
+++ b/src/angular-mocks.js
@@ -77,7 +77,7 @@ angular.mock = {};
* The following apis can be used in tests:
*
* - {@link angular.mock.service.$browser.xhr $browser.xhr} — enables testing of code that uses
- * the {@link angular.service.$xhr $xhr service} to make XmlHttpRequests.
+ * the {@link angular.service.$http $http service} to make XmlHttpRequests.
* - $browser.defer — enables testing of code that uses
* {@link angular.service.$defer $defer service} for executing functions via the `setTimeout` api.
*/
@@ -91,6 +91,10 @@ function MockBrowser() {
self.$$lastUrl = self.$$url; // used by url polling fn
self.pollFns = [];
+ // TODO(vojta): remove this temporary api
+ self.$$completeOutstandingRequest = noop;
+ self.$$incOutstandingRequestCount = noop;
+
// register url polling fn
@@ -107,157 +111,6 @@ function MockBrowser() {
return listener;
};
-
- /**
- * @ngdoc function
- * @name angular.mock.service.$browser.xhr
- *
- * @description
- * Generic method for training browser to expect a request in a test and respond to it.
- *
- * See also convenience methods for browser training:
- *
- * - {@link angular.mock.service.$browser.xhr.expectGET $browser.xhr.expectGET}
- * - {@link angular.mock.service.$browser.xhr.expectPOST $browser.xhr.expectPOST}
- * - {@link angular.mock.service.$browser.xhr.expectPUT $browser.xhr.expectPUT}
- * - {@link angular.mock.service.$browser.xhr.expectDELETE $browser.xhr.expectDELETE}
- * - {@link angular.mock.service.$browser.xhr.expectJSON $browser.xhr.expectJSON}
- *
- * To flush pending requests in tests use
- * {@link angular.mock.service.$browser.xhr.flush $browser.xhr.flush}.
- *
- * @param {string} method Expected HTTP method.
- * @param {string} url Url path for which a request is expected.
- * @param {(object|string)=} data Expected body of the (POST) HTTP request.
- * @param {function(number, *)} callback Callback to call when response is flushed.
- * @param {object} headers Key-value pairs of expected headers.
- * @returns {object} Response configuration object. You can call its `respond()` method to
- * configure what should the browser mock return when the response is
- * {@link angular.mock.service.$browser.xhr.flush flushed}.
- */
- self.xhr = function(method, url, data, callback, headers) {
- headers = headers || {};
- if (data && angular.isObject(data)) data = angular.toJson(data);
- if (data && angular.isString(data)) url += "|" + data;
- var expect = expectations[method] || {};
- var expectation = expect[url];
- if (!expectation) {
- throw new Error("Unexpected request for method '" + method + "' and url '" + url + "'.");
- }
- requests.push(function() {
- angular.forEach(expectation.headers, function(value, key){
- if (headers[key] !== value) {
- throw new Error("Missing HTTP request header: " + key + ": " + value);
- }
- });
- callback(expectation.code, expectation.response);
- });
- };
- self.xhr.expectations = expectations;
- self.xhr.requests = requests;
- self.xhr.expect = function(method, url, data, headers) {
- if (data && angular.isObject(data)) data = angular.toJson(data);
- if (data && angular.isString(data)) url += "|" + data;
- var expect = expectations[method] || (expectations[method] = {});
- return {
- respond: function(code, response) {
- if (!angular.isNumber(code)) {
- response = code;
- code = 200;
- }
- expect[url] = {code:code, response:response, headers: headers || {}};
- }
- };
- };
-
- /**
- * @ngdoc function
- * @name angular.mock.service.$browser.xhr.expectGET
- *
- * @description
- * Trains browser to expect a `GET` request and respond to it.
- *
- * @param {string} url Url path for which a request is expected.
- * @returns {object} Response configuration object. You can call its `respond()` method to
- * configure what should the browser mock return when the response is
- * {@link angular.mock.service.$browser.xhr.flush flushed}.
- */
- self.xhr.expectGET = angular.bind(self, self.xhr.expect, 'GET');
-
- /**
- * @ngdoc function
- * @name angular.mock.service.$browser.xhr.expectPOST
- *
- * @description
- * Trains browser to expect a `POST` request and respond to it.
- *
- * @param {string} url Url path for which a request is expected.
- * @returns {object} Response configuration object. You can call its `respond()` method to
- * configure what should the browser mock return when the response is
- * {@link angular.mock.service.$browser.xhr.flush flushed}.
- */
- self.xhr.expectPOST = angular.bind(self, self.xhr.expect, 'POST');
-
- /**
- * @ngdoc function
- * @name angular.mock.service.$browser.xhr.expectDELETE
- *
- * @description
- * Trains browser to expect a `DELETE` request and respond to it.
- *
- * @param {string} url Url path for which a request is expected.
- * @returns {object} Response configuration object. You can call its `respond()` method to
- * configure what should the browser mock return when the response is
- * {@link angular.mock.service.$browser.xhr.flush flushed}.
- */
- self.xhr.expectDELETE = angular.bind(self, self.xhr.expect, 'DELETE');
-
- /**
- * @ngdoc function
- * @name angular.mock.service.$browser.xhr.expectPUT
- *
- * @description
- * Trains browser to expect a `PUT` request and respond to it.
- *
- * @param {string} url Url path for which a request is expected.
- * @returns {object} Response configuration object. You can call its `respond()` method to
- * configure what should the browser mock return when the response is
- * {@link angular.mock.service.$browser.xhr.flush flushed}.
- */
- self.xhr.expectPUT = angular.bind(self, self.xhr.expect, 'PUT');
-
- /**
- * @ngdoc function
- * @name angular.mock.service.$browser.xhr.expectJSON
- *
- * @description
- * Trains browser to expect a `JSON` request and respond to it.
- *
- * @param {string} url Url path for which a request is expected.
- * @returns {object} Response configuration object. You can call its `respond()` method to
- * configure what should the browser mock return when the response is
- * {@link angular.mock.service.$browser.xhr.flush flushed}.
- */
- self.xhr.expectJSON = angular.bind(self, self.xhr.expect, 'JSON');
-
- /**
- * @ngdoc function
- * @name angular.mock.service.$browser.xhr.flush
- *
- * @description
- * Flushes all pending requests and executes xhr callbacks with the trained response as the
- * argument.
- */
- self.xhr.flush = function() {
- if (requests.length == 0) {
- throw new Error("No xhr requests to be flushed!");
- }
-
- while(requests.length) {
- requests.pop()();
- }
- };
-
self.cookieHash = {};
self.lastCookieHash = {};
self.deferredFns = [];
@@ -308,6 +161,13 @@ function MockBrowser() {
self.baseHref = function() {
return this.$$baseHref;
};
+
+ self.$$scripts = [];
+ self.addJs = function(url, domId, done) {
+ var script = {url: url, id: domId, done: done};
+ self.$$scripts.push(script);
+ return script;
+ };
}
MockBrowser.prototype = {
@@ -355,9 +215,7 @@ MockBrowser.prototype = {
}
return this.cookieHash;
}
- },
-
- addJs: function() {}
+ }
};
angular.service('$browser', function() {
@@ -546,3 +404,194 @@ function TzDate(offset, timestamp) {
//make "tzDateInstance instanceof Date" return true
TzDate.prototype = Date.prototype;
+
+function createMockHttpBackend() {
+ var definitions = [],
+ expectations = [],
+ responses = [];
+
+ function createResponse(status, data, headers) {
+ return angular.isNumber(status) ? [status, data, headers] : [200, status, data];
+ }
+
+ // TODO(vojta): change params to: method, url, data, headers, callback
+ function $httpBackend(method, url, data, callback, headers) {
+ var xhr = new MockXhr(),
+ expectation = expectations[0],
+ wasExpected = false;
+
+ function prettyPrint(data) {
+ if (angular.isString(data) || angular.isFunction(data) || data instanceof RegExp)
+ return data;
+ return angular.toJson(data);
+ }
+
+ if (expectation && expectation.match(method, url)) {
+ if (!expectation.matchData(data))
+ throw Error('Expected ' + expectation + ' with different data\n' +
+ 'EXPECTED: ' + prettyPrint(expectation.data) + '\nGOT: ' + data);
+
+ if (!expectation.matchHeaders(headers))
+ throw Error('Expected ' + expectation + ' with different headers\n' +
+ 'EXPECTED: ' + prettyPrint(expectation.headers) + '\nGOT: ' + prettyPrint(headers));
+
+ expectations.shift();
+
+ if (expectation.response) {
+ responses.push(function() {
+ xhr.$$headers = expectation.response[2];
+ callback(expectation.response[0], expectation.response[1]);
+ });
+ return method == 'JSONP' ? undefined : xhr;
+ }
+ wasExpected = true;
+ }
+
+ var i = -1, definition;
+ while ((definition = definitions[++i])) {
+ if (definition.match(method, url, data, headers || {})) {
+ if (!definition.response) throw Error('No response defined !');
+ responses.push(function() {
+ var response = angular.isFunction(definition.response) ?
+ definition.response(method, url, data, headers) : definition.response;
+ xhr.$$headers = response[2];
+ callback(response[0], response[1]);
+ });
+ return method == 'JSONP' ? undefined : xhr;
+ }
+ }
+ throw wasExpected ? Error('No response defined !') :
+ Error('Unexpected request: ' + method + ' ' + url);
+ }
+
+ $httpBackend.when = function(method, url, data, headers) {
+ var definition = new MockHttpExpectation(method, url, data, headers);
+ definitions.push(definition);
+ return {
+ respond: function(status, data, headers) {
+ definition.response = angular.isFunction(status) ? status : createResponse(status, data, headers);
+ }
+ };
+ };
+
+ $httpBackend.expect = function(method, url, data, headers) {
+ var expectation = new MockHttpExpectation(method, url, data, headers);
+ expectations.push(expectation);
+ return {
+ respond: function(status, data, headers) {
+ expectation.response = createResponse(status, data, headers);
+ }
+ };
+ };
+
+ $httpBackend.flush = function(count) {
+ if (!responses.length) throw Error('No pending request to flush !');
+
+ if (angular.isDefined(count)) {
+ while (count--) {
+ if (!responses.length) throw Error('No more pending request to flush !');
+ responses.shift()();
+ }
+ } else {
+ while (responses.length)
+ responses.shift()();
+ }
+ $httpBackend.verifyNoOutstandingExpectations();
+ };
+
+ $httpBackend.verifyNoOutstandingExpectations = function() {
+ if (expectations.length) {
+ throw Error('Unsatisfied requests: ' + expectations.join(', '));
+ }
+ };
+
+ $httpBackend.verifyRequestsHaveBeenFlushed = function() {
+ if (responses.length) {
+ throw Error('Unflushed requests: ' + responses.length);
+ }
+ };
+
+ $httpBackend.resetExpectations = function() {
+ expectations = [];
+ responses = [];
+ };
+
+ return $httpBackend;
+}
+
+function MockHttpExpectation(method, url, data, headers) {
+
+ this.data = data;
+ this.headers = headers;
+
+ this.match = function(m, u, d, h) {
+ if (method != m) return false;
+ if (!this.matchUrl(u)) return false;
+ if (angular.isDefined(d) && !this.matchData(d)) return false;
+ if (angular.isDefined(h) && !this.matchHeaders(h)) return false;
+ return true;
+ };
+
+ this.matchUrl = function(u) {
+ if (!url) return true;
+ if (angular.isFunction(url.test)) return url.test(u);
+ return url == u;
+ };
+
+ this.matchHeaders = function(h) {
+ if (angular.isUndefined(headers)) return true;
+ if (angular.isFunction(headers)) return headers(h);
+ return angular.equals(headers, h);
+ };
+
+ this.matchData = function(d) {
+ if (angular.isUndefined(data)) return true;
+ if (data && angular.isFunction(data.test)) return data.test(d);
+ if (data && !angular.isString(data)) return angular.toJson(data) == d;
+ return data == d;
+ };
+
+ this.toString = function() {
+ return method + ' ' + url;
+ };
+}
+
+function MockXhr() {
+
+ // hack for testing $http, $httpBackend
+ MockXhr.$$lastInstance = this;
+
+ this.open = function(method, url, async) {
+ this.$$method = method;
+ this.$$url = url;
+ this.$$async = async;
+ this.$$headers = {};
+ };
+
+ this.send = function(data) {
+ this.$$data = data;
+ };
+
+ this.setRequestHeader = function(key, value) {
+ this.$$headers[key] = value;
+ };
+
+ this.getResponseHeader = function(name) {
+ return this.$$headers[name];
+ };
+
+ this.getAllResponseHeaders = function() {
+ var lines = [];
+
+ angular.forEach(this.$$headers, function(value, key) {
+ lines.push(key + ': ' + value);
+ });
+ return lines.join('\n');
+ };
+
+ this.abort = noop;
+}
+
+
+// use the mock during testing
+angular.service('$httpBackend', createMockHttpBackend);
diff --git a/src/service/cacheFactory.js b/src/service/cacheFactory.js
new file mode 100644
index 000000000000..a4c8ecaa9ad9
--- /dev/null
+++ b/src/service/cacheFactory.js
@@ -0,0 +1,160 @@
+/**
+ * @workInProgress
+ * @ngdoc service
+ * @name angular.service.$cacheFactory
+ *
+ * @description
+ * Factory that constructs cache objects.
+ *
+ *
+ * @param {string} cacheId Name or id of the newly created cache.
+ * @param {object=} options Options object that specifies the cache behavior. Properties:
+ *
+ * - `{number=}` `capacity` — turns the cache into LRU cache.
+ *
+ * @returns {object} Newly created cache object with the following set of methods:
+ *
+ * - `{string}` `id()` — Returns id or name of the cache.
+ * - `{number}` `size()` — Returns number of items currently in the cache
+ * - `{void}` `put({string} key, {*} value)` — Puts a new key-value pair into the cache
+ * - `{(*}} `get({string} key) — Returns cached value for `key` or undefined for cache miss.
+ * - `{void}` `remove{string} key) — Removes a key-value pair from the cache.
+ * - `{void}` `removeAll() — Removes all cached values.
+ *
+ */
+angularServiceInject('$cacheFactory', function() {
+
+ var caches = {};
+
+ function cacheFactory(cacheId, options) {
+ if (cacheId in caches) {
+ throw Error('cacheId ' + cacheId + ' taken');
+ }
+
+ var size = 0,
+ stats = extend({}, options, {id: cacheId}),
+ data = {},
+ capacity = (options && options.capacity) || Number.MAX_VALUE,
+ lruHash = {},
+ freshEnd = null,
+ staleEnd = null;
+
+ return caches[cacheId] = {
+
+ put: function(key, value) {
+ var lruEntry = lruHash[key] || (lruHash[key] = {key: key});
+
+ refresh(lruEntry);
+
+ if (isUndefined(value)) return;
+ if (!(key in data)) size++;
+ data[key] = value;
+
+ if (size > capacity) {
+ this.remove(staleEnd.key);
+ }
+ },
+
+
+ get: function(key) {
+ var lruEntry = lruHash[key];
+
+ if (!lruEntry) return;
+
+ refresh(lruEntry);
+
+ return data[key];
+ },
+
+
+ remove: function(key) {
+ var lruEntry = lruHash[key];
+
+ if (lruEntry == freshEnd) freshEnd = lruEntry.p;
+ if (lruEntry == staleEnd) staleEnd = lruEntry.n;
+ link(lruEntry.n,lruEntry.p);
+
+ delete lruHash[key];
+ delete data[key];
+ size--;
+ },
+
+
+ removeAll: function() {
+ data = {};
+ size = 0;
+ lruHash = {};
+ freshEnd = staleEnd = null;
+ },
+
+
+ destroy: function() {
+ data = null;
+ stats = null;
+ lruHash = null;
+ delete caches[cacheId];
+ },
+
+
+ info: function() {
+ return extend({}, stats, {size: size});
+ }
+ };
+
+
+ /**
+ * makes the `entry` the freshEnd of the LRU linked list
+ */
+ function refresh(entry) {
+ if (entry != freshEnd) {
+ if (!staleEnd) {
+ staleEnd = entry;
+ } else if (staleEnd == entry) {
+ staleEnd = entry.n;
+ }
+
+ link(entry.n, entry.p);
+ link(entry, freshEnd);
+ freshEnd = entry;
+ freshEnd.n = null;
+ }
+ }
+
+
+ /**
+ * bidirectionally links two entries of the LRU linked list
+ */
+ function link(nextEntry, prevEntry) {
+ if (nextEntry != prevEntry) {
+ if (nextEntry) nextEntry.p = prevEntry; //p stands for previous, 'prev' didn't minify
+ if (prevEntry) prevEntry.n = nextEntry; //n stands for next, 'next' didn't minify
+ }
+ }
+ }
+
+
+ cacheFactory.info = function() {
+ var info = {};
+ forEach(caches, function(cache, cacheId) {
+ info[cacheId] = cache.info();
+ });
+ return info;
+ };
+
+
+ cacheFactory.get = function(cacheId) {
+ return caches[cacheId];
+ };
+
+
+ return cacheFactory;
+});
+
+/**
+ * Used by ng:include, ng:view
+ * TODO(vojta): configuration
+ * TODO(vojta): extract into separate file ?
+ */
+angularServiceInject('$templateCache', function($cacheFactory) {
+ return $cacheFactory('templates');
+}, ['$cacheFactory']);
diff --git a/src/service/http.js b/src/service/http.js
new file mode 100644
index 000000000000..13ccf04b10dd
--- /dev/null
+++ b/src/service/http.js
@@ -0,0 +1,423 @@
+'use strict';
+
+/**
+ * Parse headers into key value object
+ *
+ * @param {string} headers Raw headers as a string
+ * @returns {Object} Parsed headers as key valu object
+ */
+function parseHeaders(headers) {
+ var parsed = {}, key, val, i;
+
+ forEach(headers.split('\n'), function(line) {
+ i = line.indexOf(':');
+ key = lowercase(trim(line.substr(0, i)));
+ val = trim(line.substr(i + 1));
+
+ if (key) {
+ if (parsed[key]) {
+ parsed[key] += ', ' + val;
+ } else {
+ parsed[key] = val;
+ }
+ }
+ });
+
+ return parsed;
+}
+
+/**
+ * Chain all given functions
+ *
+ * This function is used for both request and response transforming
+ *
+ * @param {*} data Data to transform.
+ * @param {function|Array.} fns Function or an array of functions.
+ * @param {*=} param Optional parameter to be passed to all transform functions.
+ * @returns {*} Transformed data.
+ */
+function transform(data, fns, param) {
+ if (isFunction(fns))
+ return fns(data);
+
+ forEach(fns, function(fn) {
+ data = fn(data, param);
+ });
+
+ return data;
+}
+
+
+/**
+ * @workInProgress
+ * @ngdoc service
+ * @name angular.service.$http
+ * @requires $httpBacked
+ * @requires $browser
+ * @requires $exceptionHandler
+ * @requires $cacheFactory
+ *
+ * @property {Array.} pendingRequests Array of pending requests.
+ *
+ * @description
+ */
+angularServiceInject('$http', function($httpBackend, $browser, $exceptionHandler, $config, $cacheFactory) {
+
+ var rootScope = this.$root,
+ cache = $cacheFactory('$http');
+
+ // the actual service
+ function $http(config) {
+ return new XhrFuture().send(config);
+ }
+
+ $http.pendingRequests = [];
+
+ /**
+ * @workInProgress
+ * @ngdoc method
+ * @name angular.service.$http#get
+ * @methodOf angular.service.$http
+ *
+ * @description
+ * Shortcut method to perform `GET` request
+ *
+ * @param {string} url Relative or absolute URL specifying the destination of the request
+ * @param {Object=} config Optional configuration object
+ * @returns {XhrFuture} Future object
+ */
+
+ /**
+ * @workInProgress
+ * @ngdoc method
+ * @name angular.service.$http#delete
+ * @methodOf angular.service.$http
+ *
+ * @description
+ * Shortcut method to perform `DELETE` request
+ *
+ * @param {string} url Relative or absolute URL specifying the destination of the request
+ * @param {Object=} config Optional configuration object
+ * @returns {XhrFuture} Future object
+ */
+
+ /**
+ * @workInProgress
+ * @ngdoc method
+ * @name angular.service.$http#head
+ * @methodOf angular.service.$http
+ *
+ * @description
+ * Shortcut method to perform `HEAD` request
+ *
+ * @param {string} url Relative or absolute URL specifying the destination of the request
+ * @param {Object=} config Optional configuration object
+ * @returns {XhrFuture} Future object
+ */
+
+ /**
+ * @workInProgress
+ * @ngdoc method
+ * @name angular.service.$http#patch
+ * @methodOf angular.service.$http
+ *
+ * @description
+ * Shortcut method to perform `PATCH` request
+ *
+ * @param {string} url Relative or absolute URL specifying the destination of the request
+ * @param {Object=} config Optional configuration object
+ * @returns {XhrFuture} Future object
+ */
+
+ /**
+ * @workInProgress
+ * @ngdoc method
+ * @name angular.service.$http#jsonp
+ * @methodOf angular.service.$http
+ *
+ * @description
+ * Shortcut method to perform `JSONP` request
+ *
+ * @param {string} url Relative or absolute URL specifying the destination of the request.
+ * Should contain `JSON_CALLBACK` string.
+ * @param {Object=} config Optional configuration object
+ * @returns {XhrFuture} Future object
+ */
+ createShortMethods('get', 'delete', 'head', 'patch', 'jsonp');
+
+ /**
+ * @workInProgress
+ * @ngdoc method
+ * @name angular.service.$http#post
+ * @methodOf angular.service.$http
+ *
+ * @description
+ * Shortcut method to perform `POST` request
+ *
+ * @param {string} url Relative or absolute URL specifying the destination of the request
+ * @param {*} data Request content
+ * @param {Object=} config Optional configuration object
+ * @returns {XhrFuture} Future object
+ */
+
+ /**
+ * @workInProgress
+ * @ngdoc method
+ * @name angular.service.$http#put
+ * @methodOf angular.service.$http
+ *
+ * @description
+ * Shortcut method to perform `PUT` request
+ *
+ * @param {string} url Relative or absolute URL specifying the destination of the request
+ * @param {*} data Request content
+ * @param {Object=} config Optional configuration object
+ * @returns {XhrFuture} Future object
+ */
+ createShortMethodsWithData('post', 'put');
+
+ return $http;
+
+ function createShortMethods(names) {
+ forEach(arguments, function(name) {
+ $http[name] = function(url, config) {
+ return $http(extend(config || {}, {
+ method: name,
+ url: url
+ }));
+ };
+ });
+ }
+
+ function createShortMethodsWithData(name) {
+ forEach(arguments, function(name) {
+ $http[name] = function(url, data, config) {
+ return $http(extend(config || {}, {
+ method: name,
+ url: url,
+ data: data
+ }));
+ };
+ });
+ }
+
+ /**
+ * Represents Request object, returned by $http()
+ *
+ * !!! ACCESS CLOSURE VARS:
+ * $httpBackend, $browser, $config, $log, rootScope, cache, $http.pendingRequests
+ */
+ function XhrFuture() {
+ var rawRequest, parsedHeaders,
+ cfg = {}, callbacks = [],
+ defHeaders = $config.headers,
+ self = this;
+
+ /**
+ * Callback registered to $httpBackend():
+ * - caches the response if desired
+ * - calls fireCallbacks()
+ * - clears the reference to raw request object
+ */
+ function done(status, response) {
+ // aborted request or jsonp
+ if (!rawRequest) parsedHeaders = {};
+
+ if (cfg.cache && cfg.method == 'GET' && 200 <= status && status < 300) {
+ parsedHeaders = parsedHeaders || parseHeaders(rawRequest.getAllResponseHeaders());
+ cache.put(cfg.url, [status, response, parsedHeaders]);
+ }
+
+ fireCallbacks(response, status);
+ rawRequest = null;
+ }
+
+ /**
+ * Fire all registered callbacks for given status code
+ *
+ * This method when:
+ * - serving response from real request
+ * - serving response from cache
+ *
+ * It does:
+ * - transform the response
+ * - call proper callbacks
+ * - log errors
+ * - apply the $scope
+ * - clear parsed headers
+ */
+ function fireCallbacks(response, status) {
+ var strStatus = status + '';
+
+ // transform the response
+ response = transform(response, cfg.transformResponse || $config.transformResponse, rawRequest);
+
+ var idx; // remove from pending requests
+ if ((idx = indexOf($http.pendingRequests, self)) !== -1)
+ $http.pendingRequests.splice(idx, 1);
+
+ // normalize internal statuses to 0
+ status = Math.max(status, 0);
+ forEach(callbacks, function(callback) {
+ if (callback.regexp.test(strStatus)) {
+ try {
+ // use local var to call it without context
+ var fn = callback.fn;
+ fn(response, status, headers);
+ } catch(e) {
+ $exceptionHandler(e);
+ }
+ }
+ });
+
+ rootScope.$apply();
+ parsedHeaders = null;
+ }
+
+ /**
+ * This is the third argument in any user callback
+ * @see parseHeaders
+ *
+ * Return single header value or all headers parsed as object.
+ * Headers all lazy parsed when first requested.
+ *
+ * @param {string=} name Name of header
+ * @returns {string|Object}
+ */
+ function headers(name) {
+ if (name) {
+ return parsedHeaders
+ ? parsedHeaders[lowercase(name)] || null
+ : rawRequest.getResponseHeader(name);
+ }
+
+ parsedHeaders = parsedHeaders || parseHeaders(rawRequest.getAllResponseHeaders());
+
+ return parsedHeaders;
+ }
+
+ /**
+ * Retry the request
+ *
+ * @param {Object=} config Optional config object to extend the original configuration
+ * @returns {XhrFuture}
+ */
+ this.retry = function(config) {
+ if (rawRequest) throw 'Can not retry request. Abort pending request first.';
+
+ extend(cfg, config);
+ cfg.method = uppercase(cfg.method);
+
+ var data = transform(cfg.data, cfg.transformRequest || $config.transformRequest),
+ headers = extend({'X-XSRF-TOKEN': $browser.cookies()['XSRF-TOKEN']},
+ defHeaders.common, defHeaders[lowercase(cfg.method)], cfg.headers);
+
+ var fromCache;
+ if (cfg.cache && cfg.method == 'GET' && (fromCache = cache.get(cfg.url))) {
+ $browser.defer(function() {
+ parsedHeaders = fromCache[2];
+ fireCallbacks(fromCache[1], fromCache[0]);
+ });
+ } else {
+ rawRequest = $httpBackend(cfg.method, cfg.url, data, done, headers, cfg.timeout);
+ }
+
+ rootScope.$broadcast('$http.request', self);
+ $http.pendingRequests.push(self);
+ return this;
+ };
+
+ // just alias so that in stack trace we can see send() instead of retry()
+ this.send = this.retry;
+
+ /**
+ * Abort the request
+ */
+ this.abort = function() {
+ if (rawRequest) {
+ rawRequest.abort();
+ }
+ return this;
+ };
+
+ /**
+ * Register a callback function based on status code
+ * Note: all matched callbacks will be called, preserving registered order !
+ *
+ * Internal statuses:
+ * `-2` = jsonp error
+ * `-1` = timeout
+ * `0` = aborted
+ *
+ * @example
+ * .on('2xx', function(){});
+ * .on('2x1', function(){});
+ * .on('404', function(){});
+ * .on('20x,3xx', function(){});
+ * .on('success', function(){});
+ * .on('error', function(){});
+ * .on('always', function(){});
+ * .on('timeout', function(){});
+ * .on('abort', function(){});
+ *
+ * @param {string} pattern Status code pattern with "x" for any number
+ * @param {function(*, number, Object)} callback Function to be called when response arrives
+ * @returns {XhrFuture}
+ */
+ this.on = function(pattern, callback) {
+ var alias = {
+ success: '2xx',
+ error: '-2,-1,0,4xx,5xx',
+ always: 'xxx,xx,x',
+ timeout: '-1',
+ abort: '0'
+ };
+
+ callbacks.push({
+ fn: callback,
+ // create regexp from given pattern
+ regexp: new RegExp('^(' + (alias[pattern] || pattern).replace(/,/g, '|').
+ replace(/x/g, '.') + ')$')
+ });
+
+ return this;
+ };
+
+ /**
+ * Configuration object of the request
+ */
+ this.config = cfg;
+ }
+}, ['$httpBackend', '$browser', '$exceptionHandler', '$httpConfig', '$cacheFactory']);
+
+// TODO(vojta): remove when we have the concept of configuration
+angular.service('$httpConfig', function() {
+ return {
+
+ // transform in-coming reponse data
+ transformResponse: function(data) {
+ if (isString(data)) {
+ // strip json vulnerability protection prefix
+ data = data.replace(/^\)\]\}',?\n/, '');
+ if (/^\s*[\[\{]/.test(data) && /[\}\]]\s*$/.test(data))
+ data = fromJson(data, true);
+ }
+ return data;
+ },
+
+ // transform out-going request data
+ transformRequest: function(d) {
+ return isObject(d) ? toJson(d) : d;
+ },
+
+ // default headers
+ headers: {
+ common: {
+ 'Accept': 'application/json, text/plain, */*',
+ 'X-Requested-With': 'XMLHttpRequest'
+ },
+ post: {'Content-Type': 'application/json'},
+ put: {'Content-Type': 'application/json'}
+ }
+ };
+});
diff --git a/src/service/httpBackend.js b/src/service/httpBackend.js
new file mode 100644
index 000000000000..86d03c7504c9
--- /dev/null
+++ b/src/service/httpBackend.js
@@ -0,0 +1,87 @@
+var XHR = window.XMLHttpRequest || function() {
+ try { return new ActiveXObject("Msxml2.XMLHTTP.6.0"); } catch (e1) {}
+ try { return new ActiveXObject("Msxml2.XMLHTTP.3.0"); } catch (e2) {}
+ try { return new ActiveXObject("Msxml2.XMLHTTP"); } catch (e3) {}
+ throw new Error("This browser does not support XMLHttpRequest.");
+};
+
+
+/**
+ * @workInProgress
+ * @ngdoc service
+ * @name angular.service.$httpBackend
+ * @requires $browser
+ * @requires $window
+ * @requires $document
+ *
+ * @description
+ */
+angularServiceInject('$httpBackend', function($browser, $window, $document) {
+ // TODO(vojta): inject $defer service instead of $browser.defer
+ return createHttpBackend($browser, XHR, $browser.defer, $window, $document[0].body);
+}, ['$browser', '$window', '$document']);
+
+function createHttpBackend($browser, XHR, $defer, $window, body) {
+ var idCounter = 0;
+
+ function completeRequest(callback, status, response) {
+ // normalize IE bug (http://bugs.jquery.com/ticket/1450)
+ callback(status == 1223 ? 204 : status, response);
+ $browser.$$completeOutstandingRequest(noop);
+ }
+
+ // TODO(vojta): fix the signature
+ return function(method, url, post, callback, headers, timeout) {
+ $browser.$$incOutstandingRequestCount();
+
+ if (lowercase(method) == 'jsonp') {
+ var callbackId = ('angular_' + Math.random() + '_' + (idCounter++)).replace(/\d\./, '');
+ $window[callbackId] = function(data) {
+ $window[callbackId].data = data;
+ };
+
+ var script = $browser.addJs(url.replace('JSON_CALLBACK', callbackId), null, function() {
+ if ($window[callbackId].data) {
+ completeRequest(callback, 200, $window[callbackId].data);
+ } else {
+ completeRequest(callback, -2);
+ }
+ delete $window[callbackId];
+ body.removeChild(script);
+ });
+ } else {
+ var xhr = new XHR();
+ xhr.open(method, url, true);
+ forEach(headers, function(value, key) {
+ if (value) xhr.setRequestHeader(key, value);
+ });
+
+ var status;
+ xhr.send(post || '');
+
+ // IE6, IE7 bug - does sync when serving from cache
+ if (xhr.readyState == 4) {
+ // TODO(vojta): we don't want to $apply() after $defer
+ $defer(function() {
+ completeRequest(callback, status || xhr.status, xhr.responseText);
+ }, 0);
+ } else {
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState == 4) {
+ completeRequest(callback, status || xhr.status, xhr.responseText);
+ }
+ };
+ }
+
+ if (timeout > 0) {
+ // TODO(vojta): we don't want to $apply() after $defer
+ $defer(function() {
+ status = -1;
+ xhr.abort();
+ }, timeout);
+ }
+
+ return xhr;
+ }
+ };
+}
diff --git a/src/service/httpBulk.js b/src/service/httpBulk.js
new file mode 100644
index 000000000000..b2a6506cba16
--- /dev/null
+++ b/src/service/httpBulk.js
@@ -0,0 +1,106 @@
+'use strict';
+
+angular.service('httpBulk', function($http, $log) {
+ var buckets = {},
+ defaultReceiver,
+ rootScope = this;
+
+ /**
+ * @param {Object} config HTTP config object
+ * @returns {function(string, function)} HTTP promise with `on` method
+ */
+ function httpBulk(config) {
+ var name, bucket, matched,
+ //TODO(i): lame since just one pair of success and error callbacks can be registered
+ callbacks = {'success': angular.noop, 'error': angular.noop};
+
+ for (name in buckets) {
+ bucket = buckets[name];
+ if (bucket.matcher.test(config.url)) {
+ matched = true;
+ break;
+ }
+ }
+
+ if (!matched) return $http(config);
+
+ bucket.queue.push({config: config, callbacks: callbacks});
+
+ var promise = {
+ on: function onFn(resolution, callback) {
+ callbacks[resolution] = callback;
+ return promise;
+ }
+ };
+
+ return promise;
+ }
+
+
+ /**
+ * @param {string} name
+ * @param {RegExp} matcher
+ * @param {string} receiver
+ * @returns httpBulk
+ */
+ httpBulk.bucket = function(name, matcher, receiver) {
+ buckets[name] = {
+ matcher: matcher,
+ receiver: receiver || defaultReceiver,
+ queue: []
+ };
+ return httpBulk;
+ };
+
+
+ /**
+ * @param {string} receiverUrl
+ * @returns httpBulk
+ */
+ httpBulk.receiver = function(receiverUrl) {
+ defaultReceiver = receiverUrl;
+ return httpBulk;
+ };
+
+
+ /**
+ * @param {object} bucket
+ */
+ function flush(bucket) {
+ if (!bucket.queue.length) return;
+
+ var requests = [],
+ callbacks = [];
+
+ angular.forEach(bucket.queue, function(request) {
+ requests.push(request.config);
+ callbacks.push(request.callbacks);
+ });
+
+ bucket.queue = [];
+ $http.post(bucket.receiver, {requests: requests}).
+ on('success', function(responses) {
+ var i, n, response, status, callback;
+
+ for (i = 0, n = responses.length; i < n; i++) {
+ response = responses[i];
+ status = response.status;
+ callback = (200 <= status && status < 300) ? callbacks[i].success : callbacks[i].error;
+
+ try {
+ callback(response.response, status);
+ } catch(e) {
+ $log.error(e);
+ }
+ }
+ }
+ );
+ }
+
+ // register the flush method
+ rootScope.$watch(function() {
+ angular.forEach(buckets, flush);
+ });
+
+ return httpBulk;
+}, {$inject: ['$http', '$log']});
diff --git a/src/service/resource.js b/src/service/resource.js
index 8d77a9e4e0f5..9ac7b403f267 100644
--- a/src/service/resource.js
+++ b/src/service/resource.js
@@ -40,7 +40,7 @@
* - `action` – {string} – The name of action. This name becomes the name of the method on your
* resource object.
* - `method` – {string} – HTTP request method. Valid methods are: `GET`, `POST`, `PUT`, `DELETE`,
- * and `JSON` (also known as JSONP).
+ * and `JSONP`
* - `params` – {object=} – Optional set of pre-bound parameters for this action.
* - isArray – {boolean=} – If true then the returned object for this action is an array, see
* `returns` section.
@@ -163,7 +163,7 @@
this.Activity = $resource(
'https://www.googleapis.com/buzz/v1/activities/:userId/:visibility/:activityId/:comments',
{alt:'json', callback:'JSON_CALLBACK'},
- {get:{method:'JSON', params:{visibility:'@self'}}, replies: {method:'JSON', params:{visibility:'@self', comments:'@comments'}}}
+ {get:{method:'JSONP', params:{visibility:'@self'}}, replies: {method:'JSONP', params:{visibility:'@self', comments:'@comments'}}}
);
}
@@ -200,7 +200,7 @@
*/
-angularServiceInject('$resource', function($xhr){
- var resource = new ResourceFactory($xhr);
+angularServiceInject('$resource', function($http) {
+ var resource = new ResourceFactory($http);
return bind(resource, resource.route);
-}, ['$xhr.cache']);
+}, ['$http']);
diff --git a/src/service/xhr.bulk.js b/src/service/xhr.bulk.js
deleted file mode 100644
index 33c9384b8a48..000000000000
--- a/src/service/xhr.bulk.js
+++ /dev/null
@@ -1,87 +0,0 @@
-'use strict';
-
-/**
- * @ngdoc service
- * @name angular.service.$xhr.bulk
- * @requires $xhr
- * @requires $xhr.error
- * @requires $log
- *
- * @description
- *
- * @example
- */
-angularServiceInject('$xhr.bulk', function($xhr, $error, $log){
- var requests = [],
- scope = this;
- function bulkXHR(method, url, post, success, error) {
- if (isFunction(post)) {
- error = success;
- success = post;
- post = null;
- }
- var currentQueue;
- forEach(bulkXHR.urls, function(queue){
- if (isFunction(queue.match) ? queue.match(url) : queue.match.exec(url)) {
- currentQueue = queue;
- }
- });
- if (currentQueue) {
- if (!currentQueue.requests) currentQueue.requests = [];
- var request = {
- method: method,
- url: url,
- data: post,
- success: success};
- if (error) request.error = error;
- currentQueue.requests.push(request);
- } else {
- $xhr(method, url, post, success, error);
- }
- }
- bulkXHR.urls = {};
- bulkXHR.flush = function(success, errorback) {
- assertArgFn(success = success || noop, 0);
- assertArgFn(errorback = errorback || noop, 1);
- forEach(bulkXHR.urls, function(queue, url) {
- var currentRequests = queue.requests;
- if (currentRequests && currentRequests.length) {
- queue.requests = [];
- queue.callbacks = [];
- $xhr('POST', url, {requests: currentRequests},
- function(code, response) {
- forEach(response, function(response, i) {
- try {
- if (response.status == 200) {
- (currentRequests[i].success || noop)(response.status, response.response);
- } else if (isFunction(currentRequests[i].error)) {
- currentRequests[i].error(response.status, response.response);
- } else {
- $error(currentRequests[i], response);
- }
- } catch(e) {
- $log.error(e);
- }
- });
- success();
- },
- function(code, response) {
- forEach(currentRequests, function(request, i) {
- try {
- if (isFunction(request.error)) {
- request.error(code, response);
- } else {
- $error(request, response);
- }
- } catch(e) {
- $log.error(e);
- }
- });
- noop();
- });
- }
- });
- };
- this.$watch(function() { bulkXHR.flush(); });
- return bulkXHR;
-}, ['$xhr', '$xhr.error', '$log']);
diff --git a/src/service/xhr.cache.js b/src/service/xhr.cache.js
deleted file mode 100644
index 630caa5b8f4e..000000000000
--- a/src/service/xhr.cache.js
+++ /dev/null
@@ -1,114 +0,0 @@
-'use strict';
-
-/**
- * @ngdoc service
- * @name angular.service.$xhr.cache
- * @function
- *
- * @requires $xhr.bulk
- * @requires $defer
- * @requires $xhr.error
- * @requires $log
- *
- * @description
- * Acts just like the {@link angular.service.$xhr $xhr} service but caches responses for `GET`
- * requests. All cache misses are delegated to the $xhr service.
- *
- * @property {function()} delegate Function to delegate all the cache misses to. Defaults to
- * the {@link angular.service.$xhr $xhr} service.
- * @property {object} data The hashmap where all cached entries are stored.
- *
- * @param {string} method HTTP method.
- * @param {string} url Destination URL.
- * @param {(string|Object)=} post Request body.
- * @param {function(number, (string|Object))} success Response success callback.
- * @param {function(number, (string|Object))=} error Response error callback.
- * @param {boolean=} [verifyCache=false] If `true` then a result is immediately returned from cache
- * (if present) while a request is sent to the server for a fresh response that will update the
- * cached entry. The `success` function will be called when the response is received.
- * @param {boolean=} [sync=false] in case of cache hit execute `success` synchronously.
- */
-angularServiceInject('$xhr.cache', function($xhr, $defer, $error, $log) {
- var inflight = {}, self = this;
- function cache(method, url, post, success, error, verifyCache, sync) {
- if (isFunction(post)) {
- if (!isFunction(success)) {
- verifyCache = success;
- sync = error;
- error = null;
- } else {
- sync = verifyCache;
- verifyCache = error;
- error = success;
- }
- success = post;
- post = null;
- } else if (!isFunction(error)) {
- sync = verifyCache;
- verifyCache = error;
- error = null;
- }
-
- if (method == 'GET') {
- var data, dataCached;
- if ((dataCached = cache.data[url])) {
-
- if (sync) {
- success(200, copy(dataCached.value));
- } else {
- $defer(function() { success(200, copy(dataCached.value)); });
- }
-
- if (!verifyCache)
- return;
- }
-
- if ((data = inflight[url])) {
- data.successes.push(success);
- data.errors.push(error);
- } else {
- inflight[url] = {successes: [success], errors: [error]};
- cache.delegate(method, url, post,
- function(status, response) {
- if (status == 200)
- cache.data[url] = {value: response};
- var successes = inflight[url].successes;
- delete inflight[url];
- forEach(successes, function(success) {
- try {
- (success||noop)(status, copy(response));
- } catch(e) {
- $log.error(e);
- }
- });
- },
- function(status, response) {
- var errors = inflight[url].errors,
- successes = inflight[url].successes;
- delete inflight[url];
-
- forEach(errors, function(error, i) {
- try {
- if (isFunction(error)) {
- error(status, copy(response));
- } else {
- $error(
- {method: method, url: url, data: post, success: successes[i]},
- {status: status, body: response});
- }
- } catch(e) {
- $log.error(e);
- }
- });
- });
- }
-
- } else {
- cache.data = {};
- cache.delegate(method, url, post, success, error);
- }
- }
- cache.data = {};
- cache.delegate = $xhr;
- return cache;
-}, ['$xhr.bulk', '$defer', '$xhr.error', '$log']);
diff --git a/src/service/xhr.error.js b/src/service/xhr.error.js
deleted file mode 100644
index 01fb8fff9841..000000000000
--- a/src/service/xhr.error.js
+++ /dev/null
@@ -1,42 +0,0 @@
-'use strict';
-
-/**
- * @ngdoc service
- * @name angular.service.$xhr.error
- * @function
- * @requires $log
- *
- * @description
- * Error handler for {@link angular.service.$xhr $xhr service}. An application can replaces this
- * service with one specific for the application. The default implementation logs the error to
- * {@link angular.service.$log $log.error}.
- *
- * @param {Object} request Request object.
- *
- * The object has the following properties
- *
- * - `method` – `{string}` – The http request method.
- * - `url` – `{string}` – The request destination.
- * - `data` – `{(string|Object)=} – An optional request body.
- * - `success` – `{function()}` – The success callback function
- *
- * @param {Object} response Response object.
- *
- * The response object has the following properties:
- *
- * - status – {number} – Http status code.
- * - body – {string|Object} – Body of the response.
- *
- * @example
-
-
- fetch a non-existent file and log an error in the console:
-
-
-
- */
-angularServiceInject('$xhr.error', function($log){
- return function(request, response){
- $log.error('ERROR: XHR: ' + request.url, request, response);
- };
-}, ['$log']);
diff --git a/src/service/xhr.js b/src/service/xhr.js
deleted file mode 100644
index fe7d42d9c8cc..000000000000
--- a/src/service/xhr.js
+++ /dev/null
@@ -1,229 +0,0 @@
-'use strict';
-
-/**
- * @ngdoc service
- * @name angular.service.$xhr
- * @function
- * @requires $browser $xhr delegates all XHR requests to the `$browser.xhr()`. A mock version
- * of the $browser exists which allows setting expectations on XHR requests
- * in your tests
- * @requires $xhr.error $xhr delegates all non `2xx` response code to this service.
- * @requires $log $xhr delegates all exceptions to `$log.error()`.
- *
- * @description
- * Generates an XHR request. The $xhr service delegates all requests to
- * {@link angular.service.$browser $browser.xhr()} and adds error handling and security features.
- * While $xhr service provides nicer api than raw XmlHttpRequest, it is still considered a lower
- * level api in angular. For a higher level abstraction that utilizes `$xhr`, please check out the
- * {@link angular.service.$resource $resource} service.
- *
- * # Error handling
- * If no `error callback` is specified, XHR response with response code other then `2xx` will be
- * delegated to {@link angular.service.$xhr.error $xhr.error}. The `$xhr.error` can intercept the
- * request and process it in application specific way, or resume normal execution by calling the
- * request `success` method.
- *
- * # HTTP Headers
- * The $xhr service will automatically add certain http headers to all requests. These defaults can
- * be fully configured by accessing the `$xhr.defaults.headers` configuration object, which
- * currently contains this default configuration:
- *
- * - `$xhr.defaults.headers.common` (headers that are common for all requests):
- * - `Accept: application/json, text/plain, *\/*`
- * - `X-Requested-With: XMLHttpRequest`
- * - `$xhr.defaults.headers.post` (header defaults for HTTP POST requests):
- * - `Content-Type: application/x-www-form-urlencoded`
- *
- * To add or overwrite these defaults, simple add or remove a property from this configuration
- * object. To add headers for an HTTP method other than POST, simple create a new object with name
- * equal to the lowercased http method name, e.g. `$xhr.defaults.headers.get['My-Header']='value'`.
- *
- *
- * # Security Considerations
- * When designing web applications your design needs to consider security threats from
- * {@link http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx
- * JSON Vulnerability} and {@link http://en.wikipedia.org/wiki/Cross-site_request_forgery XSRF}.
- * Both server and the client must cooperate in order to eliminate these threats. Angular comes
- * pre-configured with strategies that address these issues, but for this to work backend server
- * cooperation is required.
- *
- * ## JSON Vulnerability Protection
- * A {@link http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx
- * JSON Vulnerability} allows third party web-site to turn your JSON resource URL into
- * {@link http://en.wikipedia.org/wiki/JSON#JSONP JSONP} request under some conditions. To
- * counter this your server can prefix all JSON requests with following string `")]}',\n"`.
- * Angular will automatically strip the prefix before processing it as JSON.
- *
- * For example if your server needs to return:
- *
- * ['one','two']
- *
- *
- * which is vulnerable to attack, your server can return:
- *
- * )]}',
- * ['one','two']
- *
- *
- * angular will strip the prefix, before processing the JSON.
- *
- *
- * ## Cross Site Request Forgery (XSRF) Protection
- * {@link http://en.wikipedia.org/wiki/Cross-site_request_forgery XSRF} is a technique by which an
- * unauthorized site can gain your user's private data. Angular provides following mechanism to
- * counter XSRF. When performing XHR requests, the $xhr service reads a token from a cookie
- * called `XSRF-TOKEN` and sets it as the HTTP header `X-XSRF-TOKEN`. Since only JavaScript that
- * runs on your domain could read the cookie, your server can be assured that the XHR came from
- * JavaScript running on your domain.
- *
- * To take advantage of this, your server needs to set a token in a JavaScript readable session
- * cookie called `XSRF-TOKEN` on first HTTP GET request. On subsequent non-GET requests the server
- * can verify that the cookie matches `X-XSRF-TOKEN` HTTP header, and therefore be sure that only
- * JavaScript running on your domain could have read the token. The token must be unique for each
- * user and must be verifiable by the server (to prevent the JavaScript making up its own tokens).
- * We recommend that the token is a digest of your site's authentication cookie with
- * {@link http://en.wikipedia.org/wiki/Rainbow_table salt for added security}.
- *
- * @param {string} method HTTP method to use. Valid values are: `GET`, `POST`, `PUT`, `DELETE`, and
- * `JSON`. `JSON` is a special case which causes a
- * [JSONP](http://en.wikipedia.org/wiki/JSON#JSONP) cross domain request using script tag
- * insertion.
- * @param {string} url Relative or absolute URL specifying the destination of the request. For
- * `JSON` requests, `url` should include `JSON_CALLBACK` string to be replaced with a name of an
- * angular generated callback function.
- * @param {(string|Object)=} post Request content as either a string or an object to be stringified
- * as JSON before sent to the server.
- * @param {function(number, (string|Object))} success A function to be called when the response is
- * received. The success function will be called with:
- *
- * - {number} code [HTTP status code](http://en.wikipedia.org/wiki/List_of_HTTP_status_codes) of
- * the response. This will currently always be 200, since all non-200 responses are routed to
- * {@link angular.service.$xhr.error} service (or custom error callback).
- * - {string|Object} response Response object as string or an Object if the response was in JSON
- * format.
- * @param {function(number, (string|Object))} error A function to be called if the response code is
- * not 2xx.. Accepts the same arguments as success, above.
- *
- * @example
-
-
-
-
');
rootScope.$digest();
- $browser.xhr.flush(); // no that we have to requests pending, flush!
+ $httpBackend.flush(); // no that we have two requests pending, flush!
expect(rootScope.$element.text()).toEqual('2');
});
+
+ it('should clear the content when error during xhr request', function() {
+ $route.when('/foo', {controller: angular.noop, template: 'myUrl1'});
+
+ $location.path('/foo');
+ $httpBackend.expect('GET', 'myUrl1').respond(404, '');
+ rootScope.$element.text('content');
+
+ rootScope.$digest();
+ $httpBackend.flush();
+
+ expect(rootScope.$element.text()).toBe('');
+ });
+
+ it('should be async even if served from cache', function() {
+ $route.when('/foo', {controller: angular.noop, template: 'myUrl1'});
+ rootScope.$service('$cacheFactory').get('templates').put('myUrl1', 'my partial');
+ $location.path('/foo');
+
+ var called = 0,
+ element = rootScope.$element;
+ // we want to assert only during first watch
+ rootScope.$watch(function() {
+ if (!called++) expect(element.text()).toBe('');
+ });
+
+ rootScope.$digest();
+ expect(element.text()).toBe('my partial');
+ });
});