Sinatra controller params method coming in empty on JSON post request

json, sinatra

Solution

In order to answer this question, we're first going to have to look at some HTTP requests (which are no more than simple `telnet` 'messages'; this can easily be recreated by hand). First, what happens when you submit a normal HTML `<form>`? The `POST` request will look very similar to this (probably with some extra parameters, but we don't need to worry about that right now):

POST /submit-form HTTP/1.1
Host: localhost
Content-Type: application/x-www-form-urlencoded
Content-Length: 12

name=JohnDoe

Typing that character-by-character (replacing the `/sample-form` with whatever the URL is for any form action, and the `Host` with your IP or hostname) will be same thing your browser would send. The important thing to learn from this is the parameter syntax: `formname=formvalue`. Sinatra interprets the body of the `POST` request into the `params` hash using this syntax! Therefore, JSON requests, being substantially incompatible with this, will not show up in the `params` hash because of this.

However, what you're doing in your `before` block shows the proper solution. While `params` from the above would be `{'name' => 'JohnDoe'}`, `request.body.read` will return the original body, `name=JohnDoe`.

In knowing this, one can come to understand why your 'hacky' solution works: the original body of the `POST` request gets interpreted by `JSON.parse`, then gets inserted into the empty `params` hash. The reason it seems hacky is because `params` is a needless middleman in this example. The following should do the job:

post '/locations/new' do
    @json = JSON.parse(request.body.read)
    # @json now contains a hash of the submitted JSON content
end

However, a solution exercising better practice would either only respond if JSON content is provided, or respond differently if a standard form is submitted. As seen in the above example HTTP `POST` request, an HTML form is identified with the `application/x-www-form-urlencoded` MIME type, whereas JSON is identified with `application/json`. If you want the specifics on checking the `POST` request's MIME type, check out this question with some great answers on how to do this with Sinatra!

Problem

I've got a Sinatra app and in most of my controllers json comes in and is picked up automatically in the params object. However, I've got a post action that doesn't get the params at all unless I play a trick with a before method to pull the request.body parameters parse them as JSON and merge them into the params hash. Here is the controller, along with the filter method: ``` before do if request.request_method == "POST" body_parameters = request.body.read params.merge!(JSON.parse(body_parameters)) end end post '/locations/new' do content_type :json puts "params after post params method = #{params.inspect}" ... other code ... end ``` The output I see is basically that the parameters in the controller action actually are in there correctly. However, if I comment out the before call the params are empty. The before itself feels like a hack. I would expect those params to come in no matter what...I must be doing something wrong in there but I don't know what it is. Any help would be deeply appreciated...

Original source

Related problems