Files
angular.js/src/ng/http.js
T
2017-02-02 22:37:17 +02:00

1407 lines
54 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use strict';
var APPLICATION_JSON = 'application/json';
var CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'};
var JSON_START = /^\[|^\{(?!\{)/;
var JSON_ENDS = {
'[': /]$/,
'{': /}$/
};
var JSON_PROTECTION_PREFIX = /^\)]\}',?\n/;
var $httpMinErr = minErr('$http');
function serializeValue(v) {
if (isObject(v)) {
return isDate(v) ? v.toISOString() : toJson(v);
}
return v;
}
/** @this */
function $HttpParamSerializerProvider() {
/**
* @ngdoc service
* @name $httpParamSerializer
* @description
*
* Default {@link $http `$http`} params serializer that converts objects to strings
* according to the following rules:
*
* * `{'foo': 'bar'}` results in `foo=bar`
* * `{'foo': Date.now()}` results in `foo=2015-04-01T09%3A50%3A49.262Z` (`toISOString()` and encoded representation of a Date object)
* * `{'foo': ['bar', 'baz']}` results in `foo=bar&foo=baz` (repeated key for each array element)
* * `{'foo': {'bar':'baz'}}` results in `foo=%7B%22bar%22%3A%22baz%22%7D` (stringified and encoded representation of an object)
*
* Note that serializer will sort the request parameters alphabetically.
* */
this.$get = function() {
return function ngParamSerializer(params) {
if (!params) return '';
var parts = [];
forEachSorted(params, function(value, key) {
if (value === null || isUndefined(value)) return;
if (isArray(value)) {
forEach(value, function(v) {
parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(v)));
});
} else {
parts.push(encodeUriQuery(key) + '=' + encodeUriQuery(serializeValue(value)));
}
});
return parts.join('&');
};
};
}
/** @this */
function $HttpParamSerializerJQLikeProvider() {
/**
* @ngdoc service
* @name $httpParamSerializerJQLike
*
* @description
*
* Alternative {@link $http `$http`} params serializer that follows
* jQuery's [`param()`](http://api.jquery.com/jquery.param/) method logic.
* The serializer will also sort the params alphabetically.
*
* To use it for serializing `$http` request parameters, set it as the `paramSerializer` property:
*
* ```js
* $http({
* url: myUrl,
* method: 'GET',
* params: myParams,
* paramSerializer: '$httpParamSerializerJQLike'
* });
* ```
*
* It is also possible to set it as the default `paramSerializer` in the
* {@link $httpProvider#defaults `$httpProvider`}.
*
* Additionally, you can inject the serializer and use it explicitly, for example to serialize
* form data for submission:
*
* ```js
* .controller(function($http, $httpParamSerializerJQLike) {
* //...
*
* $http({
* url: myUrl,
* method: 'POST',
* data: $httpParamSerializerJQLike(myData),
* headers: {
* 'Content-Type': 'application/x-www-form-urlencoded'
* }
* });
*
* });
* ```
*
* */
this.$get = function() {
return function jQueryLikeParamSerializer(params) {
if (!params) return '';
var parts = [];
serialize(params, '', true);
return parts.join('&');
function serialize(toSerialize, prefix, topLevel) {
if (toSerialize === null || isUndefined(toSerialize)) return;
if (isArray(toSerialize)) {
forEach(toSerialize, function(value, index) {
serialize(value, prefix + '[' + (isObject(value) ? index : '') + ']');
});
} else if (isObject(toSerialize) && !isDate(toSerialize)) {
forEachSorted(toSerialize, function(value, key) {
serialize(value, prefix +
(topLevel ? '' : '[') +
key +
(topLevel ? '' : ']'));
});
} else {
parts.push(encodeUriQuery(prefix) + '=' + encodeUriQuery(serializeValue(toSerialize)));
}
}
};
};
}
function defaultHttpResponseTransform(data, headers) {
if (isString(data)) {
// Strip json vulnerability protection prefix and trim whitespace
var tempData = data.replace(JSON_PROTECTION_PREFIX, '').trim();
if (tempData) {
var contentType = headers('Content-Type');
if ((contentType && (contentType.indexOf(APPLICATION_JSON) === 0)) || isJsonLike(tempData)) {
data = fromJson(tempData);
}
}
}
return data;
}
function isJsonLike(str) {
var jsonStart = str.match(JSON_START);
return jsonStart && JSON_ENDS[jsonStart[0]].test(str);
}
/**
* 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 = createMap(), i;
function fillInParsed(key, val) {
if (key) {
parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val;
}
}
if (isString(headers)) {
forEach(headers.split('\n'), function(line) {
i = line.indexOf(':');
fillInParsed(lowercase(trim(line.substr(0, i))), trim(line.substr(i + 1)));
});
} else if (isObject(headers)) {
forEach(headers, function(headerVal, headerKey) {
fillInParsed(lowercase(headerKey), trim(headerVal));
});
}
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 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;
return function(name) {
if (!headersObj) headersObj = parseHeaders(headers);
if (name) {
var value = headersObj[lowercase(name)];
if (value === undefined) {
value = null;
}
return value;
}
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 {number} status HTTP status code of the response.
* @param {(Function|Array.<Function>)} fns Function or an array of functions.
* @returns {*} Transformed data.
*/
function transformData(data, headers, status, fns) {
if (isFunction(fns)) {
return fns(data, headers, status);
}
forEach(fns, function(fn) {
data = fn(data, headers, status);
});
return data;
}
function isSuccess(status) {
return 200 <= status && status < 300;
}
/**
* @ngdoc provider
* @name $httpProvider
* @this
*
* @description
* Use `$httpProvider` to change the default behavior of the {@link ng.$http $http} service.
* */
function $HttpProvider() {
/**
* @ngdoc property
* @name $httpProvider#defaults
* @description
*
* Object containing default values for all {@link ng.$http $http} requests.
*
* - **`defaults.cache`** - {boolean|Object} - A boolean value or object created with
* {@link ng.$cacheFactory `$cacheFactory`} to enable or disable caching of HTTP responses
* by default. See {@link $http#caching $http Caching} for more information.
*
* - **`defaults.xsrfCookieName`** - {string} - Name of cookie containing the XSRF token.
* Defaults value is `'XSRF-TOKEN'`.
*
* - **`defaults.xsrfHeaderName`** - {string} - Name of HTTP header to populate with the
* XSRF token. Defaults value is `'X-XSRF-TOKEN'`.
*
* - **`defaults.headers`** - {Object} - Default headers for all $http requests.
* Refer to {@link ng.$http#setting-http-headers $http} for documentation on
* setting default headers.
* - **`defaults.headers.common`**
* - **`defaults.headers.post`**
* - **`defaults.headers.put`**
* - **`defaults.headers.patch`**
*
*
* - **`defaults.paramSerializer`** - `{string|function(Object<string,string>):string}` - A function
* used to the prepare string representation of request parameters (specified as an object).
* If specified as string, it is interpreted as a function registered with the {@link auto.$injector $injector}.
* Defaults to {@link ng.$httpParamSerializer $httpParamSerializer}.
*
* - **`defaults.jsonpCallbackParam`** - `{string}` - the name of the query parameter that passes the name of the
* callback in a JSONP request. The value of this parameter will be replaced with the expression generated by the
* {@link $jsonpCallbacks} service. Defaults to `'callback'`.
*
**/
var defaults = this.defaults = {
// transform incoming response data
transformResponse: [defaultHttpResponseTransform],
// transform outgoing request data
transformRequest: [function(d) {
return isObject(d) && !isFile(d) && !isBlob(d) && !isFormData(d) ? toJson(d) : d;
}],
// default headers
headers: {
common: {
'Accept': 'application/json, text/plain, */*'
},
post: shallowCopy(CONTENT_TYPE_APPLICATION_JSON),
put: shallowCopy(CONTENT_TYPE_APPLICATION_JSON),
patch: shallowCopy(CONTENT_TYPE_APPLICATION_JSON)
},
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
paramSerializer: '$httpParamSerializer',
jsonpCallbackParam: 'callback'
};
var useApplyAsync = false;
/**
* @ngdoc method
* @name $httpProvider#useApplyAsync
* @description
*
* Configure $http service to combine processing of multiple http responses received at around
* the same time via {@link ng.$rootScope.Scope#$applyAsync $rootScope.$applyAsync}. This can result in
* significant performance improvement for bigger applications that make many HTTP requests
* concurrently (common during application bootstrap).
*
* Defaults to false. If no value is specified, returns the current configured value.
*
* @param {boolean=} value If true, when requests are loaded, they will schedule a deferred
* "apply" on the next tick, giving time for subsequent requests in a roughly ~10ms window
* to load and share the same digest cycle.
*
* @returns {boolean|Object} If a value is specified, returns the $httpProvider for chaining.
* otherwise, returns the current configured value.
**/
this.useApplyAsync = function(value) {
if (isDefined(value)) {
useApplyAsync = !!value;
return this;
}
return useApplyAsync;
};
/**
* @ngdoc property
* @name $httpProvider#interceptors
* @description
*
* Array containing service factories for all synchronous or asynchronous {@link ng.$http $http}
* pre-processing of request or postprocessing of responses.
*
* These service factories are ordered by request, i.e. they are applied in the same order as the
* array, on request, but reverse order, on response.
*
* {@link ng.$http#interceptors Interceptors detailed info}
**/
var interceptorFactories = this.interceptors = [];
this.$get = ['$browser', '$httpBackend', '$$cookieReader', '$cacheFactory', '$rootScope', '$q', '$injector', '$sce',
function($browser, $httpBackend, $$cookieReader, $cacheFactory, $rootScope, $q, $injector, $sce) {
var defaultCache = $cacheFactory('$http');
/**
* Make sure that default param serializer is exposed as a function
*/
defaults.paramSerializer = isString(defaults.paramSerializer) ?
$injector.get(defaults.paramSerializer) : defaults.paramSerializer;
/**
* 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));
});
/**
* @ngdoc service
* @kind function
* @name $http
* @requires ng.$httpBackend
* @requires $cacheFactory
* @requires $rootScope
* @requires $q
* @requires $injector
*
* @description
* The `$http` service is a core AngularJS service that facilitates communication with the remote
* HTTP servers via the browser's [XMLHttpRequest](https://developer.mozilla.org/en/xmlhttprequest)
* object or via [JSONP](http://en.wikipedia.org/wiki/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 {@link $http#usage configuration object} —
* that is used to generate an HTTP request and returns a {@link ng.$q promise}.
*
* ```js
* // Simple GET request example:
* $http({
* method: 'GET',
* url: '/someUrl'
* }).then(function successCallback(response) {
* // this callback will be called asynchronously
* // when the response is available
* }, function errorCallback(response) {
* // called asynchronously if an error occurs
* // or server returns response with an error status.
* });
* ```
*
* 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.
* - **statusText** `{string}` HTTP status text of the response.
*
* A response status code between 200 and 299 is considered a success status and will result in
* the success callback being called. Any response status code outside of that range is
* considered an error status and will result in the error callback being called.
* Also, status codes less than -1 are normalized to zero. -1 usually means the request was
* aborted, e.g. using a `config.timeout`.
* Note that if the response is a redirect, XMLHttpRequest will transparently follow it, meaning
* that the outcome (success or error) will be determined by the final response status code.
*
*
* ## Shortcut methods
*
* Shortcut methods are also available. All shortcut methods require passing in the URL, and
* request data must be passed in for POST/PUT requests. An optional config can be passed as the
* last argument.
*
* ```js
* $http.get('/someUrl', config).then(successCallback, errorCallback);
* $http.post('/someUrl', data, config).then(successCallback, errorCallback);
* ```
*
* Complete list of shortcut methods:
*
* - {@link ng.$http#get $http.get}
* - {@link ng.$http#head $http.head}
* - {@link ng.$http#post $http.post}
* - {@link ng.$http#put $http.put}
* - {@link ng.$http#delete $http.delete}
* - {@link ng.$http#jsonp $http.jsonp}
* - {@link ng.$http#patch $http.patch}
*
*
* ## Writing Unit Tests that use $http
* When unit testing (using {@link ngMock ngMock}), it is necessary to call
* {@link ngMock.$httpBackend#flush $httpBackend.flush()} to flush each pending
* request using trained responses.
*
* ```
* $httpBackend.expectGET(...);
* $http.get(...);
* $httpBackend.flush();
* ```
*
* ## 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):
* - <code>Accept: application/json, text/plain, \*&#65279;/&#65279;\*</code>
* - `$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.Authorization = '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.
*
* To explicitly remove a header automatically added via $httpProvider.defaults.headers on a per request basis,
* Use the `headers` property, setting the desired header to `undefined`. For example:
*
* ```js
* var req = {
* method: 'POST',
* url: 'http://example.com',
* headers: {
* 'Content-Type': undefined
* },
* data: { test: 'test' }
* }
*
* $http(req).then(function(){...}, function(){...});
* ```
*
* ## Transforming Requests and Responses
*
* Both requests and responses can be transformed using transformation functions: `transformRequest`
* and `transformResponse`. These properties can be a single function that returns
* the transformed value (`function(data, headersGetter, status)`) or an array of such transformation functions,
* which allows you to `push` or `unshift` a new transformation function into the transformation chain.
*
* <div class="alert alert-warning">
* **Note:** AngularJS does not make a copy of the `data` parameter before it is passed into the `transformRequest` pipeline.
* That means changes to the properties of `data` are not local to the transform function (since Javascript passes objects by reference).
* For example, when calling `$http.get(url, $scope.myObject)`, modifications to the object's properties in a transformRequest
* function will be reflected on the scope and in any templates where the object is data-bound.
* To prevent this, transform functions should have no side-effects.
* If you need to modify properties, it is recommended to make a copy of the data, or create new object to return.
* </div>
*
* ### Default Transformations
*
* The `$httpProvider` provider and `$http` service expose `defaults.transformRequest` and
* `defaults.transformResponse` properties. If a request does not provide its own transformations
* then these will be applied.
*
* You can augment or replace the default transformations by modifying these properties by adding to or
* replacing the array.
*
* AngularJS provides the following default transformations:
*
* Request transformations (`$httpProvider.defaults.transformRequest` and `$http.defaults.transformRequest`):
*
* - If the `data` property of the request configuration object contains an object, serialize it
* into JSON format.
*
* Response transformations (`$httpProvider.defaults.transformResponse` and `$http.defaults.transformResponse`):
*
* - If XSRF prefix is detected, strip it (see Security Considerations section below).
* - If JSON response is detected, deserialize it using a JSON parser.
*
*
* ### Overriding the Default Transformations Per Request
*
* If you wish to override the request/response transformations only for a single request then provide
* `transformRequest` and/or `transformResponse` properties on the configuration object passed
* into `$http`.
*
* Note that if you provide these properties on the config object the default transformations will be
* overwritten. If you wish to augment the default transformations then you must include them in your
* local transformation array.
*
* The following code demonstrates adding a new response transformation to be run after the default response
* transformations have been run.
*
* ```js
* function appendTransform(defaults, transform) {
*
* // We can't guarantee that the default transformation is an array
* defaults = angular.isArray(defaults) ? defaults : [defaults];
*
* // Append the new transformation to the defaults
* return defaults.concat(transform);
* }
*
* $http({
* url: '...',
* method: 'GET',
* transformResponse: appendTransform($http.defaults.transformResponse, function(value) {
* return doTransform(value);
* })
* });
* ```
*
*
* ## Caching
*
* {@link ng.$http `$http`} responses are not cached by default. To enable caching, you must
* set the config.cache value or the default cache value to TRUE or to a cache object (created
* with {@link ng.$cacheFactory `$cacheFactory`}). If defined, the value of config.cache takes
* precedence over the default cache value.
*
* In order to:
* * cache all responses - set the default cache value to TRUE or to a cache object
* * cache a specific response - set config.cache value to TRUE or to a cache object
*
* If caching is enabled, but neither the default cache nor config.cache are set to a cache object,
* then the default `$cacheFactory("$http")` object is used.
*
* The default cache value can be set by updating the
* {@link ng.$http#defaults `$http.defaults.cache`} property or the
* {@link $httpProvider#defaults `$httpProvider.defaults.cache`} property.
*
* When caching is enabled, {@link ng.$http `$http`} stores the response from the server using
* the relevant cache object. The next time the same request is made, the response is returned
* from the cache without sending a request to the server.
*
* Take note that:
*
* * Only GET and JSONP requests are cached.
* * The cache key is the request URL including search parameters; headers are not considered.
* * Cached responses are returned asynchronously, in the same way as responses from the server.
* * If multiple identical requests are made using the same cache, which is not yet populated,
* one request will be made to the server and remaining requests will return the same response.
* * A cache-control header on the response does not affect if or how responses are 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 a http {@link $http#usage config} object. The function is free to
* modify the `config` object or create a new one. The function needs to return the `config`
* object directly, or a promise containing the `config` or a new `config` object.
* * `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` object or create a new one. The function needs to return the `response`
* object directly, or as a promise containing the `response` or a new `response` object.
* * `responseError`: interceptor gets called when a previous interceptor threw an error or
* resolved with a rejection.
*
*
* ```js
* // 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;
* },
*
* // 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;
* },
*
* // 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
* }
* };
* });
* ```
*
* ## Security Considerations
*
* When designing web applications, consider security threats from:
*
* - [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx)
* - [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery)
*
* Both server and the client must cooperate in order to eliminate these threats. AngularJS comes
* pre-configured with strategies that address these issues, but for this to work backend server
* cooperation is required.
*
* ### JSON Vulnerability Protection
*
* A [JSON vulnerability](http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx)
* allows third party website to turn your JSON resource URL into
* [JSONP](http://en.wikipedia.org/wiki/JSONP) request under some conditions. To
* counter this your server can prefix all JSON requests with following string `")]}',\n"`.
* AngularJS will automatically strip the prefix before processing it as JSON.
*
* For example if your server needs to return:
* ```js
* ['one','two']
* ```
*
* which is vulnerable to attack, your server can return:
* ```js
* )]}',
* ['one','two']
* ```
*
* AngularJS will strip the prefix, before processing the JSON.
*
*
* ### Cross Site Request Forgery (XSRF) Protection
*
* [XSRF](http://en.wikipedia.org/wiki/Cross-site_request_forgery) is an attack technique by
* which the attacker can trick an authenticated user into unknowingly executing actions on your
* website. AngularJS 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 [salt](https://en.wikipedia.org/wiki/Salt_(cryptography&#41;)
* 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.
*
* In order to prevent collisions in environments where multiple AngularJS apps share the
* same domain or subdomain, we recommend that each application uses unique cookie name.
*
* @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|TrustedObject}` Absolute or relative URL of the resource that is being requested;
* or an object created by a call to `$sce.trustAsResourceUrl(url)`.
* - **params** `{Object.<string|Object>}` Map of strings or objects which will be serialized
* with the `paramSerializer` and appended as GET parameters.
* - **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. Functions accept a config object as an argument.
* - **eventHandlers** - `{Object}` - Event listeners to be bound to the XMLHttpRequest object.
* To bind events to the XMLHttpRequest upload object, use `uploadEventHandlers`.
* The handler will be called in the context of a `$apply` block.
* - **uploadEventHandlers** - `{Object}` - Event listeners to be bound to the XMLHttpRequest upload
* object. To bind events to the XMLHttpRequest object, use `eventHandlers`.
* The handler will be called in the context of a `$apply` block.
* - **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.
* See {@link ng.$http#overriding-the-default-transformations-per-request
* Overriding the Default Transformations}
* - **transformResponse**
* `{function(data, headersGetter, status)|Array.<function(data, headersGetter, status)>}`
* transform function or an array of such functions. The transform function takes the http
* response body, headers and status and returns its transformed (typically deserialized) version.
* See {@link ng.$http#overriding-the-default-transformations-per-request
* Overriding the Default Transformations}
* - **paramSerializer** - `{string|function(Object<string,string>):string}` - A function used to
* prepare the string representation of request parameters (specified as an object).
* If specified as string, it is interpreted as function registered with the
* {@link $injector $injector}, which means you can create your own serializer
* by registering it as a {@link auto.$provide#service service}.
* The default serializer is the {@link $httpParamSerializer $httpParamSerializer};
* alternatively, you can use the {@link $httpParamSerializerJQLike $httpParamSerializerJQLike}
* - **cache** `{boolean|Object}` A boolean value or object created with
* {@link ng.$cacheFactory `$cacheFactory`} to enable or disable caching of the HTTP response.
* See {@link $http#caching $http Caching} for more information.
* - **timeout** `{number|Promise}` timeout in milliseconds, or {@link ng.$q promise}
* that should abort the request when resolved.
* - **withCredentials** - `{boolean}` - whether to set the `withCredentials` flag on the
* XHR object. See [requests with credentials](https://developer.mozilla.org/docs/Web/HTTP/Access_control_CORS#Requests_with_credentials)
* for more information.
* - **responseType** - `{string}` - see
* [XMLHttpRequest.responseType](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#xmlhttprequest-responsetype).
*
* @returns {HttpPromise} Returns a {@link ng.$q `Promise}` that will be resolved to a response object
* when the request succeeds or fails.
*
*
* @property {Array.<Object>} pendingRequests Array of config objects for currently pending
* requests. This is primarily meant to be used for debugging purposes.
*
*
* @example
<example module="httpExample" name="http-service">
<file name="index.html">
<div ng-controller="FetchController">
<select ng-model="method" aria-label="Request method">
<option>GET</option>
<option>JSONP</option>
</select>
<input type="text" ng-model="url" size="80" aria-label="URL" />
<button id="fetchbtn" ng-click="fetch()">fetch</button><br>
<button id="samplegetbtn" ng-click="updateModel('GET', 'http-hello.html')">Sample GET</button>
<button id="samplejsonpbtn"
ng-click="updateModel('JSONP',
'https://angularjs.org/greet.php?name=Super%20Hero')">
Sample JSONP
</button>
<button id="invalidjsonpbtn"
ng-click="updateModel('JSONP', 'https://angularjs.org/doesntexist')">
Invalid JSONP
</button>
<pre>http status code: {{status}}</pre>
<pre>http response data: {{data}}</pre>
</div>
</file>
<file name="script.js">
angular.module('httpExample', [])
.config(['$sceDelegateProvider', function($sceDelegateProvider) {
// We must whitelist the JSONP endpoint that we are using to show that we trust it
$sceDelegateProvider.resourceUrlWhitelist([
'self',
'https://angularjs.org/**'
]);
}])
.controller('FetchController', ['$scope', '$http', '$templateCache',
function($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}).
then(function(response) {
$scope.status = response.status;
$scope.data = response.data;
}, function(response) {
$scope.data = response.data || 'Request failed';
$scope.status = response.status;
});
};
$scope.updateModel = function(method, url) {
$scope.method = method;
$scope.url = url;
};
}]);
</file>
<file name="http-hello.html">
Hello, $http!
</file>
<file name="protractor.js" type="protractor">
var status = element(by.binding('status'));
var data = element(by.binding('data'));
var fetchBtn = element(by.id('fetchbtn'));
var sampleGetBtn = element(by.id('samplegetbtn'));
var invalidJsonpBtn = element(by.id('invalidjsonpbtn'));
it('should make an xhr GET request', function() {
sampleGetBtn.click();
fetchBtn.click();
expect(status.getText()).toMatch('200');
expect(data.getText()).toMatch(/Hello, \$http!/);
});
// Commented out due to flakes. See https://github.com/angular/angular.js/issues/9185
// it('should make a JSONP request to angularjs.org', function() {
// var sampleJsonpBtn = element(by.id('samplejsonpbtn'));
// sampleJsonpBtn.click();
// fetchBtn.click();
// expect(status.getText()).toMatch('200');
// expect(data.getText()).toMatch(/Super Hero!/);
// });
it('should make JSONP request to invalid URL and invoke the error handler',
function() {
invalidJsonpBtn.click();
fetchBtn.click();
expect(status.getText()).toMatch('0');
expect(data.getText()).toMatch('Request failed');
});
</file>
</example>
*/
function $http(requestConfig) {
if (!isObject(requestConfig)) {
throw minErr('$http')('badreq', 'Http request configuration must be an object. Received: {0}', requestConfig);
}
if (!isString($sce.valueOf(requestConfig.url))) {
throw minErr('$http')('badreq', 'Http request configuration url must be a string or a $sce trusted object. Received: {0}', requestConfig.url);
}
var config = extend({
method: 'get',
transformRequest: defaults.transformRequest,
transformResponse: defaults.transformResponse,
paramSerializer: defaults.paramSerializer,
jsonpCallbackParam: defaults.jsonpCallbackParam
}, requestConfig);
config.headers = mergeHeaders(requestConfig);
config.method = uppercase(config.method);
config.paramSerializer = isString(config.paramSerializer) ?
$injector.get(config.paramSerializer) : config.paramSerializer;
$browser.$$incOutstandingRequestCount();
var requestInterceptors = [];
var responseInterceptors = [];
var promise = $q.resolve(config);
// apply interceptors
forEach(reversedInterceptors, function(interceptor) {
if (interceptor.request || interceptor.requestError) {
requestInterceptors.unshift(interceptor.request, interceptor.requestError);
}
if (interceptor.response || interceptor.responseError) {
responseInterceptors.push(interceptor.response, interceptor.responseError);
}
});
promise = chainInterceptors(promise, requestInterceptors);
promise = promise.then(serverRequest);
promise = chainInterceptors(promise, responseInterceptors);
promise = promise.finally(completeOutstandingRequest);
return promise;
function chainInterceptors(promise, interceptors) {
for (var i = 0, ii = interceptors.length; i < ii;) {
var thenFn = interceptors[i++];
var rejectFn = interceptors[i++];
promise = promise.then(thenFn, rejectFn);
}
interceptors.length = 0;
return promise;
}
function completeOutstandingRequest() {
$browser.$$completeOutstandingRequest(noop);
}
function executeHeaderFns(headers, config) {
var headerContent, processedHeaders = {};
forEach(headers, function(headerFn, header) {
if (isFunction(headerFn)) {
headerContent = headerFn(config);
if (headerContent != null) {
processedHeaders[header] = headerContent;
}
} else {
processedHeaders[header] = headerFn;
}
});
return processedHeaders;
}
function mergeHeaders(config) {
var defHeaders = defaults.headers,
reqHeaders = extend({}, config.headers),
defHeaderName, lowercaseDefHeaderName, reqHeaderName;
defHeaders = extend({}, defHeaders.common, defHeaders[lowercase(config.method)]);
// using for-in instead of forEach to avoid unnecessary 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];
}
// execute if header value is a function for merged headers
return executeHeaderFns(reqHeaders, shallowCopy(config));
}
function serverRequest(config) {
var headers = config.headers;
var reqData = transformData(config.data, headersGetter(headers), undefined, config.transformRequest);
// strip content-type if data is undefined
if (isUndefined(reqData)) {
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).then(transformResponse, transformResponse);
}
function transformResponse(response) {
// make a copy since the response must be cacheable
var resp = extend({}, response);
resp.data = transformData(response.data, response.headers, response.status,
config.transformResponse);
return (isSuccess(response.status))
? resp
: $q.reject(resp);
}
}
$http.pendingRequests = [];
/**
* @ngdoc method
* @name $http#get
*
* @description
* Shortcut method to perform `GET` request.
*
* @param {string|TrustedObject} url Absolute or relative URL of the resource that is being requested;
* or an object created by a call to `$sce.trustAsResourceUrl(url)`.
* @param {Object=} config Optional configuration object
* @returns {HttpPromise} Future object
*/
/**
* @ngdoc method
* @name $http#delete
*
* @description
* Shortcut method to perform `DELETE` request.
*
* @param {string|TrustedObject} url Absolute or relative URL of the resource that is being requested;
* or an object created by a call to `$sce.trustAsResourceUrl(url)`.
* @param {Object=} config Optional configuration object
* @returns {HttpPromise} Future object
*/
/**
* @ngdoc method
* @name $http#head
*
* @description
* Shortcut method to perform `HEAD` request.
*
* @param {string|TrustedObject} url Absolute or relative URL of the resource that is being requested;
* or an object created by a call to `$sce.trustAsResourceUrl(url)`.
* @param {Object=} config Optional configuration object
* @returns {HttpPromise} Future object
*/
/**
* @ngdoc method
* @name $http#jsonp
*
* @description
* Shortcut method to perform `JSONP` request.
*
* Note that, since JSONP requests are sensitive because the response is given full access to the browser,
* the url must be declared, via {@link $sce} as a trusted resource URL.
* You can trust a URL by adding it to the whitelist via
* {@link $sceDelegateProvider#resourceUrlWhitelist `$sceDelegateProvider.resourceUrlWhitelist`} or
* by explicitly trusting the URL via {@link $sce#trustAsResourceUrl `$sce.trustAsResourceUrl(url)`}.
*
* JSONP requests must specify a callback to be used in the response from the server. This callback
* is passed as a query parameter in the request. You must specify the name of this parameter by
* setting the `jsonpCallbackParam` property on the request config object.
*
* ```
* $http.jsonp('some/trusted/url', {jsonpCallbackParam: 'callback'})
* ```
*
* You can also specify a default callback parameter name in `$http.defaults.jsonpCallbackParam`.
* Initially this is set to `'callback'`.
*
* <div class="alert alert-danger">
* You can no longer use the `JSON_CALLBACK` string as a placeholder for specifying where the callback
* parameter value should go.
* </div>
*
* If you would like to customise where and how the callbacks are stored then try overriding
* or decorating the {@link $jsonpCallbacks} service.
*
* @param {string|TrustedObject} url Absolute or relative URL of the resource that is being requested;
* or an object created by a call to `$sce.trustAsResourceUrl(url)`.
* @param {Object=} config Optional configuration object
* @returns {HttpPromise} Future object
*/
createShortMethods('get', 'delete', 'head', 'jsonp');
/**
* @ngdoc method
* @name $http#post
*
* @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 $http#put
*
* @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
*/
/**
* @ngdoc method
* @name $http#patch
*
* @description
* Shortcut method to perform `PATCH` 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', 'patch');
/**
* @ngdoc property
* @name $http#defaults
*
* @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) {
var deferred = $q.defer(),
promise = deferred.promise,
cache,
cachedResp,
reqHeaders = config.headers,
isJsonp = lowercase(config.method) === 'jsonp',
url = config.url;
if (isJsonp) {
// JSONP is a pretty sensitive operation where we're allowing a script to have full access to
// our DOM and JS space. So we require that the URL satisfies SCE.RESOURCE_URL.
url = $sce.getTrustedResourceUrl(url);
} else if (!isString(url)) {
// If it is not a string then the URL must be a $sce trusted object
url = $sce.valueOf(url);
}
url = buildUrl(url, config.paramSerializer(config.params));
if (isJsonp) {
// Check the url and add the JSONP callback placeholder
url = sanitizeJsonpCallbackParam(url, config.jsonpCallbackParam);
}
$http.pendingRequests.push(config);
promise.then(removePendingReq, removePendingReq);
if ((config.cache || defaults.cache) && config.cache !== false &&
(config.method === 'GET' || config.method === 'JSONP')) {
cache = isObject(config.cache) ? config.cache
: isObject(/** @type {?} */ (defaults).cache)
? /** @type {?} */ (defaults).cache
: defaultCache;
}
if (cache) {
cachedResp = cache.get(url);
if (isDefined(cachedResp)) {
if (isPromiseLike(cachedResp)) {
// cached request has already been sent, but there is no response yet
cachedResp.then(resolvePromiseWithResult, resolvePromiseWithResult);
} else {
// serving from cache
if (isArray(cachedResp)) {
resolvePromise(cachedResp[1], cachedResp[0], shallowCopy(cachedResp[2]), cachedResp[3]);
} else {
resolvePromise(cachedResp, 200, {}, 'OK');
}
}
} 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, set the xsrf headers and
// send the request to the backend
if (isUndefined(cachedResp)) {
var xsrfValue = urlIsSameOrigin(config.url)
? $$cookieReader()[config.xsrfCookieName || defaults.xsrfCookieName]
: undefined;
if (xsrfValue) {
reqHeaders[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue;
}
$httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout,
config.withCredentials, config.responseType,
createApplyHandlers(config.eventHandlers),
createApplyHandlers(config.uploadEventHandlers));
}
return promise;
function createApplyHandlers(eventHandlers) {
if (eventHandlers) {
var applyHandlers = {};
forEach(eventHandlers, function(eventHandler, key) {
applyHandlers[key] = function(event) {
if (useApplyAsync) {
$rootScope.$applyAsync(callEventHandler);
} else if ($rootScope.$$phase) {
callEventHandler();
} else {
$rootScope.$apply(callEventHandler);
}
function callEventHandler() {
eventHandler(event);
}
};
});
return applyHandlers;
}
}
/**
* Callback registered to $httpBackend():
* - caches the response if desired
* - resolves the raw $http promise
* - calls $apply
*/
function done(status, response, headersString, statusText) {
if (cache) {
if (isSuccess(status)) {
cache.put(url, [status, response, parseHeaders(headersString), statusText]);
} else {
// remove promise from the cache
cache.remove(url);
}
}
function resolveHttpPromise() {
resolvePromise(response, status, headersString, statusText);
}
if (useApplyAsync) {
$rootScope.$applyAsync(resolveHttpPromise);
} else {
resolveHttpPromise();
if (!$rootScope.$$phase) $rootScope.$apply();
}
}
/**
* Resolves the raw $http promise.
*/
function resolvePromise(response, status, headers, statusText) {
//status: HTTP response status code, 0, -1 (aborted by timeout / promise)
status = status >= -1 ? status : 0;
(isSuccess(status) ? deferred.resolve : deferred.reject)({
data: response,
status: status,
headers: headersGetter(headers),
config: config,
statusText: statusText
});
}
function resolvePromiseWithResult(result) {
resolvePromise(result.data, result.status, shallowCopy(result.headers()), result.statusText);
}
function removePendingReq() {
var idx = $http.pendingRequests.indexOf(config);
if (idx !== -1) $http.pendingRequests.splice(idx, 1);
}
}
function buildUrl(url, serializedParams) {
if (serializedParams.length > 0) {
url += ((url.indexOf('?') === -1) ? '?' : '&') + serializedParams;
}
return url;
}
function sanitizeJsonpCallbackParam(url, key) {
if (/[&?][^=]+=JSON_CALLBACK/.test(url)) {
// Throw if the url already contains a reference to JSON_CALLBACK
throw $httpMinErr('badjsonp', 'Illegal use of JSON_CALLBACK in url, "{0}"', url);
}
var callbackParamRegex = new RegExp('[&?]' + key + '=');
if (callbackParamRegex.test(url)) {
// Throw if the callback param was already provided
throw $httpMinErr('badjsonp', 'Illegal use of callback param, "{0}", in url, "{1}"', key, url);
}
// Add in the JSON_CALLBACK callback param value
url += ((url.indexOf('?') === -1) ? '?' : '&') + key + '=JSON_CALLBACK';
return url;
}
}];
}