Struggling a bit with faraday. I would like to know what I actually send to the server. I get the response body, but not access the request body. I found that there is a request.env method, but I can't get access to the body there somehow.
How would that work?
conn = Faraday.new(:url => 'http://sushi.com') do |faraday|
faraday.request :url_encoded # form-encode POST params
faraday.response :logger # log requests to STDOUT
faraday.adapter Faraday.default_adapter # make requests with Net::HTTP
end
data = conn.post do |req|
req.url '/nigiri'
req.headers['Content-Type'] = 'application/json'
req.body = '{ "name": "Unagi" }'
end
# how do I get access to the request body here?
What I tried doing was this:
[4] pry(main)> request.env.request
=> #<struct Faraday::RequestOptions
params_encoder=nil,
proxy=nil,
bind=nil,
timeout=nil,
open_timeout=nil,
boundary=nil,
oauth=nil>
But I have no access to the body. Any ideas?
Thanks!
You could try implementing a middleware just for this purpose. Just to give you a quick idea on what you can do to achieve this (there might be an easier way but I don't really know, I suppose because you specify the request body there's no real need to capture this as you should already have this available).
require 'faraday'
class RequestBody < Faraday::Middleware
def call(env)
request_body = env.body
#app.call(env).on_complete do |response|
response[:request_body] = request_body
end
end
end
conn = Faraday.new(:url => 'http://sushi.com') do |faraday|
faraday.use RequestBody
faraday.adapter Faraday.default_adapter
end
data = conn.post do |req|
req.url '/nigiri'
req.headers['Content-Type'] = 'application/json'
req.headers['foo'] = 'bar'
req.body = '{ "name": "Unagi" }'
end
# 2.2.2 > data.env[:request_body]
# => "{ \"name\": \"Unagi\" }"
# 2.2.2 > data.env.request_headers
# => {"User-Agent"=>"Faraday v0.9.2", "Content-Type"=>"application/json", "foo"=>"bar"}
# 2.2.2 > data.body
# => "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\n<html><head>\n<title>301 Moved Permanently</title>\n</head><body>\n<h1>Moved Permanently</h1>\n<p>The document has moved here.</p>\n<hr>\n<address>Apache/2.4.10 (Unix) OpenSSL/1.0.1e-fips mod_bwlimited/1.4 PHP/5.4.32 Server at sushi.com Port 80</address>\n</body></html>\n"
Related
I've got pure Ruby app where I want to create request to external API. To do so I'm using standard Ruby Net::HTTP like below:
require 'net/http'
require 'uri'
class Api
BASE_URI = 'https://staging.test.com'
WORKFLOW = 'tests'
QUIZ_PATH = "/v3/accounts/workflows/#{WORKFLOW}/conversations"
def initialize(payload:)
#payload = payload
end
def post_quiz
handle_response(Net::HTTP.post_form("#{BASE_URI}#{QUIZ_PATH}", options))
end
attr_reader :payload
private
def options
{
basic_auth: basic_auth,
body: payload.to_json,
headers: headers
}
end
def basic_auth
{
username: Settings.ln_username,
password: Settings.ln_password
}
end
def headers
{
'User-Agent' => 'Mozilla/5.0',
'Accept-Language' => 'en-US,en;q=0.5',
'Content-Type' => 'application/json'
}
end
def handle_response(response)
return response.body if response.success?
end
end
But instead of response I'm getting an error:
NoMethodError: undefined method `user' for #String:0x00007f80eef9e6f8
Did you mean? super
/Users/usr/.rvm/rubies/ruby-2.7.0/lib/ruby/2.7.0/net/http.rb:527:in `post_form'
I don't have any user there, what is it?
Net::HTTP.post_form is used to send FormData pairs - its not what you want to send JSON and it doesn't even allow you to send headers (You're actually putting them in the request body!).
If you want to send a POST request with HTTP Basic auth and custom headers and JSON body you need to create the request object manually:
require 'net/http'
require 'uri'
class Api
BASE_URI = 'https://staging.test.com'
WORKFLOW = 'tests'
QUIZ_PATH = "/v3/accounts/workflows/#{WORKFLOW}/conversations"
attr_reader :payload
def initialize(payload:)
#payload = payload
end
def post_quiz
url = URI.join(BASE_URI, QUIZ_PATH)
request = Net::HTTP::Post.new(url, headers)
request.basic_auth = Settings.ln_username, Settings.ln_password
request.body = #payload.to_json
# open a connection to the server
response = Net::HTTP.start(url.hostname, url.port, use_ssl: true) do |http|
http.request(request)
end
handle_response(response)
end
private
def headers
{
'User-Agent' => 'Mozilla/5.0',
'Accept-Language' => 'en-US,en;q=0.5',
'Content-Type' => 'application/json'
}
end
# How to respond from an API client is a whole topic in itself but a tuple or hash might
# be a better choice as it lets consumers decide what to do with the response and handle stuff like logging
# errors
def handle_response(response)
# Net::HTTP doesn't have a success? method - you're confusing it with HTTParty
case response
when Net::HTTPSuccess, Net::HTTPCreated
response.body
else
false
end
end
end
Here is the source code that raises the error:
def HTTP.post_form(url, params)
req = Post.new(url)
req.form_data = params
>> req.basic_auth url.user, url.password if url.user
start(url.hostname, url.port,
:use_ssl => url.scheme == 'https' ) {|http|
http.request(req)
}
end
From the docs:
post_form(url, params)
Posts HTML form data to the specified URI object. The form data must be provided as a Hash mapping from String to String.
That means Net::HTTP.post_form(URI("#{BASE_URI}#{QUIZ_PATH}"), options) fixes it. You are currently sending a string as url instead of a URI.
I'm creating a sample script to hit a client API. Client shared a .pkr public key for encrypting request body while making an API call. I didn't find any gems supporting it. Any thoughts on how to get it done?
Here is the sample script, where I need request_body_params to be encrypted using a .pkr public key.
require 'faraday'
require 'json'
conn = Faraday.new(:url => 'https://XXXXXX.com') do |faraday|
faraday.response :logger
faraday.adapter Faraday.default_adapter
end
request_body_params = '{
"token":"XXXXXX",
"person_id":XXXXXX,
"meetingStatus":"X"
}'
response = conn.post do |req|
req.url '/XXXXXX'
req.headers['Content-Type'] = 'application/json'
req.body = request_body_params
end
I have a Ruby script that issues a GET request to a restful API, but it ignores the params that I'm trying to pass in. I want to just get the activated users but it returns all of the users.
Am I not passing in my params correctly? This is my script:
require 'net/http'
require 'net/https'
require 'time'
require 'api-auth'
require 'json'
URL = 'https://<instance name>.mingle-api.thoughtworks.com/api/v2/users.xml'
OPTIONS = {:access_key_id => '<sign in name>', :access_secret_key => '<secret key>'}
PARAMS = {:user => { :activated => true } }
def http_get(url, options={}, params)
uri = URI.parse(url)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
body = params.to_json
request = Net::HTTP::Get.new(uri.request_uri)
request.body = body
request['Content-Type'] = 'application/json'
request['Content-Length'] = body.bytesize
ApiAuth.sign!(request, options[:access_key_id], options[:access_secret_key])
response = http.request(request)
users = response.body
if response.code.to_i > 300
raise StandardError, <<-ERROR
Request URL: #{url}
Response: #{response.code}
Response Message: #{response.message}
Response Headers: #{response.to_hash.inspect}
Response Body: #{response.body}
ERROR
end
puts users
end
http_get(URL, OPTIONS, PARAMS)
The response is XML of users, formatted like this:
<user>
<id type="integer">2228</id>
<name>NAME</name>
<login>example#example.com</login>
<email>example#example.com</email>
<light type="boolean">false</light>
<icon_path nil="true"></icon_path>
<activated type="boolean">true</activated>
<admin type="boolean">false</admin>
</user>
I'm still a beginner when it comes to coding, so any help is greatly appreciated! Thank you!
You are putting the parameters for the get request in the body, when you should be placing them in the URL, so that the end of the URL looks something like this:
?param1=value1¶m2=value2
Use a function like this (source):
require 'uri'
def hash_to_query(hash)
return URI.encode(hash.map{|k,v| "#{k}=#{v}"}.join("&"))
end
When you create the URI:
uri = URI.parse("#{url}?#{hash_to_query(params)}")
Passing the params via the URL did not work for me.
I ended up pulling the data from the XML that was returned instead of narrowing the search. This returns the correct data:
require 'net/http'
require 'net/https'
require 'time'
require 'api-auth'
require 'json'
require 'nokogiri'
URL = 'https://<instance name>.mingle-api.thoughtworks.com/api/v2/users.xml'
OPTIONS = {:access_key_id => '<sign in name>', :access_secret_key => '<secret key>'}
def http_get(url, options={})
uri = URI.parse(url)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri.request_uri)
ApiAuth.sign!(request, options[:access_key_id], options[:access_secret_key])
response = http.request(request)
users = response.body
if response.code.to_i > 300
raise StandardError, <<-ERROR
Request URL: #{url}
Response: #{response.code}
Response Message: #{response.message}
Response Headers: #{response.to_hash.inspect}
Response Body: #{response.body}
ERROR
end
return users
end
def extract_active_users
all_users = Nokogiri::XML(http_get(URL, OPTIONS))
all_users.search('//user').each do |user|
active_user = user.xpath('activated')
if active_user.text == 'true'
puts user
end
end
end
extract_active_users
In Ruby, how can I get hold of the HTTP Request Headers that will be sent by a net/http(s) or open-uri request BEFORE it actually makes the request.
In some cases, headers are used when creating a signed string in a URI. Surely there is some way to acquire the request headers that will be sent. These should include the "Host:" header for example.
see http://ruby-doc.org/stdlib-2.0/libdoc/net/http/rdoc/Net/HTTP.html#label-Setting+Headers
Works well in ruby 2.0.0 - but you are correct, different behavior in 1.9.3
Ruby 2.0.0
require 'net/http'
uri = URI('http://github.com/ruby')
http_request = Net::HTTP::Get.new(uri)
http_request.each_header { |header| puts header }
# => accept-encoding
# => accept
# => user-agent
# => host
http_response = Net::HTTP.start(uri.hostname, uri.port) do |http|
http.request(http_request)
end
Ruby 1.9.3
require 'uri'
require 'net/http'
uri = URI.parse('http://github.com/ruby')
http_request = Net::HTTP::Get.new(uri.path)
http_request.each_header { |header| puts header }
# => accept
# => user-agent
http_response = Net::HTTP.start(uri.hostname, uri.port) do |http|
http.request(http_request)
end
http_request.each_header { |header| puts header }
# => accept
# => user-agent
# => host
The Net::HTTP classes all seem to use Net::HTTPHeader as a mixin. You should be able to use to_hash() on the request object to get all headers at once, or each_header() / each() to iterate one header at a time.
Here's some code I wrote to help.
def get_request_headers(request)
http_method = request.method
path = request.path
request.to_hash.merge("method" => [http_method]).merge("path" => [path])
end
So now, you can run something like this.
url = URI("http://www.google.com")
request, response = Net::HTTP.start(uri(ico).host) do |http|
request = Net::HTTP::Get.new(uri(ico))
response = http.request request
[request, response]
end
get_request_headers(request)
=> {"accept-encoding"=>["gzip;q=1.0,deflate;q=0.6,identity;q=0.3"], "accept"=>["*/*"], "user-agent"=>["Ruby"], "host"=>["www.google.com"], "method"=>["GET"], "path"=>["/"]}
request.to_hash give us a few headers for free, but there's more information stored in the instance variables for the request and response classes.
With the following code, you can check if there's anything else you'd like to merge into the basic request hash.
request.instance_variables.each do |variable|
puts "#{variable}: #{request.instance_variable_get(variable)}"
end
=> #method: GET
=> #request_has_body: false
=> #response_has_body: true
=> #uri: http://www.google.com
=> #path: /
=> #decode_content: true
=> #header: {"accept-encoding"=>["gzip;q=1.0,deflate;q=0.6,identity;q=0.3"], "accept"=>["*/*"], "user-agent"=>["Ruby"], "host"=>["www.google.com"]}
=> #body:
=> #body_stream:
=> #body_data:
=> [:#method, :#request_has_body, :#response_has_body, :#uri, :#path, :#decode_content, :#header, :#body, :#body_stream, :#body_data]
Note that I've pulled out the method and path for the get_request_headers method.
Finally, you can do the same for the response.
def get_response_headers(response)
code = response.code
http_version = response.http_version
message = response.message
response.to_hash.merge("code" => [code]).merge("http_version" => [http_version]).merge("message" => [message])
end
get_response_headers(response)
=> {"date"=>["Thu, 06 May 2021 14:34:27 GMT"], "expires"=>["-1"], "cache-control"=>["private, max-age=0"], "content-type"=>["text/html; charset=ISO-8859-1"], "p3p"=>["CP=\"This is not a P3P policy! See g.co/p3phelp for more info.\""], "server"=>["gws"], "content-length"=>["6067"], "x-xss-protection"=>["0"], "x-frame-options"=>["SAMEORIGIN"], "set-cookie"=>["NID=215=dYowhmNSD9_CnKYLtsFI3uWVGy8ca8PKJTE8VY6_92q7tU5Y_AOWLsaabXxlSPBjc2QjOr4xXVX5SGMCrccTCnBR9pbdsKkrpVTV5TMqrV6H09ChxGjBr5mHVdZkgjOxswiXu72TF3eAX0uhXqloDb-5gmZ6NJ4w1YDKQKNoDp4; expires=Fri, 05-Nov-2021 14:34:27 GMT; path=/; domain=.google.com; HttpOnly"], "code"=>["200"], "http_version"=>["1.1"], "message"=>["OK"]}
How do I send a JSON request in ruby? I have a JSON object but I dont think I can just do .send. Do I have to have javascript send the form?
Or can I use the net/http class in ruby?
With header - content type = json and body the json object?
uri = URI('https://myapp.com/api/v1/resource')
body = { param1: 'some value', param2: 'some other value' }
headers = { 'Content-Type': 'application/json' }
response = Net::HTTP.post(uri, body.to_json, headers)
require 'net/http'
require 'json'
def create_agent
uri = URI('http://api.nsa.gov:1337/agent')
http = Net::HTTP.new(uri.host, uri.port)
req = Net::HTTP::Post.new(uri.path, 'Content-Type' => 'application/json')
req.body = {name: 'John Doe', role: 'agent'}.to_json
res = http.request(req)
puts "response #{res.body}"
rescue => e
puts "failed #{e}"
end
HTTParty makes this a bit easier I think (and works with nested json etc, which didn't seem to work in other examples I've seen.
require 'httparty'
HTTParty.post("http://localhost:3000/api/v1/users", body: {user: {email: 'user1#example.com', password: 'secret'}}).body
This works on ruby 2.4 HTTPS Post with JSON object and the response body written out.
require 'net/http' #net/https does not have to be required anymore
require 'json'
require 'uri'
uri = URI('https://your.secure-url.com')
Net::HTTP.start(uri.host, uri.port, :use_ssl => uri.scheme == 'https') do |http|
request = Net::HTTP::Post.new(uri, 'Content-Type' => 'application/json')
request.body = {parameter: 'value'}.to_json
response = http.request request # Net::HTTPResponse object
puts "response #{response.body}"
end
real life example, notify Airbrake API about new deployment via NetHttps
require 'uri'
require 'net/https'
require 'json'
class MakeHttpsRequest
def call(url, hash_json)
uri = URI.parse(url)
req = Net::HTTP::Post.new(uri.to_s)
req.body = hash_json.to_json
req['Content-Type'] = 'application/json'
# ... set more request headers
response = https(uri).request(req)
response.body
end
private
def https(uri)
Net::HTTP.new(uri.host, uri.port).tap do |http|
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
end
end
end
project_id = 'yyyyyy'
project_key = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
url = "https://airbrake.io/api/v4/projects/#{project_id}/deploys?key=#{project_key}"
body_hash = {
"environment":"production",
"username":"tomas",
"repository":"https://github.com/equivalent/scrapbook2",
"revision":"live-20160905_0001",
"version":"v2.0"
}
puts MakeHttpsRequest.new.call(url, body_hash)
Notes:
in case you doing authentication via Authorisation header set header req['Authorization'] = "Token xxxxxxxxxxxx" or http://api.rubyonrails.org/classes/ActionController/HttpAuthentication/Token.html
A simple json POST request example for those that need it even simpler than what Tom is linking to:
require 'net/http'
uri = URI.parse("http://www.example.com/search.json")
response = Net::HTTP.post_form(uri, {"search" => "Berlin"})
I like this light weight http request client called `unirest'
gem install unirest
usage:
response = Unirest.post "http://httpbin.org/post",
headers:{ "Accept" => "application/json" },
parameters:{ :age => 23, :foo => "bar" }
response.code # Status code
response.headers # Response headers
response.body # Parsed body
response.raw_body # Unparsed body
It's 2020 - nobody should be using Net::HTTP any more and all answers seem to be saying so, use a more high level gem such as Faraday - Github
That said, what I like to do is a wrapper around the HTTP api call,something that's called like
rv = Transporter::FaradayHttp[url, options]
because this allows me to fake HTTP calls without additional dependencies, ie:
if InfoSig.env?(:test) && !(url.to_s =~ /localhost/)
response_body = FakerForTests[url: url, options: options]
else
conn = Faraday::Connection.new url, connection_options
Where the faker looks something like this
I know there are HTTP mocking/stubbing frameworks, but at least when I researched last time they didn't allow me to validate requests efficiently and they were just for HTTP, not for example for raw TCP exchanges, this system allows me to have a unified framework for all API communication.
Assuming you just want to quick&dirty convert a hash to json, send the json to a remote host to test an API and parse response to ruby this is probably fastest way without involving additional gems:
JSON.load `curl -H 'Content-Type:application/json' -H 'Accept:application/json' -X POST localhost:3000/simple_api -d '#{message.to_json}'`
Hopefully this goes without saying, but don't use this in production.
The net/http api can be tough to use.
require "net/http"
uri = URI.parse(uri)
Net::HTTP.new(uri.host, uri.port).start do |client|
request = Net::HTTP::Post.new(uri.path)
request.body = "{}"
request["Content-Type"] = "application/json"
client.request(request)
end
data = {a: {b: [1, 2]}}.to_json
uri = URI 'https://myapp.com/api/v1/resource'
https = Net::HTTP.new uri.host, uri.port
https.use_ssl = true
https.post2 uri.path, data, 'Content-Type' => 'application/json'
Using my favourite http request library in ruby:
resp = HTTP.timeout(connect: 15, read: 30).accept(:json).get('https://units.d8u.us/money/1/USD/GBP/', json: {iAmOne: 'Hash'}).parse
resp.class
=> Hash