aboutsummaryrefslogtreecommitdiffstats
path: root/src/ng/http.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/ng/http.js')
-rw-r--r--src/ng/http.js214
1 files changed, 175 insertions, 39 deletions
diff --git a/src/ng/http.js b/src/ng/http.js
index e4d695c4..d54e8bd3 100644
--- a/src/ng/http.js
+++ b/src/ng/http.js
@@ -155,20 +155,52 @@ function $HttpProvider() {
xsrfHeaderName: 'X-XSRF-TOKEN'
};
- var providerResponseInterceptors = this.responseInterceptors = [];
+ /**
+ * Are order by request. I.E. they are applied in the same order as
+ * array on request, but revers order on response.
+ */
+ var interceptorFactories = this.interceptors = [];
+ /**
+ * For historical reasons, response interceptors ordered by the order in which
+ * they are applied to response. (This is in revers to interceptorFactories)
+ */
+ var responseInterceptorFactories = this.responseInterceptors = [];
this.$get = ['$httpBackend', '$browser', '$cacheFactory', '$rootScope', '$q', '$injector',
function($httpBackend, $browser, $cacheFactory, $rootScope, $q, $injector) {
- var defaultCache = $cacheFactory('$http'),
- responseInterceptors = [];
+ var defaultCache = $cacheFactory('$http');
- forEach(providerResponseInterceptors, function(interceptor) {
- responseInterceptors.push(
- isString(interceptor)
- ? $injector.get(interceptor)
- : $injector.invoke(interceptor)
- );
+ /**
+ * Interceptors stored in reverse order. Inner interceptors before outer interceptors.
+ * The reversal is needed so that we can build up the interception chain around the
+ * server request.
+ */
+ var reversedInterceptors = [];
+
+ forEach(interceptorFactories, function(interceptorFactory) {
+ reversedInterceptors.unshift(isString(interceptorFactory)
+ ? $injector.get(interceptorFactory) : $injector.invoke(interceptorFactory));
+ });
+
+ forEach(responseInterceptorFactories, function(interceptorFactory, index) {
+ var responseFn = isString(interceptorFactory)
+ ? $injector.get(interceptorFactory)
+ : $injector.invoke(interceptorFactory);
+
+ /**
+ * Response interceptors go before "around" interceptors (no real reason, just
+ * had to pick one.) But they are already revesed, so we can't use unshift, hence
+ * the splice.
+ */
+ reversedInterceptors.splice(index, 0, {
+ response: function(response) {
+ return responseFn($q.when(response));
+ },
+ responseError: function(response) {
+ return responseFn($q.reject(response));
+ }
+ });
});
@@ -310,7 +342,90 @@ function $HttpProvider() {
* To skip it, set configuration property `cache` to `false`.
*
*
- * # Response interceptors
+ * # Interceptors
+ *
+ * Before you start creating interceptors, be sure to understand the
+ * {@link ng.$q $q and deferred/promise APIs}.
+ *
+ * For purposes of global error handling, authentication or any kind of synchronous or
+ * asynchronous pre-processing of request or postprocessing of responses, it is desirable to be
+ * able to intercept requests before they are handed to the server and
+ * responses before they are handed over to the application code that
+ * initiated these requests. The interceptors leverage the {@link ng.$q
+ * promise APIs} to fulfil this need for both synchronous and asynchronous pre-processing.
+ *
+ * The interceptors are service factories that are registered with the $httpProvider by
+ * adding them to the `$httpProvider.interceptors` array. The factory is called and
+ * injected with dependencies (if specified) and returns the interceptor.
+ *
+ * There are two kinds of interceptors (and two kinds of rejection interceptors):
+ *
+ * * `request`: interceptors get called with http `config` object. The function is free to modify
+ * the `config` or create a new one. The function needs to return the `config` directly or as a
+ * promise.
+ * * `requestError`: interceptor gets called when a previous interceptor threw an error or resolved
+ * with a rejection.
+ * * `response`: interceptors get called with http `response` object. The function is free to modify
+ * the `response` or create a new one. The function needs to return the `response` directly or as a
+ * promise.
+ * * `responseError`: interceptor gets called when a previous interceptor threw an error or resolved
+ * with a rejection.
+ *
+ *
+ * <pre>
+ * // register the interceptor as a service
+ * $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) {
+ * return {
+ * // optional method
+ * 'request': function(config) {
+ * // do something on success
+ * return config || $q.when(config);
+ * },
+ *
+ * // optional method
+ * 'requestError': function(rejection) {
+ * // do something on error
+ * if (canRecover(rejection)) {
+ * return responseOrNewPromise
+ * }
+ * return $q.reject(rejection);
+ * },
+ *
+ *
+ *
+ * // optional method
+ * 'response': function(response) {
+ * // do something on success
+ * return response || $q.when(response);
+ * },
+ *
+ * // optional method
+ * 'responseError': function(rejection) {
+ * // do something on error
+ * if (canRecover(rejection)) {
+ * return responseOrNewPromise
+ * }
+ * return $q.reject(rejection);
+ * };
+ * }
+ * });
+ *
+ * $httpProvider.interceptors.push('myHttpInterceptor');
+ *
+ *
+ * // register the interceptor via an anonymous factory
+ * $httpProvider.interceptors.push(function($q, dependency1, dependency2) {
+ * return {
+ * 'request': function(config) {
+ * // same as above
+ * },
+ * 'response': function(response) {
+ * // same as above
+ * }
+ * });
+ * </pre>
+ *
+ * # Response interceptors (DEPRECATED)
*
* Before you start creating interceptors, be sure to understand the
* {@link ng.$q $q and deferred/promise APIs}.
@@ -526,45 +641,66 @@ function $HttpProvider() {
</file>
</example>
*/
- function $http(config) {
+ function $http(requestConfig) {
+ var config = {
+ transformRequest: defaults.transformRequest,
+ transformResponse: defaults.transformResponse
+ };
+ var headers = {};
+
+ extend(config, requestConfig);
+ config.headers = headers;
config.method = uppercase(config.method);
- var xsrfHeader = {},
- xsrfCookieName = config.xsrfCookieName || defaults.xsrfCookieName,
- xsrfHeaderName = config.xsrfHeaderName || defaults.xsrfHeaderName,
- xsrfToken = isSameDomain(config.url, $browser.url()) ?
- $browser.cookies()[xsrfCookieName] : undefined;
- xsrfHeader[xsrfHeaderName] = xsrfToken;
-
- var reqTransformFn = config.transformRequest || defaults.transformRequest,
- respTransformFn = config.transformResponse || defaults.transformResponse,
- defHeaders = defaults.headers,
- reqHeaders = extend(xsrfHeader,
- defHeaders.common, defHeaders[lowercase(config.method)], config.headers),
- reqData = transformData(config.data, headersGetter(reqHeaders), reqTransformFn),
- promise;
-
- // strip content-type if data is undefined
- if (isUndefined(config.data)) {
- delete reqHeaders['Content-Type'];
- }
+ extend(headers,
+ defaults.headers.common,
+ defaults.headers[lowercase(config.method)],
+ requestConfig.headers);
- if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) {
- config.withCredentials = defaults.withCredentials;
+ var xsrfValue = isSameDomain(config.url, $browser.url())
+ ? $browser.cookies()[config.xsrfCookieName || defaults.xsrfCookieName]
+ : undefined;
+ if (xsrfValue) {
+ headers[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue;
}
- // send request
- promise = sendReq(config, reqData, reqHeaders);
+ var serverRequest = function(config) {
+ var reqData = transformData(config.data, headersGetter(headers), config.transformRequest);
- // transform future response
- promise = promise.then(transformResponse, transformResponse);
+ // strip content-type if data is undefined
+ if (isUndefined(config.data)) {
+ delete headers['Content-Type'];
+ }
+
+ if (isUndefined(config.withCredentials) && !isUndefined(defaults.withCredentials)) {
+ config.withCredentials = defaults.withCredentials;
+ }
+
+ // send request
+ return sendReq(config, reqData, headers).then(transformResponse, transformResponse);
+ };
+
+ var chain = [serverRequest, undefined];
+ var promise = $q.when(config);
// apply interceptors
- forEach(responseInterceptors, function(interceptor) {
- promise = interceptor(promise);
+ forEach(reversedInterceptors, function(interceptor) {
+ if (interceptor.request || interceptor.requestError) {
+ chain.unshift(interceptor.request, interceptor.requestError);
+ }
+ if (interceptor.response || interceptor.responseError) {
+ chain.push(interceptor.response, interceptor.responseError);
+ }
});
+ while(chain.length) {
+ var thenFn = chain.shift();
+ var rejectFn = chain.shift();
+
+ promise = promise.then(thenFn, rejectFn);
+ };
+
promise.success = function(fn) {
promise.then(function(response) {
fn(response.data, response.status, response.headers, config);
@@ -584,7 +720,7 @@ function $HttpProvider() {
function transformResponse(response) {
// make a copy since the response must be cacheable
var resp = extend({}, response, {
- data: transformData(response.data, response.headers, respTransformFn)
+ data: transformData(response.data, response.headers, config.transformResponse)
});
return (isSuccess(response.status))
? resp