HTTP PUT request with Ruby Sinatra - ruby

I created a body for a PUT request with Ruby. When I print out the body, I don't see any problems. But when I try to print out the body from the actual PUT request, I start getting an error.
To elaborate, here is my code:
#data={param1: "nameserver",
param2: {code: "XYZ", name: "NAME", start: "2017"}}
puts "data = #{#data} " #This works fine
#putResponse = HTTParty.put(base_url,
:body => #data.to_json,
:headers => header)
puts "putResponse.body is #{#putResponse.body}" #This is where I get the error
So as you can see, the line puts "data = #{#data} " works fine. It prints out
data = {:param1=>"nameserver", :param2=>{:code=>"XYZ", :name=>"NAME", :start=>"2017"}}
But the line puts puts "putResponse.body is #{#putResponse.body}" doesn't work. This is what it prints out:
putResponse.body is {"errors":[{"message":"The specified resource does not exist."}],"error_report_id":443}
So what's the problem here? (I'm using HTTParty to make my PUT request)
EDIT:
Here's how I got the host and header:
config = JSON.parse(File.read('config.json'))
puts "config: #{config}"
access_token = config['canvas']['access_token']
puts "access_token: #{access_token}"
host = config['canvas']['host']
puts "host: #{host}"
base_url = 'http://#{host}/api/v1/users/self/custom_data/program_of_study'
puts "base_url: #{base_url}"
header = {'Authorization': 'Bearer ' "#{$access_token}", "Content-Type" => 'application/json', 'Accept' => 'application/json'}

Dealing with PUT in Sinatra, is similar to dealing with POST - this is why the documentation may be scarce in this aspect.
This is a simple example of a Sinatra endpoint that receives PUT arguments from a curl request:
# server.rb
require 'sinatra'
put '/' do
params.inspect
end
And test it with this curl command:
$ curl -X PUT -d n=hello http://localhost:4567/
The params hash will be available to you inside any Sinatra endpoint, including parameters received by any HTTP method.
In response to your comment, it is hard to debug without seeing your entire code.
I suggest you run the tests provided in this answer, and gradually modify it to fit your actual code, to understand what breaks.
With the above server.rb running, the below test works without errors:
# test.rb
require 'httparty'
data = {
param1: "nameserver",
param2: { code: "XYZ", name: "NAME", start: "2017" }
}
response = HTTParty.put 'http://localhost:4567/', body: data
puts response.body
# => {"param1"=>"nameserver", "param2"=>{"code"=>"XYZ", "name"=>"NAME", "start"=>"2017"}}

Related

How to use standard library to parse URL params in Ruby?

I am using the following code with "uri" and "CGI" to parse the params of the URL:
require 'socket'
require 'uri'
require 'CGI'
server = TCPServer.new 8888
while session = server.accept
request = session.gets
p "request", request
url = "http://somewebsite.com" + request.sub("GET ", "").sub(" HTTP/1.1", "").gsub(/(\r|\n)/, "")
uri = URI(url)
params = CGI.parse(uri.query)
p "params", params
session.print "HTTP/1.1 200\r\n" # 1
session.print "Content-Type: text/html\r\n" # 2
session.print "\r\n" # 3
session.print "Hello world! The time is #{Time.now}" #4
session.close
end
I had to "make up" a full URL by adding the http://somewebsite.com to the path, and use uri and CGI functions to do it. If the browser uses http://localhost:8888/?a=123&b=hello then it works well. But if the browser tried to access http://localhost:8888/favicon.ico or http://localhost:8888 then it broke right away, saying cannot split. (failing at CGI.parse(uri.query))
I can change the line to
params = uri.query ? CGI.parse(uri.query) : nil
but the whole thing seems a bit hacky, needing to make up a URL and then CGI.parse would break if query doesn't exist. Is there actually a better way to use standard library to do it? (should something else be used instead of uri and CGI?)
(Using standard library has the advantage of automatically handling the cases for %20 and multiple params as in http://localhost:8888/?a=123&b=hello%20world&b=good, giving
{"a"=>["123"], "b"=>["hello world", "good"]}
as the result.)
Why do you need to make up anything? You don't need a full URI to use CGI.parse. Something like this should work:
require 'socket'
require 'CGI'
server = TCPServer.new 8888
while session = server.accept
request = session.gets
method, full_path = request.split(' ')
path, params = full_path.split('?')
params = CGI.parse(params.gsub('?','')) if params
session.print "HTTP/1.1 200\r\n" # 1
session.print "Content-Type: text/html\r\n" # 2
session.print "\r\n" # 3
session.print "Hello world! The time is #{Time.now}" #4
session.print "\nparams: #{params}"
p "params:", params
session.close
end
You could also just use Rack if you don't want to learn about reinventing the CGI wheel. Rack is not technically part of the standard library but is as close as you get.
# Gemfile.rb
gem 'rack'
run $ bundle install.
# application.rb
class Application
# This is the main entry point for Rack
# #param env [Hash]
# #return [Array] status, headers, body
# #see https://www.rubydoc.info/gems/rack/Rack/Request
# #see https://www.rubydoc.info/gems/rack/Rack/Response
def self.call(env)
request = Rack::Request.new(env)
Rack::Response.new(
"Hello " + request.params["name"] || "World",
200,
{ "Content-Type" => "text/plain" }
).finish
end
end
request.params is a hash that contains query string parameters and parameters from the request body for POST requests.
# config.ru
require 'rack'
require_relative 'application'
run Application
Run $ rackup to start the server.

Post Request To Slack Endpoint Failing

I have making a post request in Ruby to a slack endpoint and its failing, here is my request, not sure what I'm missing:
#!/usr/bin/env ruby
#Notification Script Test
def send_slack_message
slack_rooms = [ '#test_channel_notify' ]
slack_token_file = (File.join(ENV['HOME'], '.slack_api_token'))
slack_api_token = (File.open(#slack_token_file).readlines)[0].chomp
msg = 'This is a test message send'
slack_url = "https://slack.com/api/chat.postMessage"
%x{curl -k -X POST -d"token=#{slack_api_token}\&channel=#{slack_rooms}\&text=#{msg}" '#{slack_url}'}
end
send_slack_message
I am getting the following error, not sure what I'm missing:
./cap2.rb:7:in `initialize': no implicit conversion of nil into String (TypeError)
from ./cap2.rb:7:in `open'
from ./cap2.rb:7:in `send_slack_message'
from ./cap2.rb:13:in `<main>'
I am a ruby novice so I may be missing everything would love some help!
The error says that you can't give nil to File.open. Make sure #slack_token_file exists and is not nil.
The slack API wants to receive the payload in this format: 'payload={"json": "data"}'
Using Net::HTTP you can make a POST request like this:
require 'net/http'
require 'uri'
def payload(message, channel)
{ channel: channel, username: 'your-username', text: message, icon_emoji: ':robot_face:' }
end
msg = 'This is a test message send'
body = payload(msg, '#test_channel_notify').to_json
url = URI("https://slack.com/api/chat.postMessage")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
request = Net::HTTP::Post.new(url)
request.body = "payload=#{body}"
http.request(request)
While your original problem has been solved, I will provide an alternative solution which follows the more common practice of storing secret credentials as environment variables, not as files (which can, for example, more easily exfiltrated by accident, committed to source control, pasted to a presentation, etc).
Set SLACK_TOKEN in your environment, and then use:
#!/usr/bin/env ruby
#Notification Script Test
def send_slack_message
slack_rooms = [ '#test_channel_notify' ]
slack_api_token = ENV['SLACK_TOKEN']
msg = 'This is a test message send'
slack_url = "https://slack.com/api/chat.postMessage"
%x{curl -k -X POST -d"token=#{slack_api_token}\&channel=#{slack_rooms}\&text=#{msg}" '#{slack_url}'}
end
send_slack_message

Cannot make HTTP Delete request with Ruby's net/http library

I've been trying to make an API call to my server to delete a user record help on a dev database. When I use Fiddler to call the URL with the DELETE operation I am able to immediately delete the user record. When I call that same URL, again with the DELETE operation, from my script below, I get this error:
{"Message":"The requested resource does not support http method 'DELETE'."}
I have changed the url in my script below. The url I am using is definitely correct. I suspect that there is a logical error in my code that I haven't caught. My script:
require 'net/http'
require 'json'
require 'pp'
require 'uri'
def deleteUserRole
# prepare request
url= "http://my.database.5002143.access" # dev
uri = URI.parse(url)
request = Net::HTTP::Delete.new(uri.path)
http = Net::HTTP.new(uri.host, uri.port)
# send the request
response = http.request(request)
puts "response: \n"
puts response.body
puts "response code: " + response.code + "\n \n"
# parse response
buffer= response.body
result = JSON.parse(buffer)
status= result["Success"]
if status == true
then puts "passed"
else puts "failed"
end
end
deleteUserRole
It turns out that I was typing in the wrong command. I needed to change this line:
request = Net::HTTP::Delete.new(uri.path)
to this line:
request = Net::HTTP::Delete.new(uri)
By typing uri.path I was excluding part of the URL from the API call. When I was debugging, I would type puts uri and that would show me the full URL, so I was certain the URL was right. The URL was right, but I was not including the full URL in my DELETE call.
if you miss the parameters to pass while requesting delete, it won't work
you can do like this
uri = URI.parse('http://localhost/test')
http = Net::HTTP.new(uri.host, uri.port)
attribute_url = '?'
attribute_url << body.map{|k,v| "#{k}=#{v}"}.join('&')
request = Net::HTTP::Delete.new(uri.request_uri+attribute_url)
response = http.request(request)
where body is a hashmap where you can define query params as a hashmap.. while sending request it can be joined in the url by the code above.
ex:body = { :resname => 'res', :bucket_name => 'bucket', :uploaded_by => 'upload' }

Hash/string gets escaped

This is my hyperresource client:
require 'rubygems'
require 'hyperresource'
require 'json'
api = HyperResource.new(root: 'http://127.0.0.1:9393/todos',
headers: {'Accept' => 'application/vnd.127.0.0.1:9393/todos.v1+hal+json'})
string = '{"todo":{"title":"test"}}'
hash = JSON.parse(string)
api.post(hash)
puts hash
The hash output is: {"todo"=>{"title"=>"test"}}
At my Sinatra with Roar API I have this post function:
post "/todos" do
params.to_json
puts params
#todo = Todo.new(params[:todo])
if #todo.save
#todo.extend(TodoRepresenter)
#todo.to_json
else
puts 'FAIL'
end
end
My puts 'params' over here gets: {"{\"todo\":{\"title\":\"test\"}}"=>nil}
I found out, these are 'escaped strings' but I don't know where it goes wrong.
EDIT:
I checked my api with curl and postman google extension, both work fine. It's just hyperresource I guess
You are posting JSON, ergo you either need to register a Sinatra middleware that will automatically parse incoming JSON requests, or you need to do it yourself.
require 'rubygems'
require 'hyperresource'
require 'json'
api = HyperResource.new(root: 'http://127.0.0.1:9393/todos',
headers: {'Accept' => 'application/vnd.127.0.0.1:9393/todos.v1+hal+json'})
string = '{"todo":{"title":"test"}}'
hash = JSON.parse(string)
api.post({:data => hash})
puts hash
---
post "/todos" do
p = JSON.parse(params[:data])
puts p.inspect
#todo = Todo.new(p[:todo])
if #todo.save
#todo.extend(TodoRepresenter)
#todo.to_json
else
puts 'FAIL'
end
end
Should do what you need.

Net::HTTP Proxy list

I understand that you could use proxy in the ruby Net::HTTP. However, I have no idea how to do this with a bunch of proxy. I need the Net::HTTP to change to another proxy and send another post request after every post request. Also, is it possible to make the Net::HTTP to change to another proxy if the previous proxy is not working? If so, how?
Code I'm trying to implement the script in:
require 'net/http'
sleep(8)
http = Net::HTTP.new('URLHERE', 80)
http.read_timeout = 5000
http.use_ssl = false
path = 'PATHHERE'
data = '(DATAHERE)'
headers = {
'Referer' => 'REFERER HERE',
'Content-Type' => 'application/x-www-form-urlencoded; charset=UTF-8',
'User-Agent' => '(USERAGENTHERE)'}
resp, data = http.post(path, data, headers)
# Output on the screen -> we should get either a 302 redirect (after a successful login) or an error page
puts 'Code = ' + resp.code
puts 'Message = ' + resp.message
resp.each {|key, val| puts key + ' = ' + val}
puts data
end
Given an array of proxies, the following example will make a request through each proxy in the array until it receives a "302 Found" response. (This isn't actually a working example because Google doesn't accept POST requests, but it should work if you insert your own destination and working proxies.)
require 'net/http'
destination = URI.parse "http://www.google.com/search"
proxies = [
"http://proxy-example-1.net:8080",
"http://proxy-example-2.net:8080",
"http://proxy-example-3.net:8080"
]
# Create your POST request_object once
request_object = Net::HTTP::Post.new(destination.request_uri)
request_object.set_form_data({"q" => "stack overflow"})
proxies.each do |raw_proxy|
proxy = URI.parse raw_proxy
# Create a new http_object for each new proxy
http_object = Net::HTTP.new(destination.host, destination.port, proxy.host, proxy.port)
# Make the request
response = http_object.request(request_object)
# If we get a 302, report it and break
if response.code == "302"
puts "#{proxy.host}:#{proxy.port} responded with #{response.code} #{response.message}"
break
end
end
You should also probably do some error checking with begin ... rescue ... end each time you make a request. If you don't do any error checking and a proxy is down, control will never reach the line that checks for response.code == "302" -- the program will just fail with some type of connection timeout error.
See the Net::HTTPHeader docs for other methods that can be used to customize the Net::HTTP::Post object.

Resources