MultiJson::LoadError: 795: unexpected token when trying to parse incoming body request - ruby

I'm losing my sanity trying to parse an incoming request on a Sinatra app.
This is my spec
payload = File.read("./spec/support/fixtures/payload.json")
post "/api/v1/verify_payload", { :payload => payload }
last_response.body.must_equal payload
where is simply spec/support/fixtures/payload.json
{"ref":"refs/heads/master"}
My route looks like
post '/verify_payload' do
params = MultiJson.load(request.body.read, symbolize_keys: true)
params[:payload]
end
And running the spec I get the following error:
MultiJson::LoadError: 795: unexpected token at 'payload=%7B%22ref%22%3A%22refs%2Fheads%2Fmaster%22%7D'
I have tried to parse the body request in different ways without luck.
How can I make the request valid JSON?
THANKS

If you want to send a JSON-encoded POST body, you have to set the Content-Type header to application/json. With Rack::Test, you should be able to do this:
post "/api/v1/verify_payload", payload, 'CONTENT_TYPE' => 'application/json'
Alternatively:
header 'Content-Type' => 'application/json'
post '/api/v1/verify_payload'
More info here: http://www.sinatrarb.com/testing.html

The problem it is that you are passing a ruby hash, that is not well formated, you should pass a json object.
Something like this, should work:
post "/api/v1/verify_payload", { :payload => payload.to_json }

Related

How can I return a response to an AngularJS $http POST to Sinatra?

I am able to successfully POST from AngularJS to my Sinatra route such that I get a "200" Status.
When I inspect in Chrome, I see the request payload as follows:
{"input":"testing"}
But response is empty.
Here is how I am POST-ing:
$http({
method: "POST",
url: "http://floating-beyond-3787.herokuapp.com/angular",
/*url: "https://worker-aws-us-east-1.iron.io/2/projects/542c8609827e3f0005000123/tasks/webhook?code_name=botweb&oauth=LOo5Nc0x0e2GJ838_nbKoheXqM0",*/
data: {input: $scope.newChat}
})
.success(function (data)
{
// $scope.chats.push(data);
$scope.chats.push($scope.newChat)
// if successful then get the value from the cache?
})
.error(function (data)
{
$scope.errors.push(data);
});
};
$scope.newChat = null
Chrome under Request Payload shows it properly -- as above.
When I check the logs in Heroku where I run my Sinatra app, I can't tell if I am properly processing the request payload. And I'm definitely not getting anything in the Response:
post '/angular' do
puts "params: #{params}"
puts params[:input]
puts #json = JSON.parse(request.body.read)
return RestClient.post 'https://worker.io' {:send => params[:input]}
end
My expectation is:
The Sinatra app can receive the payload :input
It can successfully post to my worker on iron.io
It can return something back in the Response to Angular JS along with Success.
Is this possible and if so, how?
Possibly you are running into a case where the request.body has already been read further up the chain before hitting your route.
Try the following
request.body.rewind
request_payload = JSON.parse request.body.read
This is a fairly common issue encountered in Sinatra so if this addresses your issue you may want to put it in a before filter.
before do
request.body.rewind
#request_payload = JSON.parse request.body.read
end
Also the following will not work with a JSON payload.
params[:input]
The params[:field] style works if the Content-Type is application/x-www-form-urlencoded to allow accessing form data in a traditional web application style. It also works to pull params off a parameterized route; something like the following.
post '/angular/:data'
puts params[:data]
# Do whatever processing you need in here
# assume you created a no_errors var to track problems with the
# post
if no_errors
body(json({key: val, key2: val2, keyetc: valetc}))
status 200
else
body(({oh_snap: "An error has occurred!"}).to_json) # json(hash) or hash.to_json should both work here.
status 400 # Replace with your appropriate 4XX error here...
end
end
Something I did recently was to use this last style post 'myroute/:myparam and then Base64 encode a JSON payload on the client side and send it in the URL :myparam slot. This is a bit of a hack and is not something I would recommend as a general practice. I had a client application that could not properly encode the JSON data + headers into the request body; so this was a viable workaround.

How to set the body of a POST request using Ruby Mechanize?

How can you set the body of a POST request using the Ruby Mechanize gem. I know you can do
mechanize.post(url, query, headers)
but I want to set the body of the POST request with a JSON string. Is that possible? So, similar to something like this with jQuery:
$.ajax({
type: 'POST',
url: 'myurl',
data: "{'key1':'value1','key2':'value2'}",
...
});
I don't really like the answer you linked to in your comment because it employs to_json() which is a rails method, and the tags for your question do not indicate that your question pertains to rails. In any case, I think the answer needs some discussion.
Here is the mechanize method:
Mechanize#post(url, query, headers)
...and your stated goal is:
I want to set the body of the POST request
Mechanize#post() allows you to set the body of the request to anything you want, but you also have to consider the question:
What is the server side expecting?
You gave an example of a jquery ajax() request for what you want to do. jquery uses the following default Content-Type header when sending an ajax() request:
application/x-www-form-urlencoded; charset=UTF-8
That tells the server that the body of the post request is going to be written in a specific secret code. Well, it's not much of a secret; it looks like this:
name1=val1&name2=val2
That secret code's name is x-www-form-urlencoded. Because the server is given the name of the secret code in the Content-Type header, the server knows how to read the body of the post request.
In the Mechanize#post() method, the second parameter is 'query', and the mechanize docs say this about the query argument:
The query is specified by either a string, or
a list of key-value pairs represented by a hash, or
an array of arrays.
http://rubydoc.info/gems/mechanize/Mechanize#post-instance_method
If you want to use the secret code named x-www-form-urlencoded in the body of your Mechanize#post() request, then you can provide a Hash with name/value pairs, e.g.
my_hash = {
'data' => '{"key1":"value1","key2":"value2"}'
}
Then you call Mechanize#post() like this:
my_agent.post(
'http://target_site.com',
my_hash,
{'Content-Type' => 'application/x-www-form-urlencoded; charset=UTF-8'},
)
Then Mechanize will convert the 'query' Hash into a String using the secret code named x-www-form-urlencoded and insert the string into the body of the post request. On the server side, the application that receives the post request can retrieve the json string doing something like this:
json_str = post_variables['data']
You should be aware that there are other secret codes that can be used for the body of a post request. One of them is called json, which is a string formatted using javascript syntax, for example:
'{
"id": 1,
"name": "A green door",
"price": 12.50,
"tags": ["home", "green"]
}'
Note how there are no '=' signs or '&' symbols in the json format--as there are with the x-www-form-urlencoded format, so the json secret code is much different from the x-www-form-urlencoded secret code.
If you want to use the json secret code in the body of your post request, you need to change two things when you call Mechanize#post(url, query, headers):
Provide a String for the 'query' argument.
Tell the server that the body of the post request uses the json secret code.
Like this:
json_str = '{"key1":"value1","key2":"value2"}'
my_agent.post(
'http://target_site.com',
json_str,
{'Content-Type' => 'application/json'},
)
When you pass a String argument for the query parameter, Mechanize doesn't do any processing of the String before inserting the String into the body of the post request. On the server side, the application that receives the post request can retrieve the json string by doing something like this:
json_str = request.body.read
#Then probably:
hash = JSON.parse(json_str)
The one hitch is that the server can ignore the Content-Type header and try to read the body of the post request using a secret code that it has already decided upon. If the body of your post request is not written in the secret code that the server expects, then you will get an error.
Note that the 'data' string you posted isn't valid json because it uses single quotes around the properties and values.

Ruby Typhoeus gem - Invalid Json in body?

I have the following code:
require 'Typhoeus'
url = Typhoeus::Request.new("https://fluidsurveys.com/api/v2/webhooks/subscribe/",
userpwd: "username_test:password_test",
method: :post,
body: {
'subscription_url' => 'http://glacial-spire-test.herokuapp.com/hooks/response_created_callback',
'event' => 'response_complete'
},
headers: { 'Content-Type' => "application/json"})
response = url.run.body
puts response
This returns a response code of 400 and an error:
Content-Type set to application/json but could not decode valid JSON in request body.
Here are the docs for the API call I am making: http://docs.fluidsurveys.com/api/webhooks.html
POST /api/v2/webhooks/subscribe/¶
Returns a status of 409 if a webhook with the subscription url already exists. Returns a
status of 201 if the webhook was successfully created. Requests must be sent as an
application/json-encoded dictionary with the required fields subscription_url and event
Sample request (ACCORDING TO DOCS):
{
"subscription_url": "http://fluidsurveys.com/api/v2/callback/",
"event": "response_complete",
"survey": 1,
"collector": 1
}
What am I doing wrong here? survey and collector are optional params, and I don't see an issue with the json in my body.
I am guessing you might need to convert the request body into JSON using a library like Oj (https://github.com/ohler55/oj). Using the Oj library:
requestBody = {
'subscription_url' => 'http://glacial-spire-test.herokuapp.com/hook/response_created_callback',
'event' => 'response_complete'
}
url = Typhoeus::Request.new("https://fluidsurveys.com/api/v2/webhooks/subscribe/",
userpwd: "username_test:password_test",
method: :post,
body: Oj.dump(requestBody, mode: :compat),
headers: { 'Content-Type' => "application/json"})
response = url.run.body
puts response
The critical line is:
body: Oj.dump(requestBody, mode: :compat)
If you need to load any JSON content to Ruby, just use Oj.load

Unable to decode request as valid JSON using RUBY

I am making the following API GET request, using ruby 1.9.3 and the httparty gem:
uri= HTTParty.post("www.surveys.com/api/v2/contacts",
:basic_auth => auth,
:headers => { 'ContentType' => 'application/json' },
:body => {
"custom_test" => test,
"name" => firstname,
"email" => emailaddress
}
)
The variables auth,test,firstname, and emailaddress are valid. This is the response I am receiving back from my request:
{
"code": "invalid_json",
"description": "Request sent with Content-Type: application/json but was unable to decode request body as valid json.",
"success": false
}
500
#<Net::HTTPInternalServerError:0x007fe4eb98bde0>
What is wrong with the way I am posting this JSON request?
EDIT: It's probably worth noting that the API allows you to define custom attributes to a contact, hence the "custom_test" attribute in the body.
Since you are receiving an internal server error (500) instead of a not accepted (406), most likely there is coding problem on the server, because an exception that he is not expecting is happening instead of delivery to you a nice error explaining what is wrong (and this would be my first guess).
But let's say it is a problem with the JSON communication. Maybe you have to specify that you are accepting json format?
Try
:headers => { 'ContentType' => 'application/json', 'Accept' => 'application/json' },
Accept header definition from w3:
The Accept request-header field can be used to specify certain media types which are acceptable for the response. Accept headers can be used to indicate that the request is specifically limited to a small set of desired types, as in the case of a request for an in-line image.
And content-type header definition:
The Content-Type entity-header field indicates the media type of the entity-body sent to the recipient or, in the case of the HEAD method, the media type that would have been sent had the request been a GET.
You're sending a regular HTTP query string while saying it's json.
From HTTParty manual: "body: The body of the request. If it‘s a Hash, it is converted into query-string format, otherwise it is sent as-is."
Try:
:body => JSON.generate({
"custom_test" => test,
"name" => firstname,
"email" => emailaddress
})
You need to require 'JSON'

HTTParty and text/xml

I'm trying to make a POST request using HTTParty, in which I need the content-type to be text/xml. How can I make that happen? Right now the API I'm calling is complaining I'm not sending any xml. If I call it using curl I get the same error, unless I specify content-type to text/xml.
HTTParty.post url, :body => xml, :headers => {'Content-type' => 'text/xml'}

Resources