I need to perform a very specific request on a legacy system and discovered that during a get request the http libraries are changing any %2C back to a ,.
Same issue with net-http, httparty, faraday, and open-uri using different implementations
2.5.0 :001 > require 'net/http'
=> true
2.5.0 :002 > require 'erb'
=> true
2.5.0 :003 > link = "http://example.com?q=" + ERB::Util.url_encode("Hello, World")
=> "http://example.com?q=Hello%2C%20World"
2.5.0 :004 > uri = URI(link)
=> #<URI::HTTP http://example.com?q=Hello%2C%20World>
2.5.0 :005 > res = Net::HTTP.get_response(uri)
=> #<Net::HTTPOK 200 OK readbody=true>
All this looks good until I look at the actual request with VCR
http_interactions:
- request:
method: get
uri: http://example.com/?q=Hello,%20World
body:
encoding: US-ASCII
string: ''
...
How do I keep the request to be http://example.com?q=Hello%2C%20World?
, is a legal character in a query (extended clarification is here https://stackoverflow.com/a/31300627/3820185)
ERB::Util.url_encode instead does replace [^a-zA-Z0-9_\-.]:
# File erb.rb, line 930
def url_encode(s)
s.to_s.dup.force_encoding("ASCII-8BIT").gsub(/[^a-zA-Z0-9_\-.]/n) {
sprintf("%%%02X", $&.unpack("C")[0])
}
end
So when doing the request, most probably the query is re-parsed to conform to the actual standards.
EDIT
Also you don't need to use ERB::Util.url_encode at all, you can just pass your URL to URI, it will propery escape it according to the standards.
irb(main):001:0> require 'net/http'
=> true
irb(main):002:0> link = URI 'http://example.com?q=Hello, World'
=> #<URI::HTTP http://example.com?q=Hello,%20World>
irb(main):003:0>
Related
How to enforce Faraday adapter typhoeus to use HTTP/2 for requests to servers which supported HTTP/2?
I have tested this over service https://http2.pro/doc/api and result was like this:
body="{\"http2\":1,\"protocol\":\"HTTP\\/2.0\",\"push\":0,\"user_agent\":\"Faraday v0.12.2\"}",
\"http2\":1, what means that HTTP/2 not used for request!
There are two things at play here. The first is that the remote API is lying to you in the response body. Their documentation says:
http2: Possible values are 0 (HTTP/2 was used) and 1 (HTTP/2 was not used).
Even though the response body shows 'http2': 1 indicating that HTTP2 was not used, it is being used. You can most easily confirm this using Chrome's dev tools:
So once we know that the API is lying in the response body, how can we independently confirm that Typhoeus is using HTTP2?
(this answer assumes you are using pry as your REPL, not IRB)
First let's confirm that Typhoeus alone will use HTTP2:
require 'typhoeus'
response = Typhoeus.get("https://http2.pro/api/v1", http_version: :httpv2_0)
response.class
=> Typhoeus::Response < Object
response.body
=> "{\"http2\":1,\"protocol\":\"HTTP\\/2.0\",\"push\":0,\"user_agent\":\"Typhoeus - https:\\/\\/github.com\\/typhoeus\\/typhoeus\"}" # this is the lying API response
response.http_version
=> "2" # this is what Typhoeus tells us was actually used
Now let's test it in Faraday:
require 'faraday'
require 'typhoeus'
require 'typhoeus/adapters/faraday'
conn = Faraday.new do |faraday|
faraday.adapter :typhoeus, http_version: :httpv2_0
end
response = conn.get("https://http2.pro/api/v1")
response.body
=> "{\"http2\":1,\"protocol\":\"HTTP\\/2.0\",\"push\":0,\"user_agent\":\"Faraday v0.17.0\"}" # again we get the lying API response
But how can we confirm it was HTTP2? This doesn't work:
response.http_version
NoMethodError: undefined method `http_version' for #<Faraday::Response:0x00007f99935519a8>
Because response isn't a Typhoeus::Response object, it's a Faraday object:
response.class
=> Faraday::Response < Object
So we need to get into the gem itself to figure out where it's creating the Typhoeus::Response object so we can call .http_version on it manually and confirm it's using the protocol we expect. As it turns out, that's right here.
Let's take the easy route and stick binding.pry into our local copy of the gem (you'll need to restart pry to pick up the changes to the gem):
def typhoeus_request(env)
opts = {
:method => env[:method],
:body => env[:body],
:headers => env[:request_headers]
}.merge(#adapter_options)
binding.pry
::Typhoeus::Request.new(env[:url].to_s, opts)
end
Then re-run the request:
require 'faraday'
require 'typhoeus'
require 'typhoeus/adapters/faraday'
conn = Faraday.new do |faraday|
faraday.adapter :typhoeus, http_version: :httpv2_0
end
response = conn.get("https://http2.pro/api/v1")
And you'll see:
Frame number: 0/3
From: /Users/foo/.rvm/gems/ruby-2.6.3/gems/typhoeus-1.3.1/lib/typhoeus/adapters/faraday.rb # line 127 Faraday::Adapter::Typhoeus#typhoeus_request:
120: def typhoeus_request(env)
121: opts = {
122: :method => env[:method],
123: :body => env[:body],
124: :headers => env[:request_headers]
125: }.merge(#adapter_options)
126: binding.pry
=> 127: ::Typhoeus::Request.new(env[:url].to_s, opts)
128: end
Now enter:
response = ::Typhoeus::Request.new(env[:url].to_s, opts).run
And confirm it's a Typhoeus::Response object:
response.class
=> Typhoeus::Response < Object
And confirm it's using HTTP2:
response.http_version
=> "2"
And confirm the API response body is a dirty liar:
response.body
=> "{\"http2\":1,\"protocol\":\"HTTP\\/2.0\",\"push\":0,\"user_agent\":\"Faraday v0.17.0\"}"
And that's how you use Typhoeus as a Faraday adapter to make an HTTP2 request.
I’m using Rails 4.2.7. I have a request I set up like so:
2.3.0 :001 > url = 'http://www.mydomeina.com/results/browse.cfm?ID=4187141102&Gen=B&Begin=1&End=31&Max=31'
=> "http://www.mydomeina.com/results/browse.cfm?ID=4187141102&Gen=B&Begin=1&End=31&Max=31"
2.3.0 :002 > uri = URI(url)
=> #<URI::HTTP http://www.mydomeina.com/results/browse.cfm?ID=4187141102&Gen=B&Begin=1&End=31&Max=31>
2.3.0 :003 > req = Net::HTTP::Get.new uri
=> #<Net::HTTP::Get GET>
2.3.0 :004 > req["Referer"] = 'http://www.mydomeina.com/results/browse.cfm?ID=4187141102&Gen=B&Begin=1&End=31&Max=31'
=> "http://www.mydomeina.com/results/browse.cfm?ID=4187141102&Gen=B&Begin=1&End=31&Max=31"
I set req["Referer"] as an example of setting a header. There may be other headers in my request object. How do I figure out what they all are? This fails:
2.3.0 :009 > req.keys
NoMethodError: undefined method `keys' for #<Net::HTTP::Get GET>
Did you mean? key?
as does req.headers. How can I iterate over my request object to figure out what all the headers are? It is not an option to construct the request object in a different way then what I am doing now.
try
req.each_key{|k| puts k}
or
req.each{|k,v| puts "#{k} => #{v}"}
to see methods on an object from inside console you can always do
req.methods - Ojbect.methods
I'm currently working on a code where I'm requesting a URL with space on request path:
http://example.com/path1/path2/some details.detail?keyword=key word
I'm using rest-client gem.
url = http://example.com/path1/path2/some details.detail?keyword=key word
RestClient::Request.execute(method: :get, url: url)
But it throws an error:
URI::InvalidURIError: bad URI(is not URI?): http://example.com/path1/path2/some details.detail?keyword=key word
The documentation suggests to parse and normalize the URL first:
parsed_url = Addressable::URI.parse(url).normalize.to_str
# parsed_url is now: http://example.com/path1/path2/some%20details.detail?keyword=key%20word
RestClient::Request.execute(method: :get, url: parsed_url)
But the error still exists.
URI::InvalidURIError: bad URI(is not URI?): http://example.com/path1/path2/some details.detail?keyword=key%20word
It looks like it cannot parse the /some details.detail part of the url.
Does anyone encountered the same issue?
Try URI.encode:
$ irb
2.0.0-p247 :001 > require "uri"
=> true
2.0.0-p247 :002 > URI.encode("http://example.com/path1/path2/some details.detail?keyword=key word")
=> "http://example.com/path1/path2/some%20details.detail?keyword=key%20word"
2.0.0-p247 :003 >
Using rest-client 1.6.7 and addressable 2.3.5, on Ruby 2.0.0, this is an irb session trying to replicate your fault:
:001 > require 'rest-client'
=> true
:002 > require "addressable/uri"
=> true
:003 > url = 'http://example.com/path1/path2/some details.detail?keyword=key word'
=> "http://example.com/path1/path2/some details.detail?keyword=key word"
:004 > parsed_url = Addressable::URI.parse(url).normalize.to_str
=> "http://example.com/path1/path2/some%20details.detail?keyword=key%20word"
:005 > RestClient::Request.execute(method: :get, url: parsed_url)
RestClient::ResourceNotFound: 404 Resource Not Found: <!doctype html>
<html>
<head>
<title>Example Domain</title>
It seems to work ok according to your own solution. As far as I can tell you either missed out repeating a step whilst investigating the problem (try again, from the start, just in case), or perhaps it is a bug in an older version of one of the two gems you are using - in which case you could fix it by installing latest versions.
Im working in a small sinatra app that i want it to fetch a json file for latter use.
Using browser, i can access the json just OK, also on irb:
1.9.2p320 :001 > require 'open-uri'
=> true
1.9.2p320 :002 > metrics = open "http://foo-bar.com:8085/metrics/index.json"
=> #<File:/tmp/open-uri20130529-12715-1upc3bm>
1.9.2p320 :003 > metrics.read
=> "[\"carbon.agents.io-a.avgUpdateTime\", \"carbon.agents.io-a.cache.overflow\", \"carbon.agents.io-a.cache.queries\", \"carbon.agents.io-a.cache.queues\", \"carbon.agents.io-a.cache.size\", \"carbon.agents.io-a.committedPoints\", \"carbon.agents.io-a.cpuUsage\", \"carbon.agents.io-a.creates\", \"carbon.agents.io-a.errors\", \"carbon.agents.io-a.memUsage\" ...
it returns me the desired file.
But when i try to do the same from sinatra_app.rb:
get '/json' do
#all_metrics = open #graphite_all_metrics
erb :json
end
or
get '/json' do
#all_metrics = Net::HTTP.get_response(URI #graphite_all_metrics)
erb :json
end
returns me a 502 bad gateway error.
Any help?
How i get #graphite_all_metrics:
#graphite_base = "http://foo-bar.com:8085/"
#graphite_all_metrics = [#graphite_base, "/metrics/index.json"].join
Your join
#graphite_all_metrics = [#graphite_base, "/metrics/index.json"].join
is adding a double slash to the URL. Use
#graphite_all_metrics = File.join(#graphite_base, "/metrics/index.json")
or just remove one of the slashes from your example.
Most browsers/programs will fix double slash errors, but there could be some issue with your server setup.
Solved. It was a misconfigured multi-domain dns. Nothing with ruby.
I want to do a XMLHttpRequest POST in Ruby. I don't want to use a framework like Watir. Something like Mechanize or Scrubyt would be fine. How can I do this?
Mechanize:
require 'mechanize'
agent = Mechanize.new
agent.post 'http://www.example.com/', :foo => 'bar'
Example with 'net/http', (ruby 1.9.3):
You only have to put an additional header for the XMLHttpRequest to your POST-request (see below).
require 'net/http'
require 'uri' # convenient for using parts of an URI
uri = URI.parse('http://server.com/path/to/resource')
# create a Net::HTTP object (the client with details of the server):
http_client = Net::HTTP.new(uri.host, uri.port)
# create a POST-object for the request:
your_post = Net::HTTP::Post.new(uri.path)
# the content (body) of your post-request:
your_post.body = 'your content'
# the headers for your post-request (you have to analyze before,
# which headers are mandatory for your request); for example:
your_post['Content-Type'] = 'put here the content-type'
your_post['Content-Length'] = your_post.body.size.to_s
# ...
# for an XMLHttpRequest you need (for example?) such header:
your_post['X-Requested-With'] = 'XMLHttpRequest'
# send the request to the server:
response = http_client.request(your_post)
# the body of the response:
puts response.body
XMLHTTPRequest is a browser concept, but since you're asking about Ruby, I assume all you want to do is simulate such a request from a ruby script? To that end, there's a gem called HTTParty which is very easy to use.
Here's a simple example (assuming you have the gem - install it with gem install httparty):
require 'httparty'
response = HTTParty.get('http://twitter.com/statuses/public_timeline.json')
puts response.body, response.code, response.message, response.headers.inspect