codebrew / backbone-rails

Easily use backbone.js with rails 3.1
MIT License
1.62k stars 255 forks source link

custom Sync messes up subdomain GET request #112

Closed JeanMertz closed 12 years ago

JeanMertz commented 12 years ago

I've been using the following function to access my API on a different subdomain (with CORS enabled on the server):

  api_url: (path) ->
    domain = window.location.host.split('.')
    domain.shift()
    "#{window.location.protocol}//api.#{domain.join('.') + path}"

Then in the Collection I use:

class MyApp.Backend.Collections.Projects extends MyApp.Backend.Collections.Application
  url: -> @api_url('/projects')

The problem is, now this is showing up in my Rails log:

Started OPTIONS "/projects" for 127.0.0.1 at 2012-04-20 20:23:53 +0200
[20:23:53] [FATAL]  [api]  ActionController::RoutingError (No route matches [OPTIONS] "/projects"):

As you can see, it's supposed to show Started GET "/projects", instead of OPTIONS, and without the custom Sync method, this works, but not anymore. I am having a hard time finding the issue in the custom method, anyone know how to solve this? I can't be the only one wanting to access my Rails app form a different subdomain?

I know I can use full domains for the url property, but I rather not specify the full url everywhere when I will always be using this subdomain.

ryanfitz commented 12 years ago

It sounds like your browser is making a preflighted request, which will be an OPTIONS request, but that should only happen on non get requests.

JeanMertz commented 12 years ago

Well, it only happens with the custom Sync override. Without it, a normal GET request is sent (but without the CSFR token)

JeanMertz commented 12 years ago

I still haven't solved this issue, but I am only using the backbone_rails_sync.js and backbone_datalink.js files from this repository. Could that cause this issue?

Also, looking at the source code for backbone_rails_sync.js, if I do this:

// Make the request.
console.log(params);
return $.ajax(params);

It returns:

Object
  beforeSend: function ( xhr ) {
  complete: function (jqXHR, textStatus) {
  dataType: "json"
  error: function (model, resp) {
  parse: true
  success: function (resp, status, xhr) {
  type: "GET"
  url: "http://api.myapp.dev/projects"
  __proto__: Object

The params clearly show type: GET and dataType: JSON, yet the next line when $.ajax(params) is fired, the server logs show:

Started OPTIONS "/projects" for 127.0.0.1 at 2012-04-21 13:24:07 +0200

Any more ideas?

JeanMertz commented 12 years ago

Actually, it seems this issue is due to using CORS, see: http://metajack.im/2010/01/19/crossdomain-ajax-for-xmpp-http-binding-made-easy/

Still though, it doesn't happen without the backbone_rails_sync.js file, so I'll see if there is a way around this and apply a pull request if I can fix this.

JeanMertz commented 12 years ago

Well, it seems this is the part that causes the problems:

beforeSend: function( xhr ) {
  var token = $('meta[name="csrf-token"]').attr('content');
  if (token) xhr.setRequestHeader('X-CSRF-Token', token);

  model.trigger('sync:start');
}

To be more precise, it's the part that sets the request header. So it seems that this can't be fixed from the client side. I did find a solution for Rails developers, but the same principle applies to all servers that wan't to use CORS with this gem.

Basically what I've done is create a catch-all route for OPTIONS requests and return the correct CORS headers. But this does tax your server more, so if I DO find a better solution to this I will post it here.

ryanfitz commented 12 years ago

I've actually built a couple of CORS servers in the past and in order to handle passing headers the browser will first make a preflight request. Your server needs to respond back saying what methods, headers, and origins are allowed.

I'm going to mark this as closed because this needs to be handled server side.

JeanMertz commented 12 years ago

You are correct about the preflight part. It wasn't firing because apparently, the browse only does this when it isn't sure if a specific action is allowed or not.

So in the case of backbone-rails, the default action is a simple GET request with the standard http headers, for which the browser doesn't fire a preflight request. However, when you add the X-CSRF-Token header, using setRequestHeader, the browser asks the server if this header is allowed before sending the actual get request.

So this was what's happening in my case, so simply modifying the backend application to work with this OPTIONS request was all that was needed.