parseUser, passing user #1 as JSON. Implementing this server-side logic in a Rails app is straightforward.
ActionController::Base#render accepts a
:callback option to specify the function to pass the resulting JSON to.
Let’s try this out (by running the Rails app locally) on the command line using
DRYing up JSONP with
Repeating the same
:callback option for multiple actions supporting JSONP quickly becomes tedious. rack-jsonp-middleware is a Ruby gem that includes a piece of Rack middleware that can take care of this repetition for us. For any JSONP request,
rack-jsonp-middleware will require a few changes to our client and server. Let’s first add the gem to our app’s Gemfile.
Then add it to the Rails middleware stack.
rack-jsonp-middleware considers a request a JSONP request if the url ends in .jsonp. We’ll need to change the ending of our client url from .js to .jsonp.
ActionController::MimeResponds.respond_to in our controller to simplify the implementation.
Testing this refactored version from the command line returns the same response as our previous implementation.
JSONP is a nice, simple solution for reading data from a server in another domain. But what if you want to write data? For that, there’s CORS.
CORS (Cross-Origin Resource Sharing)
CORS is a W3C standard, that specifies a way for a client to determine if a particular request can be made to a server in a different domain. With JSONP you’re limited to HTTP GET requests. CORS on the other hand, allows any type of request. It requires you to define who can do what to a given resource.
In CORS, the client first makes a “preflight” request to a server in another domain. This is an HTTP OPTIONS request, asking the server if the client can make a particular request. The “preflight” request includes CORS-specific headers specifying the client’s domain, the type of request they want to make (POST, GET, etc.), and any HTTP headers they want to send. The server response answers the request using CORS-specific headers. If the request is allowed, the client can then issue the cross-domain request.
Adding CORS support to a Rails app is easy with the rack-cors Ruby gem.
Supporting CORS in Rails with
rack-cors handles CORS’s “preflight” requests by adding support for HTTP OPTIONS requests. It also includes a DSL for specifying on a per-resource basis, the allowable requesting domains, the types of requests, and the supported HTTP headers.
Let’s take a look at adding CORS support for updating and deleting users in our Rails app. The first step is to add
rack-cors to our app’s Gemfile.
Then we’ll add it to the Rails middleware stack. This is where you configure your resources for CORS.
Our configuration allows HTTP PUT and DELETE requests from http://server1.example.com to
/users/\d+.json (we used a regular expression to match the user id in the URL). The allowable headers in such requests are:
Origin – the domain from where the request will be made (this is required by CORS).
Accept – the acceptable response media type.
Content-Type – the media type of the body of the request.
Let’s try this out from the command line using
curl (this is the exact same CORS “preflight” request that jQuery will make when sending a cross-domain AJAX request in a browser).
This “preflight” request uses several CORS-specific headers:
Origin – the domain from where the request will be made.
Access-Control-Request-Headers – a list of headers we want to send with our request.
Access-Control-Request-Method – the type of request we want to make.
Essentially, this “preflight” request is asking the server at
http://localhost:3000 if we can send an update from
http://server1.example.com to one of its users.
The server response also includes several CORS-specific headers:
Access-Control-Allow-Origin – the domains that are allowed to make a request to this resource (a value of “*” is a wildcard that matches any domain).
Access-Control-Allow-Methods – the types of requests that are allowed (both PUT and DELETE in the above response).
Access-Control-Allow-Headers – the allowable request headers.
rack-cors has also added some additional CORS-specific headers with default values:
Access-Control-Expose-Headers – a list of additional response headers the client can access. By default, the client can only access simple response headers such as
rack-cors defaults this header to an empty list.
Access-Control-Max-Age – how long (in seconds) the client can cache this “preflight” response.
rack-cors defaults this to 20 days.
Access-Control-Allow-Credentials – tells the client whether it should send cookies in CORS requests.
rack-cors defaults this to true.
Browser Support for CORS
According to Wikipedia, CORS is currently supported by all major browsers (IE 8+) except Opera.
I recently used CORS to support an internal app. This meant only supporting Chrome, Firefox, and Safari. So far, I’ve had no problems using CORS in any of these browsers.
Fine-grained Access Control
Access-Control-Allow-Origin header in CORS, gives us more flexibility and security than JSONP. Instead of opening up a resource to any domain using JSONP, we can create a whitelist of allowable domains, HTTP methods, and headers.
The Rise of Thick Clients
JSON APIs are already trivial to implement in Rails. And thanks to Ruby gems, such as
rack-cors, opening them up to thick client apps running on other domains is just as easy.