diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/ng/compile.js | 8 | ||||
| -rw-r--r-- | src/ng/http.js | 214 | ||||
| -rw-r--r-- | src/ngMock/angular-mocks.js | 8 |
3 files changed, 184 insertions, 46 deletions
diff --git a/src/ng/compile.js b/src/ng/compile.js index 6606dc6c..68a3dca1 100644 --- a/src/ng/compile.js +++ b/src/ng/compile.js @@ -1018,10 +1018,10 @@ function $CompileProvider($provide) { while(linkQueue.length) { - var controller = linkQueue.pop(), - linkRootElement = linkQueue.pop(), - beforeTemplateLinkNode = linkQueue.pop(), - scope = linkQueue.pop(), + var scope = linkQueue.shift(), + beforeTemplateLinkNode = linkQueue.shift(), + linkRootElement = linkQueue.shift(), + controller = linkQueue.shift(), linkNode = compileNode; if (beforeTemplateLinkNode !== beforeTemplateCompileNode) { 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 diff --git a/src/ngMock/angular-mocks.js b/src/ngMock/angular-mocks.js index 8c91e628..3980a391 100644 --- a/src/ngMock/angular-mocks.js +++ b/src/ngMock/angular-mocks.js @@ -826,7 +826,7 @@ angular.mock.dump = function(object) { </pre> */ angular.mock.$HttpBackendProvider = function() { - this.$get = [createHttpBackendMock]; + this.$get = ['$rootScope', createHttpBackendMock]; }; /** @@ -843,7 +843,7 @@ angular.mock.$HttpBackendProvider = function() { * @param {Object=} $browser Auto-flushing enabled if specified * @return {Object} Instance of $httpBackend mock */ -function createHttpBackendMock($delegate, $browser) { +function createHttpBackendMock($rootScope, $delegate, $browser) { var definitions = [], expectations = [], responses = [], @@ -1173,6 +1173,7 @@ function createHttpBackendMock($delegate, $browser) { * is called an exception is thrown (as this typically a sign of programming error). */ $httpBackend.flush = function(count) { + $rootScope.$digest(); if (!responses.length) throw Error('No pending request to flush !'); if (angular.isDefined(count)) { @@ -1205,6 +1206,7 @@ function createHttpBackendMock($delegate, $browser) { * </pre> */ $httpBackend.verifyNoOutstandingExpectation = function() { + $rootScope.$digest(); if (expectations.length) { throw Error('Unsatisfied requests: ' + expectations.join(', ')); } @@ -1606,7 +1608,7 @@ angular.module('ngMockE2E', ['ng']).config(function($provide) { * control how a matched request is handled. */ angular.mock.e2e = {}; -angular.mock.e2e.$httpBackendDecorator = ['$delegate', '$browser', createHttpBackendMock]; +angular.mock.e2e.$httpBackendDecorator = ['$rootScope', '$delegate', '$browser', createHttpBackendMock]; angular.mock.clearDataCache = function() { |
