angular.service.$xhr

Work in Progress This page is currently being revised. It might be incomplete or contain inaccuracies.

Description

Generates an XHR request. The $xhr service delegates all requests to $browser.xhr() and adds error handling and security features. While $xhr service provides nicer api than raw XmlHttpRequest, it is still considered a lower level api in angular. For a higher level abstraction that utilizes $xhr, please check out the $resource service.

Error handling

If no error callback is specified, XHR response with response code other then 2xx will be delegated to $xhr.error. The $xhr.error can intercept the request and process it in application specific way, or resume normal execution by calling the request success 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:

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 JSON Vulnerability and 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 JSON Vulnerability allows third party web-site to turn your JSON resource URL into 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

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 $xhr 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 salt for added security.

Dependencies

Usage

$xhr(method, url[, post], success, error);

Parameters

Example

  <script>
    function FetchCntl($xhr) {
      var self = this;

      this.fetch = function() {
        self.code = null;
        self.response = null;

        $xhr(self.method, self.url, function(code, response) {
          self.code = code;
          self.response = response;
        }, function(code, response) {
          self.code = code;
          self.response = response || "Request failed";
        });
      };

      this.updateModel = function(method, url) {
        self.method = method;
        self.url = url;
      };
    }
    FetchCntl.$inject = ['$xhr'];
  </script>
  <div ng:controller="FetchCntl">
    <select name="method">
      <option>GET</option>
      <option>JSON</option>
    </select>
    <input type="text" name="url" value="index.html" size="80"/>
    <button ng:click="fetch()">fetch</button><br>
    <button ng:click="updateModel('GET', 'index.html')">Sample GET</button>
    <button ng:click="updateModel('JSON', 'http://angularjs.org/greet.php?callback=JSON_CALLBACK&name=Super%20Hero')">Sample JSONP</button>
    <button ng:click="updateModel('JSON', 'http://angularjs.org/doesntexist&callback=JSON_CALLBACK')">Invalid JSONP</button>
    <pre>code={{code}}</pre>
    <pre>response={{response}}</pre>
  </div>
  it('should make xhr GET request', function() {
    element(':button:contains("Sample GET")').click();
    element(':button:contains("fetch")').click();
    expect(binding('code')).toBe('code=200');
    expect(binding('response')).toMatch(/angularjs.org/);
  });

  it('should make JSONP request to the angularjs.org', function() {
    element(':button:contains("Sample JSONP")').click();
    element(':button:contains("fetch")').click();
    expect(binding('code')).toBe('code=200');
    expect(binding('response')).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('code')).toBe('code=');
    expect(binding('response')).toBe('response=Request failed');
  });