How to receive a JSON object with Rack - ruby-on-rails

I have a very simple Ruby Rack server, like:
app = Proc.new do |env|
req = Rack::Request.new(env).params
p req.inspect
[200, { 'Content-Type' => 'text/plain' }, ['Some body']]
end
Rack::Handler::Thin.run(app, :Port => 4001, :threaded => true)
Whenever I send a POST HTTP request to the server with an JSON object:
{ "session": {
"accountId": String,
"callId": String,
"from": Object,
"headers": Object,
"id": String,
"initialText": String,
"parameters": Object,
"timestamp": String,
"to": Object,
"userType": String } }
I receive nothing. I can detect the request received but can't get the data. The results at my console for puts req.inspect is something like:
"{}"
How am I supposed to get the data?
I tried to change that with something like:
request = Rack::Request.new env
object = JSON.parse request.body
puts JSON.pretty_generate(object)
But I'm getting the following warning:
!! Unexpected error while processing request: can't convert StringIO into String

It seems that I'm supposed to use something like:
msg = JSON.parse env['rack.input'].read
Then just use params in the msg hash.
At least it worked for me this way.

env['rack.input'].gets
This worked for me. I found that using curl or wget to test POST requests against a Rack (v1.4.1) server required using this code as a fallback to get the request body. POST requests out in the wild (e.g. GitHub WebHooks) didn't have this same problem.

One more way to do that:
# ...
request = Rack::Request.new env
object = JSON.parse request.body.gets
# ...
See the documentation: www.rubydoc.info/gems/rack/Rack/Lint/InputWrapper

Related

Rails RestClient POST request failing with "400 Bad Request"

Looking at the docs there aren't any good examples of how to make a POST request. I need to make a POST request with a auth_token parameter and get a response back:
response = RestClient::Request.execute(method: :post,
url: 'http://api.example.com/starthere',
payload: '{"auth_token" : "my_token"}',
headers: {"Content-Type" => "text/plain"}
)
400 bad request error:
RestClient::BadRequest: 400 Bad Request
from /Users/me/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/gems/rest-client-1.8.0/lib/restclient/abstract_response.rb:74:in `return!'
from /Users/me/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/gems/rest-client-1.8.0/lib/restclient/request.rb:495:in `process_result'
from /Users/me/.rbenv/versions/2.2.3/lib/ruby/gems/2.2.0/gems/rest-client-1.8.0/lib/me/request.rb:421:in `block in transmit'
Any good examples how to make a POST request using RestClient?
EDIT:
This is how I make the request in the model:
def start
response = RestClient::Request.execute(method: :post,
url: 'http://api.example.com/starthere',
payload: '{"auth_token" : "my_token"}',
headers: {"Content-Type" => "text/plain"}
)
puts response
end
Try using a hash like this:
def start
url= 'http://api.example.com/starthere'
params = {auth_token: 'my_token'}.to_json
response = RestClient.post url, params
puts response
end
If you just want to replicate the curl request:
response = RestClient::Request.execute(method: :post, url: 'http://api.example.com/starthere', payload: {"auth_token" => "my_token"})
Both Curl and RestClient defaults to the same content type (application/x-www-form-urlencoded) when posting data the this format.
In case you land here having the same Issue, Just know that this is a common error that happens when your environment variables are not "set".
I put this in quotes because you might have set it but not available in the current terminal session!
You can check if the ENV KEY is available with:
printenv <yourenvkey>
if you get nothing then it means you need to re-add it or just put it in your bash files
FYI: Putting my ENV variables in my ~/.bash_profile fixed it

Google Drive API watch 400 error Ruby on Rails

I'm implementing the google drive api using the OAuth2 gem (ruby on rails). I'm not using the client library because I'm also integrating other API's, so I'm trying to make all these calls as modular as possible. I'm having trouble with the this request: POST https://www.googleapis.com/drive/v2/changes/watch.
I keep getting this error:
{"errors"=>[{"domain"=>"global", "reason"=>"required", "message"=>"entity.resource"}],
"code"=>400, "message"=>"entity.resource"}: { "error": { "errors": [ { "domain":
"global", "reason": "required", "message": "entity.resource" } ], "code": 400,
"message": "entity.resource" } }
which is not very useful. It may not be Google. It could be OAuth2, but I don't think so, because the debugger gets to the response after making the connection. Well, at this point, I don't know anything, so any help is appreciated. There is THIS GUY who has exactly the same error code as me, as well as the same conclusion.
Anyway, the relevant code parts:
First, the parameters I pass to OAuth2::AccessToken's post method(I need more than 10 rep to post another link, but here is the dochttp://rdoc.info/github/intridea/oauth2/ebe4be038ec14b349682/OAuth2/AccessToken#post-instance_method)(you can click on the request method to see how the params are handled)
base_url = request.protocol + request.host_with_port
channel_id = (0...50).map { ('a'..'z').to_a[rand(26)] }.join
body_post = {:id => channel_id,:type => 'web_hook',:address => base_url + "/googledrive/webhook"}
headers = {'Content-Type' => 'application/json'}
response = makeApiCall(token,"google_drive","/changes/watch","post",{},body_post,headers)
All this does is built my request with my the appropriate request body and headers for the call
Here is the relevant part from makeApiCall (at the line token.post is where the request is made, and where it breaks)
params = {"oauth_consumer_key" => ENV[key], "access_token" => token.token}.merge(params)
#body = Rack::Utils.build_query(body)
opts = {
:params => params,
:body => body,
:headers => headers
}
if(method=="get")
response = token.get(base + path,:opts => opts)
elsif(method=="post")
debugger
response = token.post(base + path,:opts => opts)
end
This is my first or second post, so forgive me if I messed anything up.
Okay, after struggling for so long, I figured out why it wasn't working. I forgot to do this:
JSON.generate(body_post)
So that my body was actually in application/json form.
Also, this line:
response = token.post(base + path,:opts => opts)
should be this:
response = token.post(base + path,opts)
simply because I'm not assigning the opts key, but the variable that I'm passing. Silly me.

How to assert HTTP request is made with correct URI when using VCR and WebMock?

I am testing a Ruby on Rails project using WebMock and VCR. I would like to assert that a request is made to the correct host, but I can't find an example of how to do that. Here's some pseudo-code for what I'm trying to do:
VCR.use_cassette('example.com request') do
subject.domain = 'example.com'
expect { subject.get('/something') }.to make_a_get_request_for('http://example.com/something')
end
Assuming your VCR is recording to YAML (the default, I believe?), this one-liner returns an array of HTTP URIs that were called during the current cassette block:
YAML.load_file(VCR.current_cassette.file)["http_interactions"].pluck("request").pluck("uri")
E.g.,
YAML.load_file(VCR.current_cassette.file)["http_interactions"].pluck("request").pluck("uri")
[
[0] "https://stackoverflow.com/wayland-project/libinput/e0008d3d/tools/libinput-debug-gui.c",
[1] "https://stackoverflow.com/wayland-project/libinput"
]
You can use an array matching method of your choice to assert the expected request URI from there.
Other members of the request object you might want to pluck instead of uri:
{
"method" => "get",
"uri" => "https://stackoverflow.com/adomain",
"body" => {
"encoding" => "US-ASCII",
"string" => ""
},
"headers" => {
"Accept" => [
...
}
}

Receiving POST with Rack ruby server

I have a simple ruby server something like :
app = Proc.new do |env|
puts 'am I receiving anything ? '
req = Rack::Request.new(env).params
puts "if yes any parameters ? : #{req.inspect}"
end
Rack::Handler::Thin.run(app, :Port => 4001, :threaded => true)
How am I supposed to receive POST request with the parameters , I'm sending some JSON object using post but I can see nothing like i'm receiving nothing when I send POST to localhost:4001 .
That's because you are not returning a response. Your response is empty so you won't see anything. You can test this through cURL:
$ curl -F 'foo=bar' localhost:4001
curl: (52) Empty reply from server
Response from within app:
am I receiving anything ?
if yes any parameters ? : {"foo"=>"bar"}
Try returning something:
app = Proc.new do |env|
puts 'am I receiving anything ? '
req = Rack::Request.new(env).params
puts "if yes any parameters ? : #{req.inspect}"
[200, { 'Content-Type' => 'text/plain' }, ['Some body']]
end

Ruby: HTTParty: can't format XML POST data correctly?

NOTE: "object" is a placeholder work, as I don't think I should be saying what the controller does specifically.
so, I have multiple ways of calling my apps API, the following works in the command line:
curl -H 'Content-Type: application/xml' -d '<object><name>Test API object</name><password>password</password><description>This is a test object</description></object>' "http://acme.example.dev/objects.xml?api_key=1234"
the above command generates the following request in the devlog:
Processing ObjectsController#create to xml (for 127.0.0.1 at 2011-07-07 09:17:51) [POST]
Parameters: {"format"=>"xml", "action"=>"create", "api_key"=>"1234", "controller"=>"objects",
"object"=>{"name"=>"Test API object", "description"=>"This is a test object", "password"=>"[FILTERED]"}}
Now, I'm trying to write tests for the actions using the API, to make sure the API works, as well as the controllers.
Here is my current (broken) httparty command:
response = post("create", :api_key => SharedTest.user_api_key, :xml => data, :format => "xml")
this command generates the following request in the testlog:
Processing ObjectsController#create to xml (for 0.0.0.0 at 2011-07-07 09:37:35) [POST]
Parameters: {
"xml"=>"<object><name><![CDATA[first post]]></name>
<description><![CDATA[Things are not as they used to be]]></description>
<password><![CDATA[WHEE]]></password>
</object>",
"format"=>"xml",
"api_key"=>"the_hatter_wants_to_have_tea1",
"action"=>"create",
"controller"=>"objects
So, as you can see, the command line command actually generates the object hash from the xml, whereas the httparty command ends up staying in xml, which causes problems for the create method, as it needs a hash.
Any ideas / proper documentation?
Current documentation says that post takes an url, and "options" and then never says what options are available
**EDIT:
as per #Casper's suggestion, my method now looks like this:
def post_through_api_to_url(url, data, api_key = SharedTest.user_api_key)
response = post("create", {
:query => {
:api_key => api_key
},
:headers => {
"Content-Type" => "application/xml"
},
:body => data
})
ap #request.env["REQUEST_URI"]
assert_response :success
return response
end
unfortunately, the assert_response fails, because the authentication via the api key fails.
looking at the very of of the request_uri, the api_key isn't being set properly... it shows:
api_key%5D=the_hatter_wants_to_have_tea1"
but it should just be equals, without the %5D (right square bracket)
I think this is how you're supposed to use it:
options = {
:query => {
:api_key => 1234
},
:headers => {
"Content-Type" => "application/xml"
},
:body => "<xmlcode>goes here</xmlcode>"
}
post("/create", options)
Forgive me for being basic about it but if you only want to send one variable as a parameter, why don't you do as Casper suggests, but just do:
post("/create?api_key=1234", options)
Or rather than testing HTTParty's peculiarities in accessing your API, perhaps write your tests using Rack::Test? Very rough example...
require "rack/test"
require "nokogiri"
class ObjectsTest < Test::Unit::TestCase
include Rack::Test::Methods
def app
MyApp.new
end
def create_an_object(o)
authorize "x", "1234" # or however you want to authenticate using query params
header 'Accept', 'text/xml'
header 'Content-Type', 'text/xml'
body o.to_xml
post "/create"
xml = Nokogiri::XML(last_response.body)
assert something_logic_about(xml)
end
end

Resources