diff options
| author | Igor Minar | 2011-06-29 00:25:13 -0700 | 
|---|---|---|
| committer | Igor Minar | 2011-06-30 00:34:50 -0700 | 
| commit | c5f3a413bc00acf9ac1046fb15b454096a8890c6 (patch) | |
| tree | 9000f1f5f377d5f022aa8e338dfe810dc913d282 | |
| parent | d3fb5b411e979d0a4815c663c3489652fc5350f9 (diff) | |
| download | angular.js-c5f3a413bc00acf9ac1046fb15b454096a8890c6.tar.bz2 | |
feat:$xhr: provide access to $xhr header defaults
$xhr header defaults are now exposed as $xhr.defaults.headers.common and
$xhr.default.headers.<httpmethod>. This allows applications to configure
their defaults as needed.
This commit doesn't allow headers to be set per request, only per
application. Per request change would require api change, which I tried
to avoid *for now*.
| -rw-r--r-- | CHANGELOG.md | 1 | ||||
| -rw-r--r-- | src/Browser.js | 11 | ||||
| -rw-r--r-- | src/service/xhr.js | 42 | ||||
| -rw-r--r-- | test/BrowserSpecs.js | 59 | ||||
| -rw-r--r-- | test/service/xhrSpec.js | 125 | 
5 files changed, 184 insertions, 54 deletions
| diff --git a/CHANGELOG.md b/CHANGELOG.md index bc6febc7..d9c926f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@  - New [ng:disabled], [ng:selected], [ng:checked], [ng:multiple] and [ng:readonly] directives.  - Added support for string representation of month and day in [date] filter.  - Added support for `prepend()` to [jqLite]. +- Added support for configurable HTTP header defaults for the [$xhr] service.  ### Bug Fixes diff --git a/src/Browser.js b/src/Browser.js index 5a675e3c..37fb4931 100644 --- a/src/Browser.js +++ b/src/Browser.js @@ -8,14 +8,6 @@ var XHR = window.XMLHttpRequest || function () {    throw new Error("This browser does not support XMLHttpRequest.");  }; -// default xhr headers -var XHR_HEADERS = { -  DEFAULT: { -    "Accept": "application/json, text/plain, */*", -    "X-Requested-With": "XMLHttpRequest" -  }, -  POST: {'Content-Type': 'application/x-www-form-urlencoded'} -};  /**   * @private @@ -108,8 +100,7 @@ function Browser(window, document, body, XHR, $log) {      } else {        var xhr = new XHR();        xhr.open(method, url, true); -      forEach(extend({}, XHR_HEADERS.DEFAULT, XHR_HEADERS[uppercase(method)] || {}, headers || {}), -        function(value, key) { +      forEach(headers, function(value, key) {            if (value) xhr.setRequestHeader(key, value);        });        xhr.onreadystatechange = function() { diff --git a/src/service/xhr.js b/src/service/xhr.js index 62b27263..d26cda42 100644 --- a/src/service/xhr.js +++ b/src/service/xhr.js @@ -24,6 +24,22 @@   * and process it in application specific way, or resume normal execution by calling the   * request callback method.   * + * # HTTP Headers + * The $xhr service will automatically add certain http headers to all requests. These defaults can + * be fully configured by accessing the `$xhr.defaults.headers` configuration object, which + * currently contains this default configuration: + * + * - `$xhr.defaults.headers.common` (headers that are common for all requests): + *   - `Accept: application/json, text/plain, *\/*` + *   - `X-Requested-With: XMLHttpRequest` + * - `$xhr.defaults.headers.post` (header defaults for HTTP POST requests): + *   - `Content-Type: application/x-www-form-urlencoded` + * + * To add or overwrite these defaults, simple add or remove a property from this configuration + * object. To add headers for an HTTP method other than POST, simple create a new object with name + * equal to the lowercased http method name, e.g. `$xhr.defaults.headers.get['My-Header']='value'`. + * + *   * # 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 @@ -126,7 +142,21 @@     </doc:example>   */  angularServiceInject('$xhr', function($browser, $error, $log, $updateView){ -  return function(method, url, post, callback){ + +  var xhrHeaderDefaults = { +    common: { +      "Accept": "application/json, text/plain, */*", +      "X-Requested-With": "XMLHttpRequest" +    }, +    post: {'Content-Type': 'application/x-www-form-urlencoded'}, +    get: {},      // all these empty properties are needed so that client apps can just do: +    head: {},     // $xhr.defaults.headers.head.foo="bar" without having to create head object +    put: {},      // it also means that if we add a header for these methods in the future, it +    'delete': {}, // won't be easily silently lost due to an object assignment. +    patch: {} +  }; + +  function xhr(method, url, post, callback){      if (isFunction(post)) {        callback = post;        post = null; @@ -155,8 +185,12 @@ angularServiceInject('$xhr', function($browser, $error, $log, $updateView){        } finally {          $updateView();        } -    }, { -        'X-XSRF-TOKEN': $browser.cookies()['XSRF-TOKEN'] -    }); +    }, extend({'X-XSRF-TOKEN': $browser.cookies()['XSRF-TOKEN']}, +              xhrHeaderDefaults.common, +              xhrHeaderDefaults[lowercase(method)]));    }; + +  xhr.defaults = {headers: xhrHeaderDefaults}; + +  return xhr;  }, ['$browser', '$xhr.error', '$log', '$updateView']); diff --git a/test/BrowserSpecs.js b/test/BrowserSpecs.js index 08756904..cb59137e 100644 --- a/test/BrowserSpecs.js +++ b/test/BrowserSpecs.js @@ -100,31 +100,6 @@ describe('browser', function(){        });      }); -    it('should set headers for all requests', function(){ -      var code, response, headers = {}; -      browser.xhr('GET', 'URL', 'POST', function(c,r){ -        code = c; -        response = r; -      }, {'X-header': 'value'}); - -      expect(xhr.method).toEqual('GET'); -      expect(xhr.url).toEqual('URL'); -      expect(xhr.post).toEqual('POST'); -      expect(xhr.headers).toEqual({ -        "Accept": "application/json, text/plain, */*", -        "X-Requested-With": "XMLHttpRequest", -        "X-header":"value" -      }); - -      xhr.status = 202; -      xhr.responseText = 'RESPONSE'; -      xhr.readyState = 4; -      xhr.onreadystatechange(); - -      expect(code).toEqual(202); -      expect(response).toEqual('RESPONSE'); -    }); -      it('should normalize IE\'s 1223 status code into 204', function() {        var callback = jasmine.createSpy('XHR'); @@ -138,24 +113,28 @@ describe('browser', function(){        expect(callback.argsForCall[0][0]).toEqual(204);      }); -    it('should not set Content-type header for GET requests', function() { -      browser.xhr('GET', 'URL', 'POST-DATA', function(c, r) {}); - -      expect(xhr.headers['Content-Type']).not.toBeDefined(); -    }); - -    it('should set Content-type header for POST requests', function() { -      browser.xhr('POST', 'URL', 'POST-DATA', function(c, r) {}); +    it('should set only the requested headers', function() { +      var code, response, headers = {}; +      browser.xhr('POST', 'URL', null, function(c,r){ +        code = c; +        response = r; +      }, {'X-header1': 'value1', 'X-header2': 'value2'}); -      expect(xhr.headers['Content-Type']).toBeDefined(); -      expect(xhr.headers['Content-Type']).toEqual('application/x-www-form-urlencoded'); -    }); +      expect(xhr.method).toEqual('POST'); +      expect(xhr.url).toEqual('URL'); +      expect(xhr.post).toEqual(''); +      expect(xhr.headers).toEqual({ +        "X-header1":"value1", +        "X-header2":"value2" +      }); -    it('should set default headers for custom methods', function() { -      browser.xhr('CUSTOM', 'URL', 'POST-DATA', function(c, r) {}); +      xhr.status = 202; +      xhr.responseText = 'RESPONSE'; +      xhr.readyState = 4; +      xhr.onreadystatechange(); -      expect(xhr.headers['Accept']).toEqual('application/json, text/plain, */*'); -      expect(xhr.headers['X-Requested-With']).toEqual('XMLHttpRequest'); +      expect(code).toEqual(202); +      expect(response).toEqual('RESPONSE');      });    }); diff --git a/test/service/xhrSpec.js b/test/service/xhrSpec.js index ebcd90d4..1f31bb6f 100644 --- a/test/service/xhrSpec.js +++ b/test/service/xhrSpec.js @@ -102,6 +102,131 @@ describe('$xhr', function() {      expect(response).toEqual([1, 'abc', {foo:'bar'}]);    }); + +  describe('http headers', function() { + +    describe('default headers', function() { + +      it('should set default headers for GET request', function(){ +        var callback = jasmine.createSpy('callback'); + +        $browserXhr.expectGET('URL', '', {'Accept': 'application/json, text/plain, */*', +                                          'X-Requested-With': 'XMLHttpRequest'}). +                    respond(234, 'OK'); + +        $xhr('GET', 'URL', callback); +        $browserXhr.flush(); +        expect(callback).toHaveBeenCalled(); +      }); + + +      it('should set default headers for POST request', function(){ +        var callback = jasmine.createSpy('callback'); + +        $browserXhr.expectPOST('URL', 'xx', {'Accept': 'application/json, text/plain, */*', +                                             'X-Requested-With': 'XMLHttpRequest', +                                             'Content-Type': 'application/x-www-form-urlencoded'}). +                    respond(200, 'OK'); + +        $xhr('POST', 'URL', 'xx', callback); +        $browserXhr.flush(); +        expect(callback).toHaveBeenCalled(); +      }); + + +      it('should set default headers for custom HTTP method', function(){ +        var callback = jasmine.createSpy('callback'); + +        $browserXhr.expect('FOO', 'URL', '', {'Accept': 'application/json, text/plain, */*', +                                              'X-Requested-With': 'XMLHttpRequest'}). +                    respond(200, 'OK'); + +        $xhr('FOO', 'URL', callback); +        $browserXhr.flush(); +        expect(callback).toHaveBeenCalled(); +      }); + + +      describe('custom headers', function() { + +        it('should allow appending a new header to the common defaults', function() { +          var callback = jasmine.createSpy('callback'); + +          $browserXhr.expectGET('URL', '', {'Accept': 'application/json, text/plain, */*', +                                            'X-Requested-With': 'XMLHttpRequest', +                                            'Custom-Header': 'value'}). +                      respond(200, 'OK'); + +          $xhr.defaults.headers.common['Custom-Header'] = 'value'; +          $xhr('GET', 'URL', callback); +          $browserXhr.flush(); +          expect(callback).toHaveBeenCalled(); +          callback.reset(); + +          $browserXhr.expectPOST('URL', 'xx', {'Accept': 'application/json, text/plain, */*', +                                               'X-Requested-With': 'XMLHttpRequest', +                                               'Content-Type': 'application/x-www-form-urlencoded', +                                               'Custom-Header': 'value'}). +                      respond(200, 'OK'); + +         $xhr('POST', 'URL', 'xx', callback); +         $browserXhr.flush(); +         expect(callback).toHaveBeenCalled(); +        }); + + +        it('should allow appending a new header to a method specific defaults', function() { +          var callback = jasmine.createSpy('callback'); + +          $browserXhr.expectGET('URL', '', {'Accept': 'application/json, text/plain, */*', +                                            'X-Requested-With': 'XMLHttpRequest', +                                            'Content-Type': 'application/json'}). +                      respond(200, 'OK'); + +          $xhr.defaults.headers.get['Content-Type'] = 'application/json'; +          $xhr('GET', 'URL', callback); +          $browserXhr.flush(); +          expect(callback).toHaveBeenCalled(); +          callback.reset(); + +          $browserXhr.expectPOST('URL', 'x', {'Accept': 'application/json, text/plain, */*', +                                              'X-Requested-With': 'XMLHttpRequest', +                                              'Content-Type': 'application/x-www-form-urlencoded'}). +                      respond(200, 'OK'); + +         $xhr('POST', 'URL', 'x', callback); +         $browserXhr.flush(); +         expect(callback).toHaveBeenCalled(); +        }); + + +        it('should support overwriting and deleting default headers', function() { +          var callback = jasmine.createSpy('callback'); + +          $browserXhr.expectGET('URL', '', {'Accept': 'application/json, text/plain, */*'}). +                      respond(200, 'OK'); + +          //delete a default header +          delete $xhr.defaults.headers.common['X-Requested-With']; +          $xhr('GET', 'URL', callback); +          $browserXhr.flush(); +          expect(callback).toHaveBeenCalled(); +          callback.reset(); + +          $browserXhr.expectPOST('URL', 'xx', {'Accept': 'application/json, text/plain, */*', +                                               'Content-Type': 'application/json'}). +                      respond(200, 'OK'); + +         //overwrite a default header +         $xhr.defaults.headers.post['Content-Type'] = 'application/json'; +         $xhr('POST', 'URL', 'xx', callback); +         $browserXhr.flush(); +         expect(callback).toHaveBeenCalled(); +        }); +      }); +    }); +  }); +    describe('xsrf', function(){      it('should copy the XSRF cookie into a XSRF Header', function(){        var code, response; | 
