Reputation: 1888
I have a JavaScript snippet in client side requesting data via cross domain from another Rails API app.
var myData = { "event": {"name": "some event name", "property": "some value"} };
var request = new XMLHttpRequest();
request.open("POST", "http://localhost:3000/api/events", true);
request.setRequestHeader('Content-Type', 'application/json');
request.send(JSON.stringify(myData));
I wonder why adding request.setRequestHeader('Content-Type', 'application/json'); causes browser to send a POST request, but without this statement, browser sends an OPTIONS request instead?
In addition, I got an error ActionController::ParameterMissing - param is missing or the value is empty: event: in my Rails app when running the code above. Why does browser not send the data set in myData variable?
Version info: ruby 2.1.5p273, Rails 4.1.8
# routes.rb
Rails.application.routes.draw do
match '/api/events' => 'api/v1/events#create', via: :options
...
end
# app/controller/api/v1/events_controller.rb
class API::V1::EventsController < ApplicationController
skip_before_action :verify_authenticity_token
before_action :set_headers
def index
@events = Event.all
render json: @events, status: :ok
end
def show
@event = Event.find(params[:id])
render json: @event
end
def create
@event = Event.new(event_params)
if @event.save
render json: @event, status: :created
else
render @event.errors, status: :unprocessable_entity
end
end
def update
@event = Event.find(params[:id])
if @event.update(event_params)
raise "#{@event.name}"
head :no_content
else
render @event.errors, status: :unprocessable_entity
end
end
private
def event_params
params.require(:event).permit(:name, :property)
end
def set_headers
headers['Access-Control-Allow-Origin'] = '*'
headers['Access-Control-Expose-Headers'] = 'ETag'
headers['Access-Control-Allow-Methods'] = 'OPTIONS' #'GET, POST, PATCH, PUT, DELETE, OPTIONS, HEAD'
headers['Access-Control-Allow-Headers'] = '*,x-requested-with,Content-Type,If-Modified-Since,If-None-Match'
headers['Access-Control-Max-Age'] = '1728000'
end
end
# Rails log
Started OPTIONS "/api/events" for 127.0.0.1 at 2015-01-09 14:54:31 -0600
Processing by API::V1::EventsController#create as */*
Completed 400 Bad Request in 2ms
ActionController::ParameterMissing - param is missing or the value is empty: event:
actionpack (4.1.8) lib/action_controller/metal/strong_parameters.rb:187:in `require'
app/controllers/api/v1/events_controller.rb:39:in `event_params'
app/controllers/api/v1/events_controller.rb:18:in `create'
actionpack (4.1.8) lib/action_controller/metal/implicit_render.rb:4:in `send_action'
...
Upvotes: 2
Views: 627
Reputation: 2328
Browser send OPTIONS request for every cross domain ajax post request to check if request is safe or not. To read more about it search about 'cors'.
To fix your problem remove 'set_header' before_filter from controller and
config.middleware.insert_before 0, "Rack::Cors" do allow do origins '*' resource '*', :headers => :any, :methods => [:get, :post, :options, :put, :head, :delete] end end
Upvotes: 3
Reputation: 6698
CORS requires a preflight request for non-simple requests. The preflight request uses the OPTIONS method. A POST request with unspecified Content-Type
is not simple, because a user agent that doesn't conform to CORS will not normally make such a request.
A simple POST request can only use certain Content-Type
values. Roughly, these correspond to the types that a <form>
element could send. One of these (recently added) is application/json
.
So if you set Content-Type: application/json
, the browser won't need to do a preflight request.
Upvotes: 1
Reputation: 4536
I would use jQuery when executing this since it is on all the pages with Rails by default already. You could do $.post('/api/events', myData)
it will handle a bunch of the manual work for you and make it work across multiple browsers. On a side note, you don't need to wrap the key
's in quotes.
Upvotes: 1