Ruby: HTTP Put method - ruby

I am attempting to update the 'ip' parameter in a json object in an API.
I have the following case:
when "put"
uri = URI.parse("http://#{ip}:#{port}/api/v1/address_data/1.json")
jobj = Hash.new
jobj['ip'] = "1.1.1.1"
http = Net::HTTP.new(uri.hostname, uri.port)
response = http.send_request('PUT', '/api/v1/address_data/1.json', data = jobj.to_s)
end
This does not work, but this does:
curl -X PUT http://ip:port/api/v1/address_data/1.json -d "ip=1.1.1.1"
How do I more accurately translate the curl into a Put request in Ruby? I have tried several methods I've found through google searching, but none of them have had successful results.

A few things:
You're not sending JSON in the Ruby example, it's a string representation of a Ruby hash which isn't the same. You need the JSON module or similar.
In the Ruby code you're attempting to send a JSON object (which would look like {"ip":"1.1.1.1"} and in the curl example you're sending it in application/x-www-form-urlencoded format, so they're currently not equivalent.
Also I'd look at the type of data the server expects from your requests: both Ruby and curl send a request header of Content-Type: application/x-www-form-urlencoded by default, and you're expecting to send JSON. This is why the curl example works: the data format you're using and the header matches. Note the .json in the URL shouldn't really make any difference; the header takes precedence.
Your call to send_request has you picking out the data parameter as a Python-style keyword argument. Ruby doesn't do that: what you're actually doing there is assigning a local variable in-line with the call.
So try something like this:
require 'json' # put this at the top of the file
uri = URI.parse("http://#{ip}:#{port}/api/v1/address_data/1.json")
jobj = {"ip" => "1.1.1.1"}
http = Net::HTTP.new(uri.hostname, uri.port)
response = http.send_request('PUT', uri.path, JSON.dump(jobj),
{'Content-Type' => 'application/json'})
And just a friendly reminder, saying something "doesn't work" doesn't usually give enough information to people that might answer your question: try and remember to paste in error messages, stack traces, and things like that :)

Related

Ruby URI: build URI with "param[]=value" for array params

I'm trying to query an API (lichess API). And I can't get the params to be correctly taken into account, because URI formats the URL not in the expected format for array params.
The expected format is this one: curl https://explorer.lichess.ovh/lichess?ratings[]=2200&ratings[]=2500. So the ratings params is an Array of values, and the url should be using ratings[]=value.
I'm currently generating my URI like this, as per the doc:
base = "https://explorer.lichess.ovh/master"
params = {
ratings: [1600, 1800]
}
uri = URI(base)
uri.query = URI.encode_www_form(params)
But this generate the URL like this: https://explorer.lichess.ovh/master?ratings=1600&ratings=1800... which is not the expected format, and is not understood by Lichess API.
Does Ruby provide any built-in way to generate the params in the expected format, or will I need to build the URL manually (which feels like revinventing the wheel)?
Rails/ActiveSupport has a to_query method which will do what you want.
2.1.5 :010 > params.to_query
=> "ratings%5B%5D=1600&ratings%5B%5D=1800"
Also, have a look at this comment which explains why URI.encode_www_form doesn't return square brackets.

Ruby return's HTTPVersionNotSupported object

I'm trying to make a get request to a service of mine with a valid URL string (if I put it into my browser, I get the expected response). However, when I run the following function:
def dispatch_uri(url)
uri = Addressable::URI.parse(url)
http = Net::HTTP.new(uri.host, uri.port)
request = Net::HTTP::Get.new(uri.request_uri)
response = http.request(request).to_s
response
end
The response variable holds a Net::HTTPVersionNotSupported, which has no body and isn't, of course, the expected response.
What am I doing wrong and how should I address this problem?
So, the answer is simpler than I thought.
Net::HTTP is both unable to work with an UTF-8 URL or Addressable::URI, however, Addressable gives us a fantastic tool to solve this problem: normalize.
Normalize converts your UTF=8 to a codified ASCII HTML compatible string, so a working code is:
def dispatch_uri(url)
uri = URI(Addressable::URI.parse(url).normalize.to_s)
response = Net::HTTP.get(uri)
response
end
This normalized string can be used to create a standard URI object and, thus, you are able to use a regular Net::HTTP request.

Using Sinatra to Parse JSON data from url

I'm using Sinatrarb to complete a task
I need to:
Parse the data of a JSON object from a url,
Single out one of attributes of the json data and store it as a variable
Run some arithmetic on the variable
Return the result as a new variable
then post this to a new url as a new json object.
I have seen bits and pieces of information all over including information on parsing JSON data in ruby and information on open-uri but I believe it would be very valuable having someone break this down step by step as most similar solutions given to this are either outdated or steeply complex.
Thanks in advance.
Here's a simple guide. I've done the same task recently.
Let's use this JSON (put it in a file called 'simple.json'):
{
"name": "obscurite",
"favorites": {
"icecream": [
"chocolate",
"pistachio"
],
"cars": [
"ferrari",
"porsche",
"lamborghini"
]
},
"location": "NYC",
"age": 100}
Parse the data of a JSON object from a url.
Step 1 is to add support for JSON parsing:
require 'json'
Step 2 is to load in the JSON data from our new .json file:
json_file = File.read('simple.json')
json_data = JSON.parse(json_file)
Single out one of attributes of the json data and store it as a variable
Our data is in the form of a Hash on the outside (curly braces with key:values). Some of the values are also hashes ('favorites' and 'cars'). The values of those inner hashes are lists (Arrays in Ruby). So what we have is a hash of hashes, where some hashes are arrays.
Let's pull out my location:
puts json_data['location'] # NYC
That was easy. It was just a top level key/value. Let's go deeper and pull out my favorite icecream(s):
puts json_data['favorites']['icecream'] # chocolate pistachio
Now only my second favorite car:
puts json_data['favorites']['cars'][1] # porsche
Run some arithmetic on the variable
Step 3. Let's get my age and cut it down by 50 years. Being 100 is tough!
new_age = json_data['age'] / 2
puts new_age
Return the result as a new variable
Step 4. Let's put the new age back into the json
json_data['age'] = new_age
puts json_data['age'] # 50
then post this to a new url as a new json object.
Step 5. Add the ability for your program to do an HTTP POST. Add this up at top:
require 'net/http'
and then you can post anywhere you want. I found a fake web service you could use, if you just want to make sure the request got there.
# use this guy's fake web service page as a test. handy!
uri = URI.parse("http://jsonplaceholder.typicode.com/posts")
header = {'Content-Type'=> 'text/json'}
http = Net::HTTP.new(uri.host, uri.port)
request = Net::HTTP::Post.new(uri.request_uri, header)
request.body = json_data.to_json
response = http.request(request)
# Did we get something back?
puts response.body
On linux or mac you can open a localhost port and listen as a test:
nc -4 -k -l -v localhost 1234
To POST to this port change the uri to:
uri = URI.parse("http://localhost:1234")
Hope this helps. Let me know if you get stuck and I'll try to lend a hand. I'm not a ruby expert, but wanted to help a fellow explorer. Good luck.

Ruby split and parse batched HTTP Response (multipart/mixed)

I'm using the GMail API that allows me to get a batched response of multiple Gmail objects.
This comes back in the form of a multipart/mixed HTTP response with a set of separate HTTP responses separated by a boundary as defined in the header.
Each of the HTTP sub-Responses is a JSON format.
i.e.
result.response.response_headers = {...
"content-type"=>"multipart/mixed; boundary=batch_abcdefg"...
}
result.response.body = "----batch_abcdefg
<the response header>
{some JSON}
--batch_abcdefg
<another response header>
{some JSON}
--batch_abcdefg--"
Is there a library or an easy way to convert those responses from the string into a set of separate HTTP responses or JSON objects?
Thanks to Tholle above...
def parse_batch_response(response, json=true)
# Not the same delimiter in the response as we specify ourselves in the request,
# so we have to extract it.
# This should give us exactly what we need.
delimiter = response.split("\r\n")[0].strip
parts = response.split(delimiter)
# The first part will always be an empty string. Just remove it.
parts.shift
# The last part will be the "--". Just remove it.
parts.pop
if json
# collects the response body as json
results = parts.map{ |part| JSON.parse(part.match(/{.+}/m).to_s)}
else
# collates the separate responses as strings so you can do something with them
# e.g. you need the response codes
results = parts.map{ |part| part}
end
result
end

Ruby TestUnit, VCR and HTTP API Requests

I am building an API wrapper and am writing some tests for it and I have a couple of questions.
1) How do I write an assert for calls where data doesn't exist? For example, looking up a member by id using the API but the user won't exist yet.
2) How do I write an assert for testing PUT and DELETE requests?
I already have a grasp on testing GET and POST requests just not sure on the other 2 verbs.
For your question part 1...
You have a couple choices for data that doesn't exist:
You can create the data ahead of time, for example by using a test seed file, or a fixture, or a factory. I like this choice for larger projects with more sophisticated data arrangements. I also like this choice for getting things working first because it's more straightfoward to see the data.
You can create a test double, such as a stub method or fake object. I like this choice for fastest test performance and best isolation. For fastest tests, I intercept calls as early as possible. The tradeoff is that I'm not doing end-to-end testing.
For your question part 2...
You should edit your question to show your actual code; this will help people here answer you.
Is your VCR code is something like this?
VCR.use_cassette('test_unit_example') do
response = Net::HTTP.get_response('localhost', '/', 7777)
assert_equal "Hello", response.body
end
If so, you change the HTTP get to put, something like this:
uri = URI.parse(...whatever you want...)
json = "...whatever you want..."
req = Net::HTTP::Put.new(uri)
req["content-type"] = "application/json"
req.body = json
request(req)
Same for HTTP delete:
Net::HTTP::Delete.new(uri)
A good blog post is the http://www.rubyinside.com/nethttp-cheat-sheet-2940.html>Net::HTTP cheat sheet excerpted here:
# Basic REST.
# Most REST APIs will set semantic values in response.body and response.code.
require "net/http"
http = Net::HTTP.new("api.restsite.com")
request = Net::HTTP::Post.new("/users")
request.set_form_data({"users[login]" => "quentin"})
response = http.request(request)
# Use nokogiri, hpricot, etc to parse response.body.
request = Net::HTTP::Get.new("/users/1")
response = http.request(request)
# As with POST, the data is in response.body.
request = Net::HTTP::Put.new("/users/1")
request.set_form_data({"users[login]" => "changed"})
response = http.request(request)
request = Net::HTTP::Delete.new("/users/1")
response = http.request(request)

Resources