Exercise Your Mobile App’s API with AngularJS and a Simple HTTP Proxy

Capturing your mobile app’s API responses typically very arduous:

  • Run your app, and run through a scenario to generate the calls you want while you grab the logs.
  • or… run a bunch of API request with Postman or curl, but having to run the calls in order and copy/paste Authentication headers, etc to make it work.

You would think building a AngularJS web interface for your API to do the tedious parts for you would be easy… which it technically is… but not in a way that you can trick the browser into thinking it’s secure.  That’s because a web browser uses security policies that typical block CORS (Cross-Origin Resource Sharing).  That means that if you are on http://myui.com and want to make a request to your API (which is on http://myapi.com) and you API doesn’t have JSONP (JSON with Padding) support, you are in a hard spot and need something server-side to make those calls for you, and then return the results.

A trivial example of the pain CORS will cause is shown here with my GitHub profile.  If I want to pull this data, and add it to my website, I can’t do it because of CORS blocking.

curl https://api.github.com/users/handstandsam

{
  "login": "handstandsam",
  "id": 264948,
  "avatar_url": "https://avatars.githubusercontent.com/u/264948?v=3",
  "gravatar_id": "",
  "url": "https://api.github.com/users/handstandsam",
  "html_url": "https://github.com/handstandsam",
  "followers_url": "https://api.github.com/users/handstandsam/followers",
  "following_url": "https://api.github.com/users/handstandsam/following{/other_user}",
  "gists_url": "https://api.github.com/users/handstandsam/gists{/gist_id}",
  "starred_url": "https://api.github.com/users/handstandsam/starred{/owner}{/repo}",
  "subscriptions_url": "https://api.github.com/users/handstandsam/subscriptions",
  "organizations_url": "https://api.github.com/users/handstandsam/orgs",
  "repos_url": "https://api.github.com/users/handstandsam/repos",
  "events_url": "https://api.github.com/users/handstandsam/events{/privacy}",
  "received_events_url": "https://api.github.com/users/handstandsam/received_events",
  "type": "User",
  "site_admin": false,
  "name": "Sam Edwards",
  "company": "http://github.com/handstandtech",
  "blog": "http://handstandsam.com",
  "location": null,
  "email": null,
  "hireable": null,
  "bio": null,
  "public_repos": 7,
  "public_gists": 2,
  "followers": 10,
  "following": 58,
  "created_at": "2010-05-04T22:15:11Z",
  "updated_at": "2016-02-26T23:08:36Z"
}

GitHub is smart enough to provide the ability to use JSONP (invokes a local javascript method with the result of your API request), which allows us to actually do this.

curl https://api.github.com/users/handstandsam?callback=mycallback

/**/mycallback({
  "meta": {
    "Content-Type": "application/javascript; charset=utf-8",
    "X-RateLimit-Limit": "60",
    "X-RateLimit-Remaining": "55",
    "X-RateLimit-Reset": "1457061648",
    "Cache-Control": "public, max-age=60, s-maxage=60",
    "Vary": "Accept",
    "ETag": "\"5dd9556a9a8f8067c294a028b1f1a21d\"",
    "Last-Modified": "Fri, 26 Feb 2016 23:08:36 GMT",
    "X-GitHub-Media-Type": "github.v3",
    "status": 200
  },
  "data": {
    "login": "handstandsam",
    "id": 264948,
    "avatar_url": "https://avatars.githubusercontent.com/u/264948?v=3",
    "gravatar_id": "",
    "url": "https://api.github.com/users/handstandsam",
    "html_url": "https://github.com/handstandsam",
    "followers_url": "https://api.github.com/users/handstandsam/followers",
    "following_url": "https://api.github.com/users/handstandsam/following{/other_user}",
    "gists_url": "https://api.github.com/users/handstandsam/gists{/gist_id}",
    "starred_url": "https://api.github.com/users/handstandsam/starred{/owner}{/repo}",
    "subscriptions_url": "https://api.github.com/users/handstandsam/subscriptions",
    "organizations_url": "https://api.github.com/users/handstandsam/orgs",
    "repos_url": "https://api.github.com/users/handstandsam/repos",
    "events_url": "https://api.github.com/users/handstandsam/events{/privacy}",
    "received_events_url": "https://api.github.com/users/handstandsam/received_events",
    "type": "User",
    "site_admin": false,
    "name": "Sam Edwards",
    "company": "http://github.com/handstandtech",
    "blog": "http://handstandsam.com",
    "location": null,
    "email": null,
    "hireable": null,
    "bio": null,
    "public_repos": 7,
    "public_gists": 2,
    "followers": 10,
    "following": 58,
    "created_at": "2010-05-04T22:15:11Z",
    "updated_at": "2016-02-26T23:08:36Z"
  }
})

Most of us aren’t that lucky however, and JSONP is not supported by the API we are using.  And while this is great in many cases, it still doesn’t help us with anything other than GET requests.  POST, PUT and DELETE commands are all blocked from CORS so we are very limited.

If you are just interested in GET requests, and JSONP is not a possibility, you could instead modify the server code to return the header “Access-Control-Allow-Origin: *”. Most modern browsers would allow you to do a CORS GET request. Unfortunately in a lot of cases, GET requests are not enough or we don’t have access to the server code.

Well, how do I get around this to make REST calls to our server, like POST?

Well, write some server code that can do the requests for you since it won’t have security restrictions like the browser does.  But, most of us don’t have time or that.  The solution I came up with on the latest project I’m learning the API for is “php-guzzle-http-proxy“. This is as dirt simple as I could get and navigate around all these CORS problems I have been mentioning without having to write ANY server-side code.

Just plop this repo in a directory of your PHP server, and start making requests (/proxy in this case).  You’ll just add in a special header to your request I’ve named “X-Proxy-Url”.  This is the URL you REALLY want to call, but CORS won’t let you.  You can do any type of request you want: GET, POST, PUT or DELETE.

In curl it would look like:

curl -H "X-Proxy-Url: https://myapi.com/login" -H "Accept: application/json" -X POST -d '{"username": "sam", "password": "notsecure"}' http://myui.com/proxy

In AngularJS it would look like:

$http({
  method: 'POST',
  url: '/proxy,
  headers: {
    'Accept': 'application/json',
    'X-Proxy-Url': 'https://myapi.com/login'
  },
  data: {"username": "sam", "password": "notsecure"}
}).success(function(response){
  console.log(response.data);
});

That’s it. No server-side code required, just this one tiny PHP proxy script to allow you to make all types of REST calls including GET, POST, etc.

The tool I wrote (that unfortunately I can’t share) used Angular and Bootstrap.  When using the two in combo, when you get your JSON response back, display it with the following tag to make it formatted and easy to read.

<pre>{{data | json}}</pre>

Let me know if you found this helpful on Twitter at @HandstandSam

———————–

Related Links:

Continuous Integration at LesFurets.com – Richmond Java Users Group (RJUG)

“Our highest priority is to satisfy the customer through early and continuous delivery of valuable software.” – 1st principal of the Agile Manifesto

This presentation by Raphaël Brugier (@rbrugier) at the Richmond Java Users Group last night (Feb 17th) was really good and highlighted:

  • Continuous Integration.  A case study of LesFurets.com. 40,000 unit tests running in 3 minutes.  Selenium tests running in 15 minutes.  Passing tests == shippable code.
  • They went from shipping a feature in a month, to as soon as ready (possibly only a few days).
  • A different kind of Git Flow using git octopus. – https://github.com/lesfurets/git-octopus
  • They have new developers hit the deploy button their first day.  Share deployment responsibilities within the dev team.
  • “If it hurts, do it more often.” – In regards to doing a deployment/release.
  • Work on feature branches.  Ship features as ready, not based on sprints. (git octopus facilitates this)
  • Detect feature branch merge conflicts after every push and make sure it’s resolved ASAP.
  • QA is owned by developers and sign off is given by product when dev demos it.
  • and more…

I would highly recommend watching the recording of the event.  Heads up: The speaker had a French accent and the video is from my MacBook’s camera, however, you get to watch it and otherwise you wouldn’t be able to :-p.

YouTube Link: https://www.youtube.com/watch?v=uTOPvC3lV1Q

Slides: http://www.slideshare.net/raphaelbrugier/continuous-delivery-journey-at-lesfuretscom

Meetup Info: http://www.meetup.com/Richmond-Java-Users-Group/events/226909145/

Enabling Cross-Origin Requests in Chrome from a HTML file:///

To allow cross-origin AJAX request using POST/PUT/DELETE requests to occur from a local html file that you open in your browser such as:

file:///Users/handstandsam/index.html

On your Mac, quit Chrome completely, then re-launch it using the following Terminal command:

open /Applications/Google\ Chrome.app/ –args –disable-web-security

That’s it.

WARNING: This does disable web security, so be cautious about what sites you access while Chrome is launched in this mode.

More info on Stack Overflow: http://stackoverflow.com/questions/3102819/disable-same-origin-policy-in-chrome