AI

Accessing External Apis With Angularjs

25 Aug 2013

Before getting into how to access external APIs with angular.js, let's cover some background material.

XHR (XMLHttpRequest)
This is an API available in JavaScript that allows for the sending and retrieval of http/https requests and responses to/from a server.  The response data can be used to alter the current document without reloading the page. It is typically used to transfer JSON payloads.

Same Origin Policy

A policy implemented by browsers that prevents access to scripts outside the current domain. XHR is subject to this policy, <script> tags are not. This policy means that scripts on mysite.com can not make XHR requests for data from yoursite.com-- there are workarounds to this (JSONP, CORS, cross domain messaging, modifying document.domain property)

JSONP
JSON with padding (or prefix) where the padding or prefix is a function call that wraps the response data. JSONP is a way to get data from a server in a different domain. By modifying the URL of the request to include '?jsonp=callBackFn' or '?callback=callBackFn' that notifies the responding server to wrap the response in the function named callBackFn.  The end result is that callBackFn(response_data) will be invoked in the context of the requesting script. Without JSONP, you would just receive back the raw data. With JSONP, you can receive back arbitrary javascript (though it typically tends to be JSON) that will be evaluated in the callback function you provide.

CORS (Cross Origin Resource Sharing)

A mechanism allowing JavaScript on one site to make XHR requests to another domain. This is enabled through the use of special HTTP headers. This allows a script on mysite.com to make XHR requests to yoursite.com provided that yoursite.com returns the proper HTTP headers. Specifically, yoursite.com would need to respond with "Access-Control-Allow-Origin: $value" where $value could either be '*' or 'mysite.com'

CORS supports all HTTP methods unlike JSONP which only supports GET.

Preflighting
A mechanism utilized in CORS whereby an initial "pre-flight" request is made to the server to determine if the appropriate permissions are available before making the real request. A preflight request is an 'OPTIONS' HTTP request that the server can respond to by specifying the allowed methods and origin. If the server responds appropriately, the browser sends the real request

The plunker code snippets below attempt to retrieve some data from various external APIs and display the data through the $scope's data property. Hit the play icon in the top right of each plunker to see the result.

$http.get on an API that does not support CORS
This first plunker below shows a naive get of the OpenWeatherMap API using $http.get

Clearly, this did not work. Looking at the console, you can see the following error messages.




You can run the curl cmd below to reveal the headers for this request

>> curl -X OPTIONS -i 'http://api.openweathermap.org/data/2.5/weather?lat=35&lon=139' -H "Origin: http://example.com"

Notice that there is no "Access-Control-Allow-Origin" header.
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Date: Thu, 01 Aug 2013 19:22:36 GMT
Server: nginx
X-Powered-By: OWM
Content-Length: 422
Connection: keep-alive

This means that the OpenWeatherMap API does support CORS. We can try to access the API via JSONP.

$http.jsonp on an API that does not support CORS, but supports JSONP
The plunker below shows the retrieval of OpenWeatherMap data using $http.jsonp



Success! We are able to retrieve OpenWeatherMap API data using JSONP.

$http.get of an API that supports CORS
The plunker below shows the retrieval of github api data using http.get


Success! Running curl on the github API will show the CORS related headers.


>> curl -X OPTIONS -i https://api.github.com -H "Origin: http://example.com"

HTTP/1.1 204 No Content
Server: GitHub.com
Date: Thu, 01 Aug 2013 19:29:16 GMT
Status: 204 No Content
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: ETag, Link, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes
Access-Control-Max-Age: 86400
Access-Control-Allow-Headers: Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-Requested-With
Access-Control-Allow-Methods: GET, POST, PATCH, PUT, DELETE
Access-Control-Allow-Origin: *
Vary: Accept-Encoding

APIs that do not support preflight requests
Finally, lets look at the Spotify API which does not respond appropriately to the preflight OPTIONS request.

The plunker below shows the retrieval of spotify api data using http.get in angular 1.0


This does not work as the preflight request is rejected by the server.

curl -X OPTIONS -i 'http://ws.spotify.com/search/1/artist.json?q=bah' -H "Origin: http://example.com"

HTTP/1.1 400 Bad Request
Server: nginx
Date: Thu, 01 Aug 2013 19:39:34 GMT
Content-Type: text/html
Content-Length: 134
Connection: keep-alive
Keep-Alive: timeout=10


$http requests in Angular 1.0.x include the 'X-Requested-With' header which triggers the preflight request in CORS. The plunker below removes this header.




Success! We are now able to retrieve data from the Spotify API.

The 'X-Requested-With' header has been removed in Angular 1.1.x
The plunker below shows the retrieval of Spotify API data using $http.get in angular 1.1



Summary
Use curl -X OPTIONS -i $api_endpoint -H "http://example.com" to see if $api_endpoint supports preflight requests. If not and you are using angular 1.0.x, remove the 'X-Requested-With' header from the $httpProvider

Use curl -i $api_endpoint?callback=callBackFn -H "http://example.com" to see if $api_endpoint supports JSONP

Not covered here, but angular also provides $resource which can be used to interact with RESTful API services.