'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.)} 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/; var $config = 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, true); } 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, */*', 'X-Requested-With': 'XMLHttpRequest' }, post: {'Content-Type': 'application/json;charset=utf-8'}, put: {'Content-Type': 'application/json;charset=utf-8'} } }; var providerResponseInterceptors = this.responseInterceptors = []; this.$get = ['$httpBackend', '$browser', '$cacheFactory', '$rootScope', '$q', '$injector', function($httpBackend, $browser, $cacheFactory, $rootScope, $q, $injector) { var defaultCache = $cacheFactory('$http'), responseInterceptors = []; forEach(providerResponseInterceptors, function(interceptor) { responseInterceptors.push( isString(interceptor) ? $injector.get(interceptor) : $injector.invoke(interceptor) ); }); /** * @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`. * *
     *   $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.
     *     });
     * 
* * 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. * * # 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: * *
     *   $http.get('/someUrl').success(successCallback);
     *   $http.post('/someUrl', data).success(successCallback);
     * 
* * 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} * * * # 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, * / *` * - `X-Requested-With: XMLHttpRequest` * - `$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'`. * * Additionally, the defaults can be set at runtime via the `$http.defaults` object in the same * fashion. * * * # 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. * * 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 configuration property `cache` to `true`. When the cache is * enabled, `$http` stores the response from the server in local cache. Next time 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. * * * # Response 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 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. * *
     *   // 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
     *     }
     *   });
     * 
* * * # 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: *
     * ['one','two']
     * 
* * which is vulnerable to attack, your server can return: *
     * )]}',
     * ['one','two']
     * 
* * Angular will strip the prefix, before processing the JSON. * * * ## Cross Site Request Forgery (XSRF) Protection * * {@link http://en.wikipedia.org/wiki/Cross-site_request_forgery XSRF} is a technique by which * an unauthorized site can gain your user's private data. Angular provides a mechanism * to counter XSRF. When performing XHR requests, the $http service reads a token from a cookie * called `XSRF-TOKEN` and sets it as the HTTP header `X-XSRF-TOKEN`. Since only JavaScript that * runs on your domain could read the cookie, your server can be assured that the XHR came from * JavaScript running on your domain. * * To take advantage of this, your server needs to set a token in a JavaScript readable session * cookie called `XSRF-TOKEN` on 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. * * * @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.}` – 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 representing HTTP headers to send to the server. * - **transformRequest** – `{function(data, headersGetter)|Array.}` – * 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.}` – * 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}` – timeout in milliseconds. * - **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. * * @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.} pendingRequests Array of config objects for currently pending * requests. This is primarily meant to be used for debugging purposes. * * * @example

http status code: {{status}}
http response data: {{data}}
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; }; } Hello, $http! 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'); });
*/ function $http(config) { config.method = uppercase(config.method); var reqTransformFn = config.transformRequest || $config.transformRequest, respTransformFn = config.transformResponse || $config.transformResponse, reqHeaders = extend({}, config.headers), defHeaders = extend( {'X-XSRF-TOKEN': $browser.cookies()['XSRF-TOKEN']}, $config.headers.common, $config.headers[lowercase(config.method)] ), reqData, defHeaderName, lowercaseDefHeaderName, headerName, promise; // using for-in instead of forEach to avoid unecessary iteration after header has been found defaultHeadersIteration: for(defHeaderName in defHeaders) { lowercaseDefHeaderName = lowercase(defHeaderName); for(headerName in config.headers) { if (lowercase(headerName) === lowercaseDefHeaderName) { continue defaultHeadersIteration; } } reqHeaders[defHeaderName] = defHeaders[defHeaderName]; } // strip content-type if data is undefined if (isUndefined(config.data)) { for(var header in reqHeaders) { if (lowercase(header) === 'content-type') { delete reqHeaders[header]; break; } } } reqData = transformData(config.data, headersGetter(reqHeaders), reqTransformFn); // send request promise = sendReq(config, reqData, reqHeaders); // transform future response promise = promise.then(transformResponse, transformResponse); // apply interceptors forEach(responseInterceptors, function(interceptor) { promise = interceptor(promise); }); 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, respTransformFn) }); return (isSuccess(response.status)) ? resp : $q.reject(resp); } } $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 as well as request and response transformations. * * See "Setting HTTP Headers" and "Transforming Requests and Responses" sections above. */ $http.defaults = $config; 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, $config, $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 && config.method == 'GET') { cache = isObject(config.cache) ? config.cache : defaultCache; } if (cache) { cachedResp = cache.get(url); if (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 (!cachedResp) { $httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout, config.withCredentials); } 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); $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 || value == undefined) return; if (isObject(value)) { value = toJson(value); } parts.push(encodeURIComponent(key) + '=' + encodeURIComponent(value)); }); return url + ((url.indexOf('?') == -1) ? '?' : '&') + parts.join('&'); } }]; }