Sambath Prum
Sambath Prum

Reputation: 1888

How to make a post request with XMLHttpRequest object?

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

Answers (3)

Anil Maurya
Anil Maurya

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

  1. Add gem 'rack-cors' to Gemfile
  2. bundle install
  3. Add following to config/application.rb
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

guest
guest

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

CWitty
CWitty

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

Related Questions