CORS with Angular.js and Sinatra

//devblast.com/b/cors-with-angular-js-and-sinatra

UPDATE: I’ve created a follow-up article that shows PUT and DELETE calls.

Today, I’m gonna talk about something that always caused me a lot of pain, everytime I had to deal with it : Same-origin policy. This thing is simply awful if you have to make HTTP requests from Javascript.

To counter that, you can use JSONP. But only to make GET requests. If you need more than that (POST, PUT, DELETE), you will have to use Cross-Origin Resource Sharing and that’s what I am going to explain in this post. To do this, I will use Angular.js and Sinatra. Let’s get started, shall we ?

Prerequisites

To follow this tutorial, you will need a version of Ruby and the Sinatra gem installed. You will also need a webserver, if you don’t have any check my post about SimpleHTTPServer.

Setup

So here is the basic code nothing fancy, a very very simple index.html, an angular module called MyApp completly empty and a Sinatra app with the basic route /hi coming from the documentation.

HTML

<code class="hljs xml"><span class="hljs-meta"><!doctype html></span>
<span class="hljs-tag"><<span class="hljs-name">html</span> <span class="hljs-attr">ng-app</span>=<span class="hljs-string">"myApp"</span>></span>
    <span class="hljs-tag"><<span class="hljs-name">head</span>></span>
        <span class="hljs-tag"><<span class="hljs-name">title</span>></span>CORS App<span class="hljs-tag"></<span class="hljs-name">title</span>></span>
    <span class="hljs-tag"></<span class="hljs-name">head</span>></span>
    <span class="hljs-tag"><<span class="hljs-name">body</span>></span>
        Cross-Origin Resource Sharing
        <span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"<a class="vglnk" href="http://code.angularjs.org/1.1.5/angular.min.js" rel="nofollow">//code.angularjs.org/1.1.5/angular.min.js</a>"</span>></span><span class="hljs-tag"></<span class="hljs-name">script</span>></span>
        <span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"app.js"</span>></span><span class="hljs-tag"></<span class="hljs-name">script</span>></span>
    <span class="hljs-tag"></<span class="hljs-name">body</span>></span>
<span class="hljs-tag"></<span class="hljs-name">html</span>></span></code>

JS

<code class="hljs ruby">var app = angular.<span class="hljs-keyword">module</span>(<span class="hljs-string">'myApp'</span>, []);</code>

Ruby

<code class="hljs ruby"><span class="hljs-keyword">require</span> <span class="hljs-string">'sinatra'</span>
get <span class="hljs-string">'/hi'</span> <span class="hljs-keyword">do</span>
  <span class="hljs-string">"Hello World!"</span>
<span class="hljs-keyword">end</span></code>

Just create three files : index.html, app.js and server.rb and Copy/Paste the corresponding code in each file.

Run the code

You can run the Sinatra server with ruby server.rb and you can use any webserver to access index.html at //localhost:9000. Indeed, CORS calls won’t work if you use the file url, you need a real domain.

Let’s get started by configuring the Sinatra app (don’t forget to restart Sinatra server) :

Ruby

<code class="hljs ruby"><span class="hljs-keyword">require</span> <span class="hljs-string">'sinatra'</span>
<span class="hljs-keyword">require</span> <span class="hljs-string">'json'</span>

before <span class="hljs-keyword">do</span>
   content_type <span class="hljs-symbol">:json</span>      
<span class="hljs-keyword">end</span>

set <span class="hljs-symbol">:protection</span>, <span class="hljs-literal">false</span>

get <span class="hljs-string">'/movie'</span> <span class="hljs-keyword">do</span>
  { <span class="hljs-symbol">result:</span> <span class="hljs-string">"Monster University"</span> }.to_json
<span class="hljs-keyword">end</span>

post <span class="hljs-string">'/movie'</span> <span class="hljs-keyword">do</span>
  { <span class="hljs-symbol">result:</span> params[<span class="hljs-symbol">:movie</span>] }.to_json
<span class="hljs-keyword">end</span></code>

Okay, we are ready to go. If you access //localhost:4567/movie from your browser, you should be able to see the last movie I saw. So let’s make our first javascript HTTP GET call.

CORS Get

HTML

<code class="hljs django"><span class="xml"><span class="hljs-meta"><!doctype html></span>
<span class="hljs-tag"><<span class="hljs-name">html</span> <span class="hljs-attr">ng-app</span>=<span class="hljs-string">"myApp"</span>></span>
    <span class="hljs-tag"><<span class="hljs-name">head</span>></span>
        <span class="hljs-tag"><<span class="hljs-name">title</span>></span>CORS App<span class="hljs-tag"></<span class="hljs-name">title</span>></span>
    <span class="hljs-tag"></<span class="hljs-name">head</span>></span>
    <span class="hljs-tag"><<span class="hljs-name">body</span> <span class="hljs-attr">ng-controller</span>=<span class="hljs-string">"MainCtrl"</span>></span>
        Cross-Origin Resource Sharing<span class="hljs-tag"><<span class="hljs-name">br</span>/></span>

        <span class="hljs-tag"><<span class="hljs-name">button</span> <span class="hljs-attr">ng-click</span>=<span class="hljs-string">"get()"</span>></span>GET<span class="hljs-tag"></<span class="hljs-name">button</span>></span>
        Result : </span><span class="hljs-template-variable">{{result}}</span><span class="xml">
        <span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"<a class="vglnk" href="http://code.angularjs.org/1.1.5/angular.min.js" rel="nofollow">//code.angularjs.org/1.1.5/angular.min.js</a>"</span>></span><span class="hljs-tag"></<span class="hljs-name">script</span>></span>
        <span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"app.js"</span>></span><span class="hljs-tag"></<span class="hljs-name">script</span>></span>
    <span class="hljs-tag"></<span class="hljs-name">body</span>></span>
<span class="hljs-tag"></<span class="hljs-name">html</span>></span></span></code>

JS

<code class="hljs php"><span class="hljs-keyword">var</span> app = angular.module(<span class="hljs-string">'myApp'</span>, []);

app.controller(<span class="hljs-string">'MainCtrl'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">($scope, $http)</span> </span>{

    $scope.get = <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">()</span> </span>{
        $http.get(<span class="hljs-string">"<a class="vglnk" href="http://localhost:4567/movie" rel="nofollow">//localhost:4567/movie</a>"</span>).success(<span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">(result)</span> </span>{
            console.log(<span class="hljs-string">"Success"</span>, result);
            $scope.result = result;
        }).error(<span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">()</span> </span>{
            console.log(<span class="hljs-string">"error"</span>);
        });
    };

});</code>

Open it in your browser. You should see this beautiful red line saying that you cannot access this resouce due to the Same-origin policy.

<code class="hljs sql">XMLHttpRequest cannot <span class="hljs-keyword">load</span> <span class="hljs-keyword">http</span>://localhost:<span class="hljs-number">4567</span>/movie. Origin <span class="hljs-keyword">http</span>://localhost:<span class="hljs-number">8000</span> <span class="hljs-keyword">is</span> <span class="hljs-keyword">not</span> allowed <span class="hljs-keyword">by</span> <span class="hljs-keyword">Access</span>-Control-<span class="hljs-keyword">Allow</span>-Origin.</code>

Time to fix that with Cross-origin Resource Sharing !

JS

<code class="hljs php">  <span class="hljs-keyword">var</span> app = angular.module(<span class="hljs-string">'myApp'</span>, []);

  app.config(<span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">($httpProvider)</span> </span>{
      <span class="hljs-comment">//Enable cross domain calls</span>
      $httpProvider.defaults.useXDomain = <span class="hljs-keyword">true</span>;

      <span class="hljs-comment">//Remove the header used to identify ajax call  that would prevent CORS from working</span>
      delete $httpProvider.defaults.headers.common[<span class="hljs-string">'X-Requested-With'</span>];
  });

  app.controller(<span class="hljs-string">'MainCtrl'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">($scope, $http)</span> </span>{

    $scope.get = <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">()</span> </span>{
      $http.get(<span class="hljs-string">"<a class="vglnk" href="http://localhost:4567/movie" rel="nofollow">//localhost:4567/movie</a>"</span>).success(<span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">(result)</span> </span>{
          console.log(<span class="hljs-string">"Success"</span>, result);
          $scope.result = result;
      }).error(<span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">()</span> </span>{
          console.log(<span class="hljs-string">"error"</span>);
      });
    };

  });</code>

Ruby

<code class="hljs ruby"><span class="hljs-keyword">require</span> <span class="hljs-string">'sinatra'</span>
<span class="hljs-keyword">require</span> <span class="hljs-string">'json'</span>

before <span class="hljs-keyword">do</span>
   content_type <span class="hljs-symbol">:json</span>    
   headers <span class="hljs-string">'Access-Control-Allow-Origin'</span> =&gt; <span class="hljs-string">'*'</span>, 
            <span class="hljs-string">'Access-Control-Allow-Methods'</span> =&gt; [<span class="hljs-string">'OPTIONS'</span>, <span class="hljs-string">'GET'</span>, <span class="hljs-string">'POST'</span>]  
<span class="hljs-keyword">end</span>

set <span class="hljs-symbol">:protection</span>, <span class="hljs-literal">false</span>

options <span class="hljs-string">'/movie'</span> <span class="hljs-keyword">do</span>
    <span class="hljs-number">200</span>
<span class="hljs-keyword">end</span>

get <span class="hljs-string">'/movie'</span> <span class="hljs-keyword">do</span>
  { <span class="hljs-symbol">result:</span> <span class="hljs-string">"Monster University"</span> }.to_json
<span class="hljs-keyword">end</span>

post <span class="hljs-string">'/movie'</span> <span class="hljs-keyword">do</span>
  { <span class="hljs-symbol">result:</span> params[<span class="hljs-symbol">:movie</span>] }.to_json
<span class="hljs-keyword">end</span></code>

Restart Sinatra server and tadaaaa ! Now it’s working \o/ So what did we do ?

Well, first we told the $http module that we were going to send requests to another domain. We also removed the header used by the browser/server to identify our call as XmlHTTPRequest. Then, we enabled CORS on the server by specifying the available HTTP methods and the allowed origins (in our case, any origin *).

You probably noticed that we added a new route on our server :

options '/movie' do

This is part of the Cross-Origin Resource Sharing specification. Before sending a request to another domain, a call with the HTTP method OPTIONS will be fired. The response to this call will determine if CORS is available or not. This response must contain the allowed origins and the available HTTP methods

Security notice

In a production environment, you should not accept any origin of course, you should specify the allowed domain names like this :

<code class="hljs php">headers <span class="hljs-string">'Access-Control-Allow-Origin'</span> =&gt; <span class="hljs-string">'<a class="vglnk" href="//localhost:9000/" rel="nofollow">//localhost:9000</a>, <a class="vglnk" href="//localhost:8000/" rel="nofollow">//localhost:8000</a>'</span></code>

You should also keep the default Sinatra protection enabled. However, you may have to disable the http origin security to make CORS calls work with Sinatra.

set :protection, except: :http_origin

Unfortunately, we are not done yet. Let’s add a post call and see it miserably fail… :

CORS Post

HTML

<code class="hljs django"><span class="xml"><span class="hljs-meta"><!doctype html></span>
<span class="hljs-tag"><<span class="hljs-name">html</span> <span class="hljs-attr">ng-app</span>=<span class="hljs-string">"myApp"</span>></span>
    <span class="hljs-tag"><<span class="hljs-name">head</span>></span>
        <span class="hljs-tag"><<span class="hljs-name">title</span>></span>CORS App<span class="hljs-tag"></<span class="hljs-name">title</span>></span>
    <span class="hljs-tag"></<span class="hljs-name">head</span>></span>
    <span class="hljs-tag"><<span class="hljs-name">body</span> <span class="hljs-attr">ng-controller</span>=<span class="hljs-string">"MainCtrl"</span>></span>
        Cross-Origin Resource Sharing<span class="hljs-tag"><<span class="hljs-name">br</span>/></span>

        <span class="hljs-tag"><<span class="hljs-name">button</span> <span class="hljs-attr">ng-click</span>=<span class="hljs-string">"get()"</span>></span>GET<span class="hljs-tag"></<span class="hljs-name">button</span>></span>
        Result : </span><span class="hljs-template-variable">{{resultGet}}</span><span class="xml">
        <span class="hljs-tag"><<span class="hljs-name">br</span>/></span>
        <span class="hljs-tag"><<span class="hljs-name">input</span> <span class="hljs-attr">ng-model</span>=<span class="hljs-string">"movie"</span>/></span><span class="hljs-tag"><<span class="hljs-name">button</span> <span class="hljs-attr">ng-click</span>=<span class="hljs-string">"post(movie)"</span>></span>POST<span class="hljs-tag"></<span class="hljs-name">button</span>></span>
        Result : </span><span class="hljs-template-variable">{{resultPost}}</span><span class="xml">
        <span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"<a class="vglnk" href="http://code.angularjs.org/1.1.5/angular.min.js" rel="nofollow">//code.angularjs.org/1.1.5/angular.min.js</a>"</span>></span><span class="hljs-tag"></<span class="hljs-name">script</span>></span>
        <span class="hljs-tag"><<span class="hljs-name">script</span> <span class="hljs-attr">src</span>=<span class="hljs-string">"app.js"</span>></span><span class="hljs-tag"></<span class="hljs-name">script</span>></span>
    <span class="hljs-tag"></<span class="hljs-name">body</span>></span>
<span class="hljs-tag"></<span class="hljs-name">html</span>></span></span></code>

JS

<code class="hljs php"><span class="hljs-keyword">var</span> app = angular.module(<span class="hljs-string">'myApp'</span>, []);

app.config(<span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">($httpProvider)</span> </span>{
    <span class="hljs-comment">//Enable cross domain calls</span>
    $httpProvider.defaults.useXDomain = <span class="hljs-keyword">true</span>;

    <span class="hljs-comment">//Remove the header containing XMLHttpRequest used to identify ajax call </span>
    <span class="hljs-comment">//that would prevent CORS from working</span>
    delete $httpProvider.defaults.headers.common[<span class="hljs-string">'X-Requested-With'</span>];
});

app.controller(<span class="hljs-string">'MainCtrl'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">($scope, $http)</span> </span>{

    $scope.get = <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">()</span> </span>{
        $http.get(<span class="hljs-string">"<a class="vglnk" href="http://localhost:4567/movie" rel="nofollow">//localhost:4567/movie</a>"</span>).success(<span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">(result)</span> </span>{
            console.log(<span class="hljs-string">"Success"</span>, result);
            $scope.resultGet = result;
        }).error(<span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">()</span> </span>{
            console.log(<span class="hljs-string">"error"</span>);
        });
    };

    $scope.post = <span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">(value)</span> </span>{
        $http.post(<span class="hljs-string">"<a class="vglnk" href="http://localhost:4567/movie" rel="nofollow">//localhost:4567/movie</a>"</span>, { <span class="hljs-string">'movie'</span>: value }).success(<span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">(result)</span> </span>{
            console.log(result);
            $scope.resultPost = result;
        }).error(<span class="hljs-function"><span class="hljs-keyword">function</span><span class="hljs-params">()</span> </span>{
            console.log(<span class="hljs-string">"error"</span>);
        });
    };

});</code>

Yay, we got a new error :

<code class="hljs sql">OPTIONS <a class="vglnk" href="//localhost:4567/movie" rel="nofollow">http://localhost:4567/movie</a> Request header field Content-Type is not allowed by Access-Control-Allow-Headers. angular.min.js:106

XMLHttpRequest cannot <span class="hljs-keyword">load</span> <span class="hljs-keyword">http</span>://localhost:<span class="hljs-number">4567</span>/movie. Request header <span class="hljs-keyword">field</span> <span class="hljs-keyword">Content</span>-<span class="hljs-keyword">Type</span> <span class="hljs-keyword">is</span> <span class="hljs-keyword">not</span> allowed <span class="hljs-keyword">by</span> <span class="hljs-keyword">Access</span>-Control-<span class="hljs-keyword">Allow</span>-Headers.</code>

So what’s wrong ?

The answer is in the error, we need to add Content-Type to the allowed headers :

<code class="hljs php"> headers <span class="hljs-string">'Access-Control-Allow-Origin'</span> =&gt; <span class="hljs-string">'*'</span>, 
        <span class="hljs-string">'Access-Control-Allow-Methods'</span> =&gt; [<span class="hljs-string">'OPTIONS'</span>, <span class="hljs-string">'GET'</span>, <span class="hljs-string">'POST'</span>],
        <span class="hljs-string">'Access-Control-Allow-Headers'</span> =&gt; <span class="hljs-string">'Content-Type'</span></code>

Now, Post requests are working. But the server doest not send back the submitted word. Instead, we receive : Object {result: null}

If we add a trace to check the received parameters on the server, we get, well nothing :

<code class="hljs css"><span class="hljs-selector-tag">I</span>, <span class="hljs-selector-attr">[2013-08-14T21:11:04.901188 #28960]</span>  <span class="hljs-selector-tag">INFO</span> <span class="hljs-selector-tag">--</span> : <span class="hljs-selector-tag">Params</span> : {}</code>

That is due to how Angular.js handles the params to be sent with the post. Sinatra does not detect the params. We are going to do it by ourselves.

Ruby

<code class="hljs ruby"><span class="hljs-keyword">require</span> <span class="hljs-string">'sinatra'</span>
<span class="hljs-keyword">require</span> <span class="hljs-string">'json'</span>

use Rack::Logger

before <span class="hljs-keyword">do</span>
   content_type <span class="hljs-symbol">:json</span>    
   headers <span class="hljs-string">'Access-Control-Allow-Origin'</span> =&gt; <span class="hljs-string">'*'</span>, 
           <span class="hljs-string">'Access-Control-Allow-Methods'</span> =&gt; [<span class="hljs-string">'OPTIONS'</span>, <span class="hljs-string">'GET'</span>, <span class="hljs-string">'POST'</span>],
           <span class="hljs-string">'Access-Control-Allow-Headers'</span> =&gt; <span class="hljs-string">'Content-Type'</span>  
<span class="hljs-keyword">end</span>

set <span class="hljs-symbol">:protection</span>, <span class="hljs-literal">false</span>

options <span class="hljs-string">'/movie'</span> <span class="hljs-keyword">do</span>
    <span class="hljs-number">200</span>
<span class="hljs-keyword">end</span>

get <span class="hljs-string">'/movie'</span> <span class="hljs-keyword">do</span>
  { <span class="hljs-symbol">result:</span> <span class="hljs-string">"Monster University"</span> }.to_json
<span class="hljs-keyword">end</span>

post <span class="hljs-string">'/movie'</span> <span class="hljs-keyword">do</span>

  <span class="hljs-keyword">begin</span>
    params.merge! JSON.parse(request.env[<span class="hljs-string">"rack.input"</span>].read)
  <span class="hljs-keyword">rescue</span> JSON::ParserError
    logger.error <span class="hljs-string">"Cannot parse request body."</span> 
  <span class="hljs-keyword">end</span>

  { <span class="hljs-symbol">result:</span> params[<span class="hljs-symbol">:movie</span>], <span class="hljs-symbol">seen:</span> <span class="hljs-literal">true</span> }.to_json
<span class="hljs-keyword">end</span></code>

With this, it should work fine.

There is an other way to get the params, but they will be poorly formatted. You can add the following to app.js :

<code class="hljs ruby">$httpProvider.defaults.headers.post[<span class="hljs-string">"Content-Type"</span>] = <span class="hljs-string">"application/x-www-form-urlencoded"</span>;</code>

With this, we tell the server that our params come from a form. This is how Sinatra handles it :

<code class="hljs tex">I, [2013-08-14T21:42:05.648475 #30008]  INFO -- : Params : {"{<span class="hljs-tag">\<span class="hljs-name">"</span></span>movie<span class="hljs-tag">\<span class="hljs-name">"</span></span>:<span class="hljs-tag">\<span class="hljs-name">"</span></span>aaaa<span class="hljs-tag">\<span class="hljs-name">"</span></span>,<span class="hljs-tag">\<span class="hljs-name">"</span></span>rating<span class="hljs-tag">\<span class="hljs-name">"</span></span>:5,<span class="hljs-tag">\<span class="hljs-name">"</span></span>comment<span class="hljs-tag">\<span class="hljs-name">"</span></span>:<span class="hljs-tag">\<span class="hljs-name">"</span></span>Super fun !<span class="hljs-tag">\<span class="hljs-name">"</span></span>}"=&gt;nil}</code>

As I told you, it’s kinda weird. Well, you can easily clean it up by getting the first key of the hash, parse it and get the params hash, that’s up to you.

Well, now you should be able to setup CORS calls between your client and your backend ! If you have any problem, feel free to contact me or leave a comment. It took me a while to figure out everything and I hope it will save you a lot of time. If you are interested in seeing PUT and DELETE calls, leave a comment and I will add it.

The code is available on Github.