diff options
| author | Igor Minar | 2012-01-10 19:16:33 -0800 |
|---|---|---|
| committer | Igor Minar | 2012-01-13 14:28:20 -0800 |
| commit | 939c8e8fac0487da12e56291d69c5199a8ed9a30 (patch) | |
| tree | f249b1cada0b289db0ad1dccf150f7b244c9c527 /src | |
| parent | d2ba4c5170f604f2610aaef1d205137e2848a791 (diff) | |
| download | angular.js-939c8e8fac0487da12e56291d69c5199a8ed9a30.tar.bz2 | |
docs($http, $httpBackend): docs docs docs
Diffstat (limited to 'src')
| -rw-r--r-- | src/angular-mocks.js | 576 | ||||
| -rw-r--r-- | src/service/http.js | 322 | ||||
| -rw-r--r-- | src/service/httpBackend.js | 5 |
3 files changed, 868 insertions, 35 deletions
diff --git a/src/angular-mocks.js b/src/angular-mocks.js index 0d284884..dfa43d7d 100644 --- a/src/angular-mocks.js +++ b/src/angular-mocks.js @@ -580,11 +580,175 @@ angular.mock.dump = function(object) { /** * @ngdoc object * @name angular.module.ngMock.$httpBackend - * @describe - * Fake version of `$httpBackend` service used by the `$http` service during unit testing. + * @description + * Fake HTTP backend implementation suitable for unit testing application that use the + * {@link angular.module.ng.$http $http service}. + * + * *Note*: For fake http backend implementation suitable for end-to-end testing or backend-less + * development please see {@link angular.module.ngMockE2E.$httpBackend e2e $httpBackend mock}. + * + * During unit testing, we want our unit tests to run quickly and have no external dependencies so + * we don’t want to send {@link https://developer.mozilla.org/en/xmlhttprequest XHR} or + * {@link http://en.wikipedia.org/wiki/JSONP JSONP} requests to a real server. All we really need is + * to verify whether a certain request has been sent or not, or alternatively just let the + * application make requests, respond with pre-trained responses and assert that the end result is + * what we expect it to be. + * + * This mock implementation can be used to respond with static or dynamic responses via the + * `expect` and `when` apis and their shortcuts (`expectGET`, `whenPOST`, etc). + * + * When an Angular application needs some data from a server, it calls the $http service, which + * sends the request to a real server using $httpBackend service. With dependency injection, it is + * easy to inject $httpBackend mock (which has the same API as $httpBackend) and use it to verify + * the requests and respond with some testing data without sending a request to real server. + * + * There are two ways to specify what test data should be returned as http responses by the mock + * backend when the code under test makes http requests: + * + * - `$httpBackend.expect` - specifies a request expectation + * - `$httpBackend.when` - specifies a backend definition + * + * + * # Request Expectations vs Backend Definitions + * + * Request expectations provide a way to make assertions about requests made by the application and + * to define responses for those requests. The test will fail if the expected requests are not made + * or they are made in the wrong order. + * + * Backend definitions allow you to define a fake backend for your application which doesn't assert + * if a particular request was made or not, it just returns a trained response if a request is made. + * The test will pass whether or not the request gets made during testing. + * * - * This implementation can be used to respond with static or dynamic responses via the `expect` and - * `when` apis and their shortcuts (`expectGET`, `whenPOST`, etc). + * <table class="table"> + * <tr><th width="220px"></th><th>Request expectations</th><th>Backend definitions</th></tr> + * <tr> + * <th>Syntax</th> + * <td>.expect(...).respond(...)</td> + * <td>.when(...).respond(...)</td> + * </tr> + * <tr> + * <th>Typical usage</th> + * <td>strict unit tests</td> + * <td>loose (black-box) unit testing</td> + * </tr> + * <tr> + * <th>Fulfills multiple requests</th> + * <td>NO</td> + * <td>YES</td> + * </tr> + * <tr> + * <th>Order of requests matters</th> + * <td>YES</td> + * <td>NO</td> + * </tr> + * <tr> + * <th>Request required</th> + * <td>YES</td> + * <td>NO</td> + * </tr> + * <tr> + * <th>Response required</th> + * <td>optional (see below)</td> + * <td>YES</td> + * </tr> + * </table> + * + * In cases where both backend definitions and request expectations are specified during unit + * testing, the request expectations are evaluated first. + * + * If a request expectation has no response specified, the algorithm will search your backend + * definitions for an appropriate response. + * + * If a request didn't match any expectation or if the expectation doesn't have the response + * defined, the backend definitions are evaluated in sequential order to see if any of them match + * the request. The response from the first matched definition is returned. + * + * + * # Flushing HTTP requests + * + * The $httpBackend used in production, always responds to requests with responses asynchronously. + * If we preserved this behavior in unit testing, we'd have to create async unit tests, which are + * hard to write, follow and maintain. At the same time the testing mock, can't respond + * synchronously because that would change the execution of the code under test. For this reason the + * mock $httpBackend has a `flush()` method, which allows the test to explicitly flush pending + * requests and thus preserving the async api of the backend, while allowing the test to execute + * synchronously. + * + * + * # Unit testing with mock $httpBackend + * + * <pre> + // controller + function MyController($http) { + var scope = this; + + $http.get('/auth.py').success(function(data) { + scope.user = data; + }); + + this.saveMessage = function(message) { + scope.status = 'Saving...'; + $http.post('/add-msg.py', message).success(function(response) { + scope.status = ''; + }).error(function() { + scope.status = 'ERROR!'; + }); + }; + } + + // testing controller + var $http; + + beforeEach(inject(function($injector) { + $httpBackend = $injector.get('$httpBackend'); + + // backend definition common for all tests + $httpBackend.when('GET', '/auth.py').respond({userId: 'userX'}, {'A-Token': 'xxx'}); + })); + + + afterEach(function() { + $httpBackend.verifyNoOutstandingExpectation(); + $httpBackend.verifyNoOutstandingRequest(); + }); + + + it('should fetch authentication token', function() { + $httpBackend.expectGET('/auth.py'); + var controller = scope.$new(MyController); + $httpBackend.flush(); + }); + + + it('should send msg to server', function() { + // now you don’t care about the authentication, but + // the controller will still send the request and + // $httpBackend will respond without you having to + // specify the expectation and response for this request + $httpBackend.expectPOST('/add-msg.py', 'message content').respond(201, ''); + + var controller = scope.$new(MyController); + $httpBackend.flush(); + controller.saveMessage('message content'); + expect(controller.status).toBe('Saving...'); + $httpBackend.flush(); + expect(controller.status).toBe(''); + }); + + + it('should send auth header', function() { + $httpBackend.expectPOST('/add-msg.py', undefined, function(headers) { + // check if the header was send, if it wasn't the expectation won't + // match the request and the test will fail + return headers['Authorization'] == 'xxx'; + }).respond(201, ''); + + var controller = scope.$new(MyController); + controller.saveMessage('whatever'); + $httpBackend.flush(); + }); + </pre> */ angular.mock.$HttpBackendProvider = function() { this.$get = [createHttpBackendMock]; @@ -677,6 +841,26 @@ function createHttpBackendMock($delegate, $defer) { (expectation ? 'Expected ' + expectation : 'No more request expected')); } + /** + * @ngdoc method + * @name angular.module.ngMock.$httpBackend#when + * @methodOf angular.module.ngMock.$httpBackend + * @description + * Creates a new backend definition. + * + * @param {string} method HTTP method. + * @param {string|RegExp} url HTTP url. + * @param {(string|RegExp)=} data HTTP request body. + * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header + * object and returns true if the headers match the current definition. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + * + * - respond – `{function([status,] data[, headers])|function(function(method, url, data, headers)}` + * – The respond method takes a set of static data to be returned or a function that can return + * an array containing response status (number), response data (string) and response headers + * (Object). + */ $httpBackend.when = function(method, url, data, headers) { var definition = new MockHttpExpectation(method, url, data, headers), chain = { @@ -695,9 +879,107 @@ function createHttpBackendMock($delegate, $defer) { return chain; }; + /** + * @ngdoc method + * @name angular.module.ngMock.$httpBackend#whenGET + * @methodOf angular.module.ngMock.$httpBackend + * @description + * Creates a new backend definition for GET requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + */ + + /** + * @ngdoc method + * @name angular.module.ngMock.$httpBackend#whenHEAD + * @methodOf angular.module.ngMock.$httpBackend + * @description + * Creates a new backend definition for HEAD requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + */ + + /** + * @ngdoc method + * @name angular.module.ngMock.$httpBackend#whenDELETE + * @methodOf angular.module.ngMock.$httpBackend + * @description + * Creates a new backend definition for DELETE requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + */ + + /** + * @ngdoc method + * @name angular.module.ngMock.$httpBackend#whenPOST + * @methodOf angular.module.ngMock.$httpBackend + * @description + * Creates a new backend definition for POST requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(string|RegExp)=} data HTTP request body. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + */ + + /** + * @ngdoc method + * @name angular.module.ngMock.$httpBackend#whenPUT + * @methodOf angular.module.ngMock.$httpBackend + * @description + * Creates a new backend definition for PUT requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(string|RegExp)=} data HTTP request body. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + */ + + /** + * @ngdoc method + * @name angular.module.ngMock.$httpBackend#whenJSONP + * @methodOf angular.module.ngMock.$httpBackend + * @description + * Creates a new backend definition for JSONP requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + */ createShortMethods('when'); + /** + * @ngdoc method + * @name angular.module.ngMock.$httpBackend#expect + * @methodOf angular.module.ngMock.$httpBackend + * @description + * Creates a new request expectation. + * + * @param {string} method HTTP method. + * @param {string|RegExp} url HTTP url. + * @param {(string|RegExp)=} data HTTP request body. + * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header + * object and returns true if the headers match the current expectation. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + * + * - respond – `{function([status,] data[, headers])|function(function(method, url, data, headers)}` + * – The respond method takes a set of static data to be returned or a function that can return + * an array containing response status (number), response data (string) and response headers + * (Object). + */ $httpBackend.expect = function(method, url, data, headers) { var expectation = new MockHttpExpectation(method, url, data, headers); expectations.push(expectation); @@ -708,9 +990,99 @@ function createHttpBackendMock($delegate, $defer) { }; }; + + /** + * @ngdoc method + * @name angular.module.ngMock.$httpBackend#expectGET + * @methodOf angular.module.ngMock.$httpBackend + * @description + * Creates a new request expectation for GET requests. For more info see `expect()`. + * + * @param {string|RegExp} url HTTP url. + * @param {Object=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. See #expect for more info. + */ + + /** + * @ngdoc method + * @name angular.module.ngMock.$httpBackend#expectHEAD + * @methodOf angular.module.ngMock.$httpBackend + * @description + * Creates a new request expectation for HEAD requests. For more info see `expect()`. + * + * @param {string|RegExp} url HTTP url. + * @param {Object=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + */ + + /** + * @ngdoc method + * @name angular.module.ngMock.$httpBackend#expectDELETE + * @methodOf angular.module.ngMock.$httpBackend + * @description + * Creates a new request expectation for DELETE requests. For more info see `expect()`. + * + * @param {string|RegExp} url HTTP url. + * @param {Object=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + */ + + /** + * @ngdoc method + * @name angular.module.ngMock.$httpBackend#expectPOST + * @methodOf angular.module.ngMock.$httpBackend + * @description + * Creates a new request expectation for POST requests. For more info see `expect()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(string|RegExp)=} data HTTP request body. + * @param {Object=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + */ + + /** + * @ngdoc method + * @name angular.module.ngMock.$httpBackend#expectPUT + * @methodOf angular.module.ngMock.$httpBackend + * @description + * Creates a new request expectation for PUT requests. For more info see `expect()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(string|RegExp)=} data HTTP request body. + * @param {Object=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + */ + + /** + * @ngdoc method + * @name angular.module.ngMock.$httpBackend#expectJSONP + * @methodOf angular.module.ngMock.$httpBackend + * @description + * Creates a new request expectation for JSONP requests. For more info see `expect()`. + * + * @param {string|RegExp} url HTTP url. + * @returns {requestHandler} Returns an object with `respond` method that control how a matched + * request is handled. + */ createShortMethods('expect'); + /** + * @ngdoc method + * @name angular.module.ngMock.$httpBackend#flush + * @methodOf angular.module.ngMock.$httpBackend + * @description + * Flushes all pending requests using the trained responses. + * + * @param {number=} count Number of responses to flush (in the order they arrived). If undefined, + * all pending requests will be flushed. If there are no pending requests when the flush method + * is called an exception is thrown (as this typically a sign of programming error). + */ $httpBackend.flush = function(count) { if (!responses.length) throw Error('No pending request to flush !'); @@ -727,18 +1099,59 @@ function createHttpBackendMock($delegate, $defer) { $httpBackend.verifyNoOutstandingExpectation(); }; + + /** + * @ngdoc method + * @name angular.module.ngMock.$httpBackend#verifyNoOutstandingExpectation + * @methodOf angular.module.ngMock.$httpBackend + * @description + * Verifies that all of the requests defined via the `expect` api were made. If any of the + * requests were not made, verifyNoOutstandingExpectation throws an exception. + * + * Typically, you would call this method following each test case that asserts requests using an + * "afterEach" clause. + * + * <pre> + * afterEach($httpBackend.verifyExpectations); + * </pre> + */ $httpBackend.verifyNoOutstandingExpectation = function() { if (expectations.length) { throw Error('Unsatisfied requests: ' + expectations.join(', ')); } }; + + /** + * @ngdoc method + * @name angular.module.ngMock.$httpBackend#verifyNoOutstandingRequest + * @methodOf angular.module.ngMock.$httpBackend + * @description + * Verifies that there are no outstanding requests that need to be flushed. + * + * Typically, you would call this method following each test case that asserts requests using an + * "afterEach" clause. + * + * <pre> + * afterEach($httpBackend.verifyNoOutstandingRequest); + * </pre> + */ $httpBackend.verifyNoOutstandingRequest = function() { if (responses.length) { throw Error('Unflushed requests: ' + responses.length); } }; + + /** + * @ngdoc method + * @name angular.module.ngMock.$httpBackend#resetExpectations + * @methodOf angular.module.ngMock.$httpBackend + * @description + * Resets all request expectations, but preserves all backend definitions. Typically, you would + * call resetExpectations during a multiple-phase test when you want to reuse the same instance of + * $httpBackend mock. + */ $httpBackend.resetExpectations = function() { expectations.length = 0; responses.length = 0; @@ -870,13 +1283,164 @@ angular.module('ngMock', ['ng']).service({ * @name angular.module.ngMockE2E * @description * - * The `ngMockE2E` is an angular module which contains mock for `$httpBackend`. This mock allows you - * to either respond with fake data or delegate to real backend. + * The `ngMockE2E` is an angular module which contains mocks suitable for end-to-end testing. + * Currently there is only one mock present in this module - + * the {@link angular.module.ngMockE2E.$httpBackend e2e $httpBackend} mock. */ angular.module('ngMockE2E', ['ng']).init(function($provide) { $provide.decorator('$httpBackend', angular.mock.e2e.$httpBackendDecorator); }); +/** + * @ngdoc object + * @name angular.module.ngMockE2E.$httpBackend + * @description + * Fake HTTP backend implementation suitable for end-to-end testing or backend-less development of + * applications that use the {@link angular.module.ng.$http $http service}. + * + * *Note*: For fake http backend implementation suitable for unit testing please see + * {@link angular.module.ngMock.$httpBackend unit-testing $httpBackend mock}. + * + * This implementation can be used to respond with static or dynamic responses via the `when` api + * and its shortcuts (`whenGET`, `whenPOST`, etc) and optionally pass through requests to the + * real $httpBackend for specific requests (e.g. to interact with certain remote apis or to fetch + * templates from a webserver). + * + * As opposed to unit-testing, in an end-to-end testing scenario or in scenario when an application + * is being developed with the real backend api replaced with a mock, it is often desirable for + * certain category of requests to bypass the mock and issue a real http request (e.g. to fetch + * templates or static files from the webserver). To configure the backend with this behavior + * use the `passThrough` request handler of `when` instead of `respond`. + * + * Additionally, we don't want to manually have to flush mocked out requests like we do during unit + * testing. For this reason the e2e $httpBackend automatically flushes mocked out requests + * automatically, closely simulating the behavior of the XMLHttpRequest object. + * + * To setup the application to run with this http backend, you have to create a module that depends + * on the `ngMockE2E` and your application modules and defines the fake backend: + * + * <pre> + * myAppDev = angular.module('myAppDev', ['myApp', 'ngMockE2E']); + * myAppDev.run(function($httpBackend) { + * phones = [{name: 'phone1'}, {name: 'phone2'}]; + * + * // returns the current list of phones + * $httpBackend.whenGET('/phones').respond(phones); + * + * // adds a new phone to the phones array + * $httpBackend.whenPOST('/phones').respond(function(method, url, data) { + * phones.push(angular.fromJSON(data)); + * }); + * $httpBackend.whenGET(/^\/templates\//).passThrough(); + * //... + * }); + * </pre> + * + * Afterwards, bootstrap your app with this new module. + */ + +/** + * @ngdoc method + * @name angular.module.ngMockE2E.$httpBackend#when + * @methodOf angular.module.ngMockE2E.$httpBackend + * @description + * Creates a new backend definition. + * + * @param {string} method HTTP method. + * @param {string|RegExp} url HTTP url. + * @param {(string|RegExp)=} data HTTP request body. + * @param {(Object|function(Object))=} headers HTTP headers or function that receives http header + * object and returns true if the headers match the current definition. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. + * + * - respond – `{function([status,] data[, headers])|function(function(method, url, data, headers)}` + * – The respond method takes a set of static data to be returned or a function that can return + * an array containing response status (number), response data (string) and response headers + * (Object). + * - passThrough – `{function()}` – Any request matching a backend definition with `passThrough` + * handler, will be pass through to the real backend (an XHR request will be made to the + * server. + */ + +/** + * @ngdoc method + * @name angular.module.ngMockE2E.$httpBackend#whenGET + * @methodOf angular.module.ngMockE2E.$httpBackend + * @description + * Creates a new backend definition for GET requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. + */ + +/** + * @ngdoc method + * @name angular.module.ngMockE2E.$httpBackend#whenHEAD + * @methodOf angular.module.ngMockE2E.$httpBackend + * @description + * Creates a new backend definition for HEAD requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. + */ + +/** + * @ngdoc method + * @name angular.module.ngMockE2E.$httpBackend#whenDELETE + * @methodOf angular.module.ngMockE2E.$httpBackend + * @description + * Creates a new backend definition for DELETE requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. + */ + +/** + * @ngdoc method + * @name angular.module.ngMockE2E.$httpBackend#whenPOST + * @methodOf angular.module.ngMockE2E.$httpBackend + * @description + * Creates a new backend definition for POST requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(string|RegExp)=} data HTTP request body. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. + */ + +/** + * @ngdoc method + * @name angular.module.ngMockE2E.$httpBackend#whenPUT + * @methodOf angular.module.ngMockE2E.$httpBackend + * @description + * Creates a new backend definition for PUT requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @param {(string|RegExp)=} data HTTP request body. + * @param {(Object|function(Object))=} headers HTTP headers. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. + */ + +/** + * @ngdoc method + * @name angular.module.ngMockE2E.$httpBackend#whenJSONP + * @methodOf angular.module.ngMockE2E.$httpBackend + * @description + * Creates a new backend definition for JSONP requests. For more info see `when()`. + * + * @param {string|RegExp} url HTTP url. + * @returns {requestHandler} Returns an object with `respond` and `passThrough` methods that + * control how a matched request is handled. + */ angular.mock.e2e = {}; angular.mock.e2e.$httpBackendDecorator = ['$delegate', '$defer', createHttpBackendMock]; diff --git a/src/service/http.js b/src/service/http.js index e9c49ec2..f9a8d921 100644 --- a/src/service/http.js +++ b/src/service/http.js @@ -139,35 +139,314 @@ function $HttpProvider() { * @requires $q * @requires $injector * - * @param {object} config Object describing the request to be made and how it should be processed. - * The object has following properties: + * @description + * The `$http` service is a core Angular service that is responsible for communication with the + * remote HTTP servers via 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 angular.module.ngMock.$httpBackend $httpBackend mock}. + * + * For a higher level of abstraction, please check out the {@link angular.module.ng.$resource + * $resource} service. + * + * + * # 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 angular.module.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 status + * // code outside of the <200, 400) range + * }); + * </pre> + * + * Since the returned value is a Promise object, 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. + * + * + * # Shortcut methods + * + * Since all invocation of the $http service require definition of the http method and url and + * POST and PUT requests require response body/data to be provided as well, shortcut methods + * were created to simplify using the api: + * + * <pre> + * $http.get('/someUrl').success(successCallback); + * $http.post('/someUrl', data).success(successCallback); + * </pre> + * + * Complete list of shortcut methods: + * + * - {@link angular.module.ng.$http#get $http.get} + * - {@link angular.module.ng.$http#head $http.head} + * - {@link angular.module.ng.$http#post $http.post} + * - {@link angular.module.ng.$http#put $http.put} + * - {@link angular.module.ng.$http#delete $http.delete} + * - {@link angular.module.ng.$http#jsonp $http.jsonp} + * + * + * # 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 HTTP POST requests) + * - `Content-Type: application/json` + * - `$httpProvider.defaults.headers.put` (header defaults for HTTP PUT requests) + * - `Content-Type: application/json` + * + * To add or overwrite these defaults, simply add or remove a property from this configuration + * objects. To add headers for an HTTP method other than POST or PUT, simply add a new object + * with name equal to the lower-cased http method name, e.g. + * `$httpProvider.defaults.headers.get['My-Header']='value'`. + * + * + * # Request / Response transformations + * + * 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 config 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 + * + * These transformations can be overridden locally by specifying transform functions as + * `transformRequest` and/or `transformResponse` properties of the config object. To globally + * override the default transforms, override the `$httpProvider.defaults.transformRequest` and + * `$httpProvider.defaults.transformResponse` properties of the `$httpProvider`. + * + * + * # Caching + * + * You can enable caching by setting 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 for the first request. + * + * + * # Response interceptors + * + * 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 angular.module.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 angular.module.ng.$q promise} and returns the original or a new promise. + * + * Before you start creating interceptors, be sure to understand the + * {@link angular.module.ng.$q $q and deferred/promise APIs}. + * + * <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 + * }, 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 your design needs to consider security threats from + * {@link http://haacked.com/archive/2008/11/20/anatomy-of-a-subtle-json-vulnerability.aspx + * JSON Vulnerability} and {@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 web-site to turn your JSON resource URL into + * {@link http://en.wikipedia.org/wiki/JSON#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 following 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 first HTTP GET request. On subsequent non-GET 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 read the token. The token must be + * unique for each user and must be verifiable by the server (to prevent the JavaScript making + * up its own tokens). We recommend that the token is a digest of your site's authentication + * cookie with {@link http://en.wikipedia.org/wiki/Rainbow_table 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. * - **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.<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 $cacheFactory, this cache will be - * used for caching. + * GET request, otherwise if a cache instance built with + * {@link angular.module.ng.$cacheFactory $cacheFactory}, this cache will be used for + * caching. + * - **timeout** – `{number}` – timeout in milliseconds. * - * @returns {HttpPromise} Returns a 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: + * @returns {HttpPromise} Returns a {@link angular.module.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 pending requests. - * This is primarily meant to be used for debugging purposes. + * @property {Array.<Object>} pendingRequests Array of config objects for currently pending + * requests. This is primarily meant to be used for debugging purposes. * - * @description - * $http is a service through which XHR and JSONP requests can be made. + * + * @example + <doc:example> + <doc:source jsfiddle="false"> + <script> + function FetchCtrl($http) { + var self = this; + this.method = 'GET'; + this.url = 'examples/http-hello.html'; + + this.fetch = function() { + self.code = null; + self.response = null; + + $http({method: self.method, url: self.url}). + success(function(data, status) { + self.status = status; + self.data = data; + }). + error(function(data, status) { + self.data = data || "Request failed"; + self.status = status; + }); + }; + + this.updateModel = function(method, url) { + self.method = method; + self.url = url; + }; + } + </script> + <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', 'examples/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> + </doc:source> + <doc:scenario> + it('should make an xhr GET request', function() { + element(':button:contains("Sample GET")').click(); + element(':button:contains("fetch")').click(); + expect(binding('status')).toBe('http status code: 200'); + expect(binding('data')).toBe('http response data: Hello, $http!\n'); + }); + + 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('http status code: 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('http status code: 0'); + expect(binding('data')).toBe('http response data: Request failed'); + }); + </doc:scenario> + </doc:example> */ function $http(config) { config.method = uppercase(config.method); @@ -263,19 +542,6 @@ function $HttpProvider() { /** * @ngdoc method - * @name angular.module.ng.$http#patch - * @methodOf angular.module.ng.$http - * - * @description - * Shortcut method to perform `PATCH` 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 angular.module.ng.$http#jsonp * @methodOf angular.module.ng.$http * diff --git a/src/service/httpBackend.js b/src/service/httpBackend.js index f18808c6..291d24bb 100644 --- a/src/service/httpBackend.js +++ b/src/service/httpBackend.js @@ -15,7 +15,10 @@ var XHR = window.XMLHttpRequest || function() { * * @description * HTTP backend used by the {@link angular.module.ng.$http service} that delegates to - * XMLHttpRequest object. + * XMLHttpRequest object or JSONP and deals with browser incompatibilities. + * + * You should never need to use this service directly, instead use the higher-level abstractions: + * {@link angular.module.ng.$http $http} or {@link angular.module.ng.$resource $resource}. * * During testing this implementation is swapped with {@link angular.module.ngMock.$httpBackend mock * $httpBackend} which can be trained with responses. |
