Suppose I have an endpoint like so:
get "/foo" do
request.body.rewind
data = request.body.read
# some logic goes here
end
How do I test this in Sinatra?
If I try:
it "gets /foo" do
payload = {:bar => "baz"}
get "/foo", payload
end
the payload gets sent as get parameters and not as request.body.
I am not able to change the endpoint to accept params instead of request.body at this point.
Looking at the code, you can specify it in env with key :input, like so:
get "/foo", {}, {:input => JSON.generate(payload)}
assuming that the input is in json.
Related
I'm using opal-jquery to send an ajax request to my backend, but the json payload is malformed when I try to access it on the backend.
When I do puts #params.to_s on my back end, it gets displays as:
{"{\"username\":\"some_user\", \"password\":\"some_password\"}"=>nil}
So to get the login details I have to do:
#params.each do |k,v|
login_details = JSON.parse(k)
end
My front end (opal) submit request looks like this:
def handle_login_submit details
url = "/client/api/auth"
HTTP.post url, payload: details.to_json, dataType: 'json' do |response|
case response.status_code / 100
when 2
puts response.status_code
self.login_state = :return_success
puts response.json
when 4
self.login_state = :return_failed
puts response.status_code
puts response.json
when 5
self.f_message = {type: :error, message: "There was a server error" }
self.login_state = :return_failed
end
end
end
I definitely know that details is correctly formatted so that's not an issue.
I guess in the end, it boils down to is whether it's
opal-jquery sending the post request incorrectly
sinatra improperly parsing the request.
I'm not sure which one it is, but I'm inclined to believe it's the former because it was working fine before I switched to using react.rb for the front end.
After following the advice from this, specifically I'm doing:
before do
request.body.rewind
req = request.body.read
puts req
puts JSON.parse(req)
end
I was able to see that the request arrived at the server correctly and as expected. I don't know why but sinatra was mangling the response in such a away that #params came out as
{"{\"username\":\"some_user\", \"password\":\"some_password\"}"=>nil}
so, in the end I'm doing
before do
request.body.rewind
#request_payload = JSON.parse(request.body.read)
request.body.rewind
end
To read out the request body before sinatra gets anything and just reading out the params I need from #request_payload.
I'm using the following code to make a request and follow redirects:
require 'faraday'
require 'faraday_middleware'
conn = Faraday.new() do |f|
f.use FaradayMiddleware::FollowRedirects, limit: 5
f.adapter Faraday.default_adapter
end
resp = conn.get('http://www.example.com/redirect')
resp.status
This code outputs 200 because it followed the redirect, which is great. But is there anyway to know if a redirect existed or not? something like resp.redirected which is set to true if a redirect was followed or false if no redirect was followed?
I didn't see anything obvious in the FollowRedirects code.
Will I need to write my own custom middleware if I want to know this? Does anyone know of middleware out there that might do this already?
I found a solution. You can pass a callback to FaradayMiddleware::FollowRedirects. The callback should live in a hash that the FollowRedirects takes a second parameter. Since we have to use the use function for middleware you can pass the hash as a second parameter to that function.
redirects_opts = {}
# Callback function for FaradayMiddleware::FollowRedirects
# will only be called if redirected to another url
redirects_opts[:callback] = proc do |old_response, new_response|
# you can pull the new redirected URL with this line of code.
# since you have access to the new url you can make a variable or
# instance vairable to keep track of the current URL
puts 'new url', new_response.url
end
#base_client = Faraday.new(url: url, ssl: { verify: true, verify_mode: 0 }) do |c|
c.request :multipart
c.request :url_encoded
c.response :json, content_type: /\bjson$/
c.use FaradayMiddleware::FollowRedirects, redirects_opts //<- pass hash here
c.adapter Faraday.default_adapter
end
Actually, I think I just found the answer based on the post here: https://stackoverflow.com/a/20818142/4701287
I need to compare the original url i passed in with the resulting url. Extending my example from above:
original_url = 'http://www.example.com/redirect'
resp = conn.get(original_url)
was_redirected = (original_url == resp.to_hash[:url].to_s)
I am using thin to receive HTTP POST requests, my server code is this:
http_server = proc do |env|
# Want to make response dependent on content
response = "Hello World!"
[200, {"Connection" => "close", "Content-Length" => response.bytesize.to_s}, [response]]
end
Setting a breakpoint, I can see that I have received the content-type (json), and content length, but can't see the actual content. How can I retrieve the content from the request for processing?
You need to use the rack.input entry of the env object. From the Rack Spec:
The input stream is an IO-like object which contains the raw HTTP POST data. When applicable, its external encoding must be “ASCII-8BIT” and it must be opened in binary mode, for Ruby 1.9 compatibility. The input stream must respond to gets, each, read and rewind.
So you can call read on it like this:
http_server = proc do |env|
json_string = env['rack.input'].read
json_string.force_encoding 'utf-8' # since the body has ASCII-8BIT encoding,
# but we know this is json, we can use
# force_encoding to get the right encoding
# parse json_string and do your stuff
response = "Hello World!"
[200, {"Connection" => "close", "Content-Length" => response.bytesize.to_s}, [response]]
end
I am trying to serialized an AMF body and send it with RestClient.post.
From the Charles proxy, I can deserialized my request body and show it as follows:
# s is the raw binary from request body
pp RocketAMF::Envelope.new.populate_from_stream(s).messages
However, I cannot figure it out how to serialize such an object and send it (with RestClient.post) in the body.
You'll want to change the URL it's using, but the below is the correct way to do it.
require 'rubygems'
require 'RocketAMF'
require 'rest-client'
data = [] # whatever data you want
env = RocketAMF::Envelope.new :amf_version => 3
env.messages << RocketAMF::Message.new('BatchController.authenticate_iphone', '/1', data)
res = RestClient.post "http://localhost:9292/amf", env.to_s, :content_type => 'application/x-amf'
puts RocketAMF::Envelope.new.populate_from_stream(res).inspect
I am sending a multidimensional array using the post method given by the Rack::Test like
post "#{url}.json",:data => [["Company","Website"],["this is the dummy text, with,comma","www.technology.com"],["some company","www.url.com"]]
But in my controller when check my params params[:data] i am receiving it as a single dimensioned array ["Company", "Website", "this is the dummy text, with comma", "www.technology.com", "some company", "www.url.com"]
But i want it to retain it's property as a multi-dimensional array. I need help to do this.
The Rack::Test methods all have the same signature... and the second param is a hash of params
i.e.
post '/path', params={}, rack_env={}
This is because they're just URL params - which are typical key/value structures (i.e. a hash)
Why do you need it to be a multi-dimensional array?
EDIT: oh, I get it - you have a single hash with one key (:data)
If it's still causing you grief you could explicitly call to_param in there
ruby-1.9.2-p180 :003 > h = {:data => [["Company","Website"],["this is the dummy text, with,comma","www.technology.com"],["some company","www.url.com"]]}
=> {:data=>[["Company", "Website"], ["this is the dummy text, with,comma", "www.technology.com"], ["some company", "www.url.com"]]}
ruby-1.9.2-p180 :004 > h.to_param
=> "data[][]=Company&data[][]=Website&data[][]=this+is+the+dummy+text%2C+with%2Ccomma&data[][]=www.technology.com&data[][]=some+company&data[][]=www.url.com"
A workaround if you really need nested arrays is to change the request content type to JSON:
post url, JSON.dump([[1, 2], [3, 4]]), { "CONTENT_TYPE" => "application/json" }
This will correctly send a nested array through to the rack app.
Neither of the above worked too well for me but this did (see my original answer here).
The problem is body is sent as application/x-www-form-urlencoded by default, which doesn't handle multi-dimensional arrays too well. You can send it as application/json, but Sinatra probably won't merge the data into the request params. I use a middleware from rack-contrib which parses a json body from a POST request and merges it for you:
# Gemfile
`gem 'rack-contrib'`
then config.ru:
require 'rack/contrib'
require './app'
use Rack::PostBodyContentTypeParser
run Sinatra::Application
This won't be used in testing by default, but you can specify it:
# spec_helper.rb
OUTER_APP = Rack::Builder.parse_file("config.ru").first
module RSpecMixin
include Rack::Test::Methods
def app
OUTER_APP # typically this might just be Sinatra::Application
end
end
RSpec.configure do |config|
config.include RSpecMixin
end
And example usage:
it 'is ok' do
post '/', { key: 'value' }.to_json, { 'CONTENT_TYPE' => 'application/json' }
expect(last_response).to be_ok
end