Output Raw HTTP Request without Sending in Ruby - ruby

I am trying to setup a POST request to a rest api using ruby. What I want to do is to output the raw HTTP request without actually sending the request. I have looked at HTTParty and Net:HTTP, but it seems the only way to output the request is only once you send the request. So basically I want a convenient way for creating an HTTP request string without actually having to send it.

The HTTParty.get and similar methods methods are helper functions that wraps a lot of the internal complexity; you should just peek inside the method to find that HTTParty.get to find that inside it it just makes a call to perform_request:
def get(path, options={}, &block)
perform_request Net::HTTP::Get, path, options, &block
end
and peeking into perform_request, we get that it just constructs a Request object and call perform on it:
def perform_request(http_method, path, options, &block) #:nodoc:
options = default_options.merge(options)
process_headers(options)
process_cookies(options)
Request.new(http_method, path, options).perform(&block)
end
You should take a look into the Request class.

Take a look at Typhoeus
request = Typhoeus::Request.new(
"www.example.com",
method: :post,
body: "this is a request body",
params: { field1: "a field" },
headers: { Accept: "text/html" }
)
It allows you to create the request and then you can run it or not with
request.run

Related

Mocking a post request with binary data in Sinatra

I have an endpoint in my Sinatra application that will be receiving binary data as part of the body. The other application sending it data will have a Faraday request that looks like this:
connection = Faraday.new(url: "https://example.com/post_data") do |conn|
conn.request :multipart
conn.adapter :net_http
conn.headers['Content-Type'] = 'octet/stream'
end
#response ||= connection.post do |req|
req.params = { :host => host,
:verification => "false"}
req.body = my_base64_encoded_binary
end
Then, in Sinatra, I will have an endpoint that receives those request parameters and binary data and passes it along to a model, like so:
post '/post_data' do
request.body.rewind
payload = request.body.read
raise Sinatra::NotFound unless payload and params[:host]
output = MyOutput.new(params, payload)
content_type 'application/json'
body output.data
end
When I try to test this endpoint using Rack::Test helpers, I end up in a weird situation. I can't seem to create the proper mock in order to test this endpoint properly. Based on some manual testing with PostMan, I'm pretty certain my endpoint works properly. It's just the test that's holding me up. Here is the spec:
it "should return a json response" do
post(url, :host => host, :verification => "false") do |req|
req.body = [my_binary]
req.content_type = "application/octet-stream"
end
expect(last_response.status).to eq(200)
expect(last_response.content_type).to eq("application/json")
end
And when I inspect what the incoming request looks like in the test, it does not contain a proper body. params is properly set to the host and verification settings I set, but the body is also being set to the params (inspected through payload = request.body.read) instead of the binary.
Is there a different way to set up the mock so that the binary properly is set to the body of the request, and the parameters are still set to the request parameters properly?
The answer is that the only way to post the body is where I was adding the params in the rack test helper. I needed to take the params and move them into the query string of the URL in the request, and then only add the binary as the body of the post request in the test helper.
let(:url) { "http://example.com/post_data?host=>#{host}&verification=#{verification}" }
it "should return a json response" do
post(url, my_binary)
expect(last_response.status).to eq(200)
expect(last_response.content_type).to eq("application/json")
end

Faraday Connection: Switching the request mode?

I am using faraday to handle some requests to an internal API. The API endpoints use a CSRF token, so I am also using faraday-cookie_jar.
For a couple of the API endpoints, they require a :multipart request. Others do not.
Question
Is there any way to utilize the same Connection object, but switch whether or not you are doing a :multipart or a :url_encoded request?
Currently, I have to use two connections depending on which type of request I'm making. It does not seem you can change a connection's request method after it has done at least 1 request.
#connection = Faraday.new(url: 'http://localhost:3000') do |faraday|
faraday.request :url_encoded
faraday.use :cookie_jar
faraday.adapter Faraday.default_adapter
end
#connection.get '/something'
# try to change to :multipart
#connection.request :multipart # => Faraday::RackBuilder::StackLocked: can't modify middleware stack after making a request
It doesn't seem to allow you to switch after you've made a request. I know that you can modify the request a bit for each request itself by passing a block, but I can't seem to find where to modify to switch to :multipart in it.
Thanks in advance.
You'll want to include both middleware options when creating your connection (:url_encoded and :multipart), and then switch using explicit Content-Type headers.
Per the Faraday ReadMe, you can specify 'em both. An excerpt:
Faraday.new(...) do |conn|
# POST/PUT params encoders:
conn.request :multipart
conn.request :url_encoded
conn.adapter :net_http
end
This request middleware setup affects POST/PUT requests in the following way:
Request::Multipart checks for files in the payload, otherwise leaves everything untouched;
Request::UrlEncoded encodes as "application/x-www-form-urlencoded" if not already encoded or of another type.
So adding :multipart still allows for url-encoded posts, because it only matters when there are files in the payload. Then, if you explicitly set your Content-Type for the file upload, you should be fine - Faraday will use the correct request middleware because you explicitly told it to use multipart. But if you don't specify, it will default to url-encoded.
# works using :url_encoded
#connection.post '/something' do |req|
req.body = { some: 'posted fields' }
end
# works using :multipart because Content-Type was explicitly set
#connection.post '/some_file_endpoint' do |req|
req.headers['Content-Type'] = 'multipart/form-data'
req.body = { file_field: Faraday::UploadIO.new(file_path, 'text/plain') }
end
Calling #connection.request :multipart adds Faraday::Request::Multipart to #connecton.builder.handlers. If you want to remove something, you can manipulate that array.
I make no claim that messing with Faraday's (relative) internals is a good idea. Keeping your two connections sounds like a better plan.
Sean’s answer sounds like the right thing to do here, but if you do want to modify your connection, you have to duplicate it before:
#connection = #connection.dup
#connection.request :multipart
This can also be used to delete middleware from a connection:
#connection = #connection.dup
#connection.builder.delete(Faraday::Request::lookup_middleware(:multipart))

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.

Net::HTTPNotFound (404) For API Call. Paste in the URL into browser and 200

While using the Wistia API, I've encountered this strange phenomena: 200 in browser, 404 from HTTParty.
Currently, I have a video module with an embed class as so:
module Video
class Embed
include ActiveModel::Model
include HTTParty
base_uri 'https://fast.wistia.net/oembed'
format :json
default_params api_password: ENV['WISTIA_PASSWORD']
class << self
def find(hashed_id)
get("", query: { url: url_query("#{hashed_id}") })
end
private
def url_query(hashed_id)
"http%3A%2F%2Fhome%2Ewistia%2Ecom%2Fmedias%2F#{hashed_id}"
end
end
end
end
When performing a Video::Embed.find(#video.hashed_id) the code that is output appears as so in the log:
HTTP GET (59.97ms) https://fast.wistia.net:443/oembed?api_password=not_getting_my_password&url=http%3A%2F%2Fhome%2Ewistia%2Ecom%2Fmedias%2Fth3-3Xamp13
Response status Net::HTTPNotFound (404)
Response body
But, if I paste in my url, I can see the JSON load inside of the browser (200).
The password is correct (tested), the hashed_id is correct (tested), and the URL was tested by pasting in the url into the browser.
I have a feeling its a cross-origin issue, but even then I'm not sure how to solve that one.
Update: Performing a curl returns the JSON just like the browser.

Request headers in Goliath middleware

I am writing own middleware for Goliath server.
How can I get request headers in "call" method?
Thanks!
"call" method always return [status_code, headers, body] tuple, see example below:
class AwesomeMiddleware
include Goliath::Rack::AsyncMiddleware
def call(env)
status, headers, response = super(env)
[status, headers, response]
end
end
Also checkout AsyncMiddleware and SimpleAroundwareFactory in Goliath repository.

Resources