mirror of
https://github.com/Hopiu/angular.js.git
synced 2026-03-17 07:40:22 +00:00
This removes some outdated advice which no longer is true against the latest angular version. The information about unit testing with ngMocks remains, because it's always good to have information like that easily found. This little snippet is not worded perfectly, and is not a very good example unit test, so additional work is needed here. Relates to #5206 Closes #5485
1033 lines
38 KiB
JavaScript
1033 lines
38 KiB
JavaScript
'use strict';
|
||
|
||
/**
|
||
* Parse headers into key value object
|
||
*
|
||
* @param {string} headers Raw headers as a string
|
||
* @returns {Object} Parsed headers as key value object
|
||
*/
|
||
function parseHeaders(headers) {
|
||
var parsed = {}, key, val, i;
|
||
|
||
if (!headers) return parsed;
|
||
|
||
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;
|
||
}
|
||
|
||
|
||
/**
|
||
* Returns a function that provides access to parsed headers.
|
||
*
|
||
* Headers are lazy parsed when first requested.
|
||
* @see parseHeaders
|
||
*
|
||
* @param {(string|Object)} headers Headers to provide access to.
|
||
* @returns {function(string=)} Returns a getter function which if called with:
|
||
*
|
||
* - if called with single an argument returns a single header value or null
|
||
* - if called with no arguments returns an object containing all headers.
|
||
*/
|
||
function headersGetter(headers) {
|
||
var headersObj = isObject(headers) ? headers : undefined;
|
||
|
||
return function(name) {
|
||
if (!headersObj) headersObj = parseHeaders(headers);
|
||
|
||
if (name) {
|
||
return headersObj[lowercase(name)] || null;
|
||
}
|
||
|
||
return headersObj;
|
||
};
|
||
}
|
||
|
||
|
||
/**
|
||
* Chain all given functions
|
||
*
|
||
* This function is used for both request and response transforming
|
||
*
|
||
* @param {*} data Data to transform.
|
||
* @param {function(string=)} headers Http headers getter fn.
|
||
* @param {(function|Array.<function>)} fns Function or an array of functions.
|
||
* @returns {*} Transformed data.
|
||
*/
|
||
function transformData(data, headers, fns) {
|
||
if (isFunction(fns))
|
||
return fns(data, headers);
|
||
|
||
forEach(fns, function(fn) {
|
||
data = fn(data, headers);
|
||
});
|
||
|
||
return data;
|
||
}
|
||
|
||
|
||
function isSuccess(status) {
|
||
return 200 <= status && status < 300;
|
||
}
|
||
|
||
|
||
function $HttpProvider() {
|
||
var JSON_START = /^\s*(\[|\{[^\{])/,
|
||
JSON_END = /[\}\]]\s*$/,
|
||
PROTECTION_PREFIX = /^\)\]\}',?\n/,
|
||
CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': 'application/json;charset=utf-8'};
|
||
|
||
var defaults = this.defaults = {
|
||
// transform incoming response data
|
||
transformResponse: [function(data) {
|
||
if (isString(data)) {
|
||
// strip json vulnerability protection prefix
|
||
data = data.replace(PROTECTION_PREFIX, '');
|
||
if (JSON_START.test(data) && JSON_END.test(data))
|
||
data = fromJson(data);
|
||
}
|
||
return data;
|
||
}],
|
||
|
||
// transform outgoing request data
|
||
transformRequest: [function(d) {
|
||
return isObject(d) && !isFile(d) ? toJson(d) : d;
|
||
}],
|
||
|
||
// default headers
|
||
headers: {
|
||
common: {
|
||
'Accept': 'application/json, text/plain, */*'
|
||
},
|
||
post: copy(CONTENT_TYPE_APPLICATION_JSON),
|
||
put: copy(CONTENT_TYPE_APPLICATION_JSON),
|
||
patch: copy(CONTENT_TYPE_APPLICATION_JSON)
|
||
},
|
||
|
||
xsrfCookieName: 'XSRF-TOKEN',
|
||
xsrfHeaderName: 'X-XSRF-TOKEN'
|
||
};
|
||
|
||
/**
|
||
* Are ordered by request, i.e. they are applied in the same order as the
|
||
* array, on request, but reverse order, on response.
|
||
*/
|
||
var interceptorFactories = this.interceptors = [];
|
||
|
||
/**
|
||
* For historical reasons, response interceptors are ordered by the order in which
|
||
* they are applied to the response. (This is the opposite of interceptorFactories)
|
||
*/
|
||
var responseInterceptorFactories = this.responseInterceptors = [];
|
||
|
||
this.$get = ['$httpBackend', '$browser', '$cacheFactory', '$rootScope', '$q', '$injector',
|
||
function($httpBackend, $browser, $cacheFactory, $rootScope, $q, $injector) {
|
||
|
||
var defaultCache = $cacheFactory('$http');
|
||
|
||
/**
|
||
* 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 reversed, 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));
|
||
}
|
||
});
|
||
});
|
||
|
||
|
||
/**
|
||
* @ngdoc function
|
||
* @name ng.$http
|
||
* @requires $httpBackend
|
||
* @requires $browser
|
||
* @requires $cacheFactory
|
||
* @requires $rootScope
|
||
* @requires $q
|
||
* @requires $injector
|
||
*
|
||
* @description
|
||
* The `$http` service is a core Angular service that facilitates communication with the remote
|
||
* HTTP servers via the browser's {@link https://developer.mozilla.org/en/xmlhttprequest
|
||
* XMLHttpRequest} object or via {@link http://en.wikipedia.org/wiki/JSONP JSONP}.
|
||
*
|
||
* For unit testing applications that use `$http` service, see
|
||
* {@link ngMock.$httpBackend $httpBackend mock}.
|
||
*
|
||
* For a higher level of abstraction, please check out the {@link ngResource.$resource
|
||
* $resource} service.
|
||
*
|
||
* The $http API is based on the {@link ng.$q deferred/promise APIs} exposed by
|
||
* the $q service. While for simple usage patterns this doesn't matter much, for advanced usage
|
||
* it is important to familiarize yourself with these APIs and the guarantees they provide.
|
||
*
|
||
*
|
||
* # General usage
|
||
* The `$http` service is a function which takes a single argument — a configuration object —
|
||
* that is used to generate an HTTP request and returns a {@link ng.$q promise}
|
||
* with two $http specific methods: `success` and `error`.
|
||
*
|
||
* <pre>
|
||
* $http({method: 'GET', url: '/someUrl'}).
|
||
* success(function(data, status, headers, config) {
|
||
* // this callback will be called asynchronously
|
||
* // when the response is available
|
||
* }).
|
||
* error(function(data, status, headers, config) {
|
||
* // called asynchronously if an error occurs
|
||
* // or server returns response with an error status.
|
||
* });
|
||
* </pre>
|
||
*
|
||
* Since the returned value of calling the $http function is a `promise`, you can also use
|
||
* the `then` method to register callbacks, and these callbacks will receive a single argument –
|
||
* an object representing the response. See the API signature and type info below for more
|
||
* details.
|
||
*
|
||
* A response status code between 200 and 299 is considered a success status and
|
||
* will result in the success callback being called. Note that if the response is a redirect,
|
||
* XMLHttpRequest will transparently follow it, meaning that the error callback will not be
|
||
* called for such responses.
|
||
*
|
||
* # Writing Unit Tests that use $http
|
||
* When unit testing (using {@link api/ngMock ngMock}), it is necessary to call
|
||
* {@link api/ngMock.$httpBackend#methods_flush $httpBackend.flush()} to flush each pending
|
||
* request using trained responses.
|
||
*
|
||
* ```
|
||
* $httpBackend.expectGET(...);
|
||
* $http.get(...);
|
||
* $httpBackend.flush();
|
||
* ```
|
||
*
|
||
* # Shortcut methods
|
||
*
|
||
* Since all invocations of the $http service require passing in an HTTP method and URL, and
|
||
* POST/PUT requests require request data to be provided as well, shortcut methods
|
||
* were created:
|
||
*
|
||
* <pre>
|
||
* $http.get('/someUrl').success(successCallback);
|
||
* $http.post('/someUrl', data).success(successCallback);
|
||
* </pre>
|
||
*
|
||
* Complete list of shortcut methods:
|
||
*
|
||
* - {@link ng.$http#methods_get $http.get}
|
||
* - {@link ng.$http#methods_head $http.head}
|
||
* - {@link ng.$http#methods_post $http.post}
|
||
* - {@link ng.$http#methods_put $http.put}
|
||
* - {@link ng.$http#methods_delete $http.delete}
|
||
* - {@link ng.$http#methods_jsonp $http.jsonp}
|
||
*
|
||
*
|
||
* # Setting HTTP Headers
|
||
*
|
||
* The $http service will automatically add certain HTTP headers to all requests. These defaults
|
||
* can be fully configured by accessing the `$httpProvider.defaults.headers` configuration
|
||
* object, which currently contains this default configuration:
|
||
*
|
||
* - `$httpProvider.defaults.headers.common` (headers that are common for all requests):
|
||
* - `Accept: application/json, text/plain, * / *`
|
||
* - `$httpProvider.defaults.headers.post`: (header defaults for POST requests)
|
||
* - `Content-Type: application/json`
|
||
* - `$httpProvider.defaults.headers.put` (header defaults for PUT requests)
|
||
* - `Content-Type: application/json`
|
||
*
|
||
* To add or overwrite these defaults, simply add or remove a property from these configuration
|
||
* objects. To add headers for an HTTP method other than POST or PUT, simply add a new object
|
||
* with the lowercased HTTP method name as the key, e.g.
|
||
* `$httpProvider.defaults.headers.get = { 'My-Header' : 'value' }.
|
||
*
|
||
* The defaults can also be set at runtime via the `$http.defaults` object in the same
|
||
* fashion. For example:
|
||
*
|
||
* ```
|
||
* module.run(function($http) {
|
||
* $http.defaults.headers.common.Authentication = 'Basic YmVlcDpib29w'
|
||
* });
|
||
* ```
|
||
*
|
||
* In addition, you can supply a `headers` property in the config object passed when
|
||
* calling `$http(config)`, which overrides the defaults without changing them globally.
|
||
*
|
||
*
|
||
* # Transforming Requests and Responses
|
||
*
|
||
* Both requests and responses can be transformed using transform functions. By default, Angular
|
||
* applies these transformations:
|
||
*
|
||
* Request transformations:
|
||
*
|
||
* - If the `data` property of the request configuration object contains an object, serialize it
|
||
* into JSON format.
|
||
*
|
||
* Response transformations:
|
||
*
|
||
* - If XSRF prefix is detected, strip it (see Security Considerations section below).
|
||
* - If JSON response is detected, deserialize it using a JSON parser.
|
||
*
|
||
* To globally augment or override the default transforms, modify the
|
||
* `$httpProvider.defaults.transformRequest` and `$httpProvider.defaults.transformResponse`
|
||
* properties. These properties are by default an array of transform functions, which allows you
|
||
* to `push` or `unshift` a new transformation function into the transformation chain. You can
|
||
* also decide to completely override any default transformations by assigning your
|
||
* transformation functions to these properties directly without the array wrapper. These defaults
|
||
* are again available on the $http factory at run-time, which may be useful if you have run-time
|
||
* services you wish to be involved in your transformations.
|
||
*
|
||
* Similarly, to locally override the request/response transforms, augment the
|
||
* `transformRequest` and/or `transformResponse` properties of the configuration object passed
|
||
* into `$http`.
|
||
*
|
||
*
|
||
* # Caching
|
||
*
|
||
* To enable caching, set the request configuration `cache` property to `true` (to use default
|
||
* cache) or to a custom cache object (built with {@link ng.$cacheFactory `$cacheFactory`}).
|
||
* When the cache is enabled, `$http` stores the response from the server in the specified
|
||
* cache. The next time the same request is made, the response is served from the cache without
|
||
* sending a request to the server.
|
||
*
|
||
* Note that even if the response is served from cache, delivery of the data is asynchronous in
|
||
* the same way that real requests are.
|
||
*
|
||
* If there are multiple GET requests for the same URL that should be cached using the same
|
||
* cache, but the cache is not populated yet, only one request to the server will be made and
|
||
* the remaining requests will be fulfilled using the response from the first request.
|
||
*
|
||
* You can change the default cache to a new object (built with
|
||
* {@link ng.$cacheFactory `$cacheFactory`}) by updating the
|
||
* {@link ng.$http#properties_defaults `$http.defaults.cache`} property. All requests who set
|
||
* their `cache` property to `true` will now use this cache object.
|
||
*
|
||
* If you set the default cache to `false` then only requests that specify their own custom
|
||
* cache object will be cached.
|
||
*
|
||
* # 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 fulfill 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');
|
||
*
|
||
*
|
||
* // alternatively, 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}.
|
||
*
|
||
* For purposes of global error handling, authentication or any kind of synchronous or
|
||
* asynchronous preprocessing of received responses, it is desirable to be able to intercept
|
||
* responses for http requests before they are handed over to the application code that
|
||
* initiated these requests. The response interceptors leverage the {@link ng.$q
|
||
* promise apis} to fulfil this need for both synchronous and asynchronous preprocessing.
|
||
*
|
||
* The interceptors are service factories that are registered with the $httpProvider by
|
||
* adding them to the `$httpProvider.responseInterceptors` array. The factory is called and
|
||
* injected with dependencies (if specified) and returns the interceptor — a function that
|
||
* takes a {@link ng.$q promise} and returns the original or a new promise.
|
||
*
|
||
* <pre>
|
||
* // register the interceptor as a service
|
||
* $provide.factory('myHttpInterceptor', function($q, dependency1, dependency2) {
|
||
* return function(promise) {
|
||
* return promise.then(function(response) {
|
||
* // do something on success
|
||
* return response;
|
||
* }, function(response) {
|
||
* // do something on error
|
||
* if (canRecover(response)) {
|
||
* return responseOrNewPromise
|
||
* }
|
||
* return $q.reject(response);
|
||
* });
|
||
* }
|
||
* });
|
||
*
|
||
* $httpProvider.responseInterceptors.push('myHttpInterceptor');
|
||
*
|
||
*
|
||
* // register the interceptor via an anonymous factory
|
||
* $httpProvider.responseInterceptors.push(function($q, dependency1, dependency2) {
|
||
* return function(promise) {
|
||
* // same as above
|
||
* }
|
||
* });
|
||
* </pre>
|
||
*
|
||
*
|
||
* # Security Considerations
|
||
*
|
||
* When designing web applications, consider security threats from:
|
||
*
|
||
* - {@link http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx
|
||
* JSON vulnerability}
|
||
* - {@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 website to turn your JSON resource URL into
|
||
* {@link http://en.wikipedia.org/wiki/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:
|
||
* <pre>
|
||
* ['one','two']
|
||
* </pre>
|
||
*
|
||
* which is vulnerable to attack, your server can return:
|
||
* <pre>
|
||
* )]}',
|
||
* ['one','two']
|
||
* </pre>
|
||
*
|
||
* 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 a mechanism
|
||
* to counter XSRF. When performing XHR requests, the $http service reads a token from a cookie
|
||
* (by default, `XSRF-TOKEN`) and sets it as an 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. The header will not be set for
|
||
* cross-domain requests.
|
||
*
|
||
* To take advantage of this, your server needs to set a token in a JavaScript readable session
|
||
* cookie called `XSRF-TOKEN` on the first HTTP GET request. On subsequent XHR 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 sent the request. The token must be
|
||
* unique for each user and must be verifiable by the server (to prevent the JavaScript from
|
||
* making up its own tokens). We recommend that the token is a digest of your site's
|
||
* authentication cookie with a {@link https://en.wikipedia.org/wiki/Salt_(cryptography) salt}
|
||
* for added security.
|
||
*
|
||
* The name of the headers can be specified using the xsrfHeaderName and xsrfCookieName
|
||
* properties of either $httpProvider.defaults at config-time, $http.defaults at run-time,
|
||
* or the per-request config object.
|
||
*
|
||
*
|
||
* @param {object} config Object describing the request to be made and how it should be
|
||
* processed. The object has following properties:
|
||
*
|
||
* - **method** – `{string}` – HTTP method (e.g. 'GET', 'POST', etc)
|
||
* - **url** – `{string}` – Absolute or relative URL of the resource that is being requested.
|
||
* - **params** – `{Object.<string|Object>}` – Map of strings or objects which will be turned
|
||
* to `?key1=value1&key2=value2` after the url. If the value is not a string, it will be
|
||
* JSONified.
|
||
* - **data** – `{string|Object}` – Data to be sent as the request message data.
|
||
* - **headers** – `{Object}` – Map of strings or functions which return strings representing
|
||
* HTTP headers to send to the server. If the return value of a function is null, the
|
||
* header will not be sent.
|
||
* - **xsrfHeaderName** – `{string}` – Name of HTTP header to populate with the XSRF token.
|
||
* - **xsrfCookieName** – `{string}` – Name of cookie containing the XSRF token.
|
||
* - **transformRequest** –
|
||
* `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
|
||
* transform function or an array of such functions. The transform function takes the http
|
||
* request body and headers and returns its transformed (typically serialized) version.
|
||
* - **transformResponse** –
|
||
* `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` –
|
||
* transform function or an array of such functions. The transform function takes the http
|
||
* response body and headers and returns its transformed (typically deserialized) version.
|
||
* - **cache** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the
|
||
* GET request, otherwise if a cache instance built with
|
||
* {@link ng.$cacheFactory $cacheFactory}, this cache will be used for
|
||
* caching.
|
||
* - **timeout** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise}
|
||
* that should abort the request when resolved.
|
||
* - **withCredentials** - `{boolean}` - whether to to set the `withCredentials` flag on the
|
||
* XHR object. See {@link https://developer.mozilla.org/en/http_access_control#section_5
|
||
* requests with credentials} for more information.
|
||
* - **responseType** - `{string}` - see {@link
|
||
* https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType requestType}.
|
||
*
|
||
* @returns {HttpPromise} Returns a {@link ng.$q promise} object with the
|
||
* standard `then` method and two http specific methods: `success` and `error`. The `then`
|
||
* method takes two arguments a success and an error callback which will be called with a
|
||
* response object. The `success` and `error` methods take a single argument - a function that
|
||
* will be called when the request succeeds or fails respectively. The arguments passed into
|
||
* these functions are destructured representation of the response object passed into the
|
||
* `then` method. The response object has these properties:
|
||
*
|
||
* - **data** – `{string|Object}` – The response body transformed with the transform
|
||
* functions.
|
||
* - **status** – `{number}` – HTTP status code of the response.
|
||
* - **headers** – `{function([headerName])}` – Header getter function.
|
||
* - **config** – `{Object}` – The configuration object that was used to generate the request.
|
||
*
|
||
* @property {Array.<Object>} pendingRequests Array of config objects for currently pending
|
||
* requests. This is primarily meant to be used for debugging purposes.
|
||
*
|
||
*
|
||
* @example
|
||
<example>
|
||
<file name="index.html">
|
||
<div ng-controller="FetchCtrl">
|
||
<select ng-model="method">
|
||
<option>GET</option>
|
||
<option>JSONP</option>
|
||
</select>
|
||
<input type="text" ng-model="url" size="80"/>
|
||
<button ng-click="fetch()">fetch</button><br>
|
||
<button ng-click="updateModel('GET', 'http-hello.html')">Sample GET</button>
|
||
<button
|
||
ng-click="updateModel('JSONP',
|
||
'http://angularjs.org/greet.php?callback=JSON_CALLBACK&name=Super%20Hero')">
|
||
Sample JSONP
|
||
</button>
|
||
<button
|
||
ng-click="updateModel('JSONP', 'http://angularjs.org/doesntexist&callback=JSON_CALLBACK')">
|
||
Invalid JSONP
|
||
</button>
|
||
<pre>http status code: {{status}}</pre>
|
||
<pre>http response data: {{data}}</pre>
|
||
</div>
|
||
</file>
|
||
<file name="script.js">
|
||
function FetchCtrl($scope, $http, $templateCache) {
|
||
$scope.method = 'GET';
|
||
$scope.url = 'http-hello.html';
|
||
|
||
$scope.fetch = function() {
|
||
$scope.code = null;
|
||
$scope.response = null;
|
||
|
||
$http({method: $scope.method, url: $scope.url, cache: $templateCache}).
|
||
success(function(data, status) {
|
||
$scope.status = status;
|
||
$scope.data = data;
|
||
}).
|
||
error(function(data, status) {
|
||
$scope.data = data || "Request failed";
|
||
$scope.status = status;
|
||
});
|
||
};
|
||
|
||
$scope.updateModel = function(method, url) {
|
||
$scope.method = method;
|
||
$scope.url = url;
|
||
};
|
||
}
|
||
</file>
|
||
<file name="http-hello.html">
|
||
Hello, $http!
|
||
</file>
|
||
<file name="scenario.js">
|
||
it('should make an xhr GET request', function() {
|
||
element(':button:contains("Sample GET")').click();
|
||
element(':button:contains("fetch")').click();
|
||
expect(binding('status')).toBe('200');
|
||
expect(binding('data')).toMatch(/Hello, \$http!/);
|
||
});
|
||
|
||
it('should make a JSONP request to angularjs.org', function() {
|
||
element(':button:contains("Sample JSONP")').click();
|
||
element(':button:contains("fetch")').click();
|
||
expect(binding('status')).toBe('200');
|
||
expect(binding('data')).toMatch(/Super Hero!/);
|
||
});
|
||
|
||
it('should make JSONP request to invalid URL and invoke the error handler',
|
||
function() {
|
||
element(':button:contains("Invalid JSONP")').click();
|
||
element(':button:contains("fetch")').click();
|
||
expect(binding('status')).toBe('0');
|
||
expect(binding('data')).toBe('Request failed');
|
||
});
|
||
</file>
|
||
</example>
|
||
*/
|
||
function $http(requestConfig) {
|
||
var config = {
|
||
transformRequest: defaults.transformRequest,
|
||
transformResponse: defaults.transformResponse
|
||
};
|
||
var headers = mergeHeaders(requestConfig);
|
||
|
||
extend(config, requestConfig);
|
||
config.headers = headers;
|
||
config.method = uppercase(config.method);
|
||
|
||
var xsrfValue = urlIsSameOrigin(config.url)
|
||
? $browser.cookies()[config.xsrfCookieName || defaults.xsrfCookieName]
|
||
: undefined;
|
||
if (xsrfValue) {
|
||
headers[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue;
|
||
}
|
||
|
||
|
||
var serverRequest = function(config) {
|
||
headers = config.headers;
|
||
var reqData = transformData(config.data, headersGetter(headers), config.transformRequest);
|
||
|
||
// strip content-type if data is undefined
|
||
if (isUndefined(config.data)) {
|
||
forEach(headers, function(value, header) {
|
||
if (lowercase(header) === 'content-type') {
|
||
delete headers[header];
|
||
}
|
||
});
|
||
}
|
||
|
||
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(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);
|
||
});
|
||
return promise;
|
||
};
|
||
|
||
promise.error = function(fn) {
|
||
promise.then(null, function(response) {
|
||
fn(response.data, response.status, response.headers, config);
|
||
});
|
||
return promise;
|
||
};
|
||
|
||
return promise;
|
||
|
||
function transformResponse(response) {
|
||
// make a copy since the response must be cacheable
|
||
var resp = extend({}, response, {
|
||
data: transformData(response.data, response.headers, config.transformResponse)
|
||
});
|
||
return (isSuccess(response.status))
|
||
? resp
|
||
: $q.reject(resp);
|
||
}
|
||
|
||
function mergeHeaders(config) {
|
||
var defHeaders = defaults.headers,
|
||
reqHeaders = extend({}, config.headers),
|
||
defHeaderName, lowercaseDefHeaderName, reqHeaderName;
|
||
|
||
defHeaders = extend({}, defHeaders.common, defHeaders[lowercase(config.method)]);
|
||
|
||
// execute if header value is function
|
||
execHeaders(defHeaders);
|
||
execHeaders(reqHeaders);
|
||
|
||
// using for-in instead of forEach to avoid unecessary iteration after header has been found
|
||
defaultHeadersIteration:
|
||
for (defHeaderName in defHeaders) {
|
||
lowercaseDefHeaderName = lowercase(defHeaderName);
|
||
|
||
for (reqHeaderName in reqHeaders) {
|
||
if (lowercase(reqHeaderName) === lowercaseDefHeaderName) {
|
||
continue defaultHeadersIteration;
|
||
}
|
||
}
|
||
|
||
reqHeaders[defHeaderName] = defHeaders[defHeaderName];
|
||
}
|
||
|
||
return reqHeaders;
|
||
|
||
function execHeaders(headers) {
|
||
var headerContent;
|
||
|
||
forEach(headers, function(headerFn, header) {
|
||
if (isFunction(headerFn)) {
|
||
headerContent = headerFn();
|
||
if (headerContent != null) {
|
||
headers[header] = headerContent;
|
||
} else {
|
||
delete headers[header];
|
||
}
|
||
}
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
$http.pendingRequests = [];
|
||
|
||
/**
|
||
* @ngdoc method
|
||
* @name ng.$http#get
|
||
* @methodOf ng.$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 {HttpPromise} Future object
|
||
*/
|
||
|
||
/**
|
||
* @ngdoc method
|
||
* @name ng.$http#delete
|
||
* @methodOf ng.$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 {HttpPromise} Future object
|
||
*/
|
||
|
||
/**
|
||
* @ngdoc method
|
||
* @name ng.$http#head
|
||
* @methodOf ng.$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 {HttpPromise} Future object
|
||
*/
|
||
|
||
/**
|
||
* @ngdoc method
|
||
* @name ng.$http#jsonp
|
||
* @methodOf ng.$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 {HttpPromise} Future object
|
||
*/
|
||
createShortMethods('get', 'delete', 'head', 'jsonp');
|
||
|
||
/**
|
||
* @ngdoc method
|
||
* @name ng.$http#post
|
||
* @methodOf ng.$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 {HttpPromise} Future object
|
||
*/
|
||
|
||
/**
|
||
* @ngdoc method
|
||
* @name ng.$http#put
|
||
* @methodOf ng.$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 {HttpPromise} Future object
|
||
*/
|
||
createShortMethodsWithData('post', 'put');
|
||
|
||
/**
|
||
* @ngdoc property
|
||
* @name ng.$http#defaults
|
||
* @propertyOf ng.$http
|
||
*
|
||
* @description
|
||
* Runtime equivalent of the `$httpProvider.defaults` property. Allows configuration of
|
||
* default headers, withCredentials as well as request and response transformations.
|
||
*
|
||
* See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above.
|
||
*/
|
||
$http.defaults = defaults;
|
||
|
||
|
||
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
|
||
}));
|
||
};
|
||
});
|
||
}
|
||
|
||
|
||
/**
|
||
* Makes the request.
|
||
*
|
||
* !!! ACCESSES CLOSURE VARS:
|
||
* $httpBackend, defaults, $log, $rootScope, defaultCache, $http.pendingRequests
|
||
*/
|
||
function sendReq(config, reqData, reqHeaders) {
|
||
var deferred = $q.defer(),
|
||
promise = deferred.promise,
|
||
cache,
|
||
cachedResp,
|
||
url = buildUrl(config.url, config.params);
|
||
|
||
$http.pendingRequests.push(config);
|
||
promise.then(removePendingReq, removePendingReq);
|
||
|
||
|
||
if ((config.cache || defaults.cache) && config.cache !== false && config.method == 'GET') {
|
||
cache = isObject(config.cache) ? config.cache
|
||
: isObject(defaults.cache) ? defaults.cache
|
||
: defaultCache;
|
||
}
|
||
|
||
if (cache) {
|
||
cachedResp = cache.get(url);
|
||
if (isDefined(cachedResp)) {
|
||
if (cachedResp.then) {
|
||
// cached request has already been sent, but there is no response yet
|
||
cachedResp.then(removePendingReq, removePendingReq);
|
||
return cachedResp;
|
||
} else {
|
||
// serving from cache
|
||
if (isArray(cachedResp)) {
|
||
resolvePromise(cachedResp[1], cachedResp[0], copy(cachedResp[2]));
|
||
} else {
|
||
resolvePromise(cachedResp, 200, {});
|
||
}
|
||
}
|
||
} else {
|
||
// put the promise for the non-transformed response into cache as a placeholder
|
||
cache.put(url, promise);
|
||
}
|
||
}
|
||
|
||
// if we won't have the response in cache, send the request to the backend
|
||
if (isUndefined(cachedResp)) {
|
||
$httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout,
|
||
config.withCredentials, config.responseType);
|
||
}
|
||
|
||
return promise;
|
||
|
||
|
||
/**
|
||
* Callback registered to $httpBackend():
|
||
* - caches the response if desired
|
||
* - resolves the raw $http promise
|
||
* - calls $apply
|
||
*/
|
||
function done(status, response, headersString) {
|
||
if (cache) {
|
||
if (isSuccess(status)) {
|
||
cache.put(url, [status, response, parseHeaders(headersString)]);
|
||
} else {
|
||
// remove promise from the cache
|
||
cache.remove(url);
|
||
}
|
||
}
|
||
|
||
resolvePromise(response, status, headersString);
|
||
if (!$rootScope.$$phase) $rootScope.$apply();
|
||
}
|
||
|
||
|
||
/**
|
||
* Resolves the raw $http promise.
|
||
*/
|
||
function resolvePromise(response, status, headers) {
|
||
// normalize internal statuses to 0
|
||
status = Math.max(status, 0);
|
||
|
||
(isSuccess(status) ? deferred.resolve : deferred.reject)({
|
||
data: response,
|
||
status: status,
|
||
headers: headersGetter(headers),
|
||
config: config
|
||
});
|
||
}
|
||
|
||
|
||
function removePendingReq() {
|
||
var idx = indexOf($http.pendingRequests, config);
|
||
if (idx !== -1) $http.pendingRequests.splice(idx, 1);
|
||
}
|
||
}
|
||
|
||
|
||
function buildUrl(url, params) {
|
||
if (!params) return url;
|
||
var parts = [];
|
||
forEachSorted(params, function(value, key) {
|
||
if (value === null || isUndefined(value)) return;
|
||
if (!isArray(value)) value = [value];
|
||
|
||
forEach(value, function(v) {
|
||
if (isObject(v)) {
|
||
v = toJson(v);
|
||
}
|
||
parts.push(encodeUriQuery(key) + '=' +
|
||
encodeUriQuery(v));
|
||
});
|
||
});
|
||
return url + ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&');
|
||
}
|
||
|
||
|
||
}];
|
||
}
|