Ruby, forming API request without implicitly stating each parameter - ruby-on-rails

I'm trying to make a request to a web service (fwix), and in my rails app I've created the following initializer, which works... sorta, I have two problems however:
For some reason the values of the parameters need to have +'s as the spaces, is this a standard thing that I can accomplish with ruby? Additionally is this a standard way to form a url? I thought that spaces were %20.
In my code how can I take any of the options sent in and just use them instead of having to state each one like query_items << "api_key=#{options[:api_key]}" if options[:api_key]
The following is my code, the trouble area I'm having are the lines starting with query_items for each parameter in the last method, any ideas would be awesome!
require 'httparty'
module Fwix
class API
include HTTParty
class JSONParser < HTTParty::Parser
def json
JSON.parse(body)
end
end
parser JSONParser
base_uri "http://geoapi.fwix.com"
def self.query(options = {})
begin
query_url = query_url(options)
puts "querying: #{base_uri}#{query_url}"
response = get( query_url )
rescue
raise "Connection to Fwix API failed" if response.nil?
end
end
def self.query_url(input_options = {})
#defaults ||= {
:api_key => "my_api_key",
}
options = #defaults.merge(input_options)
query_url = "/content.json?"
query_items = []
query_items << "api_key=#{options[:api_key]}" if options[:api_key]
query_items << "province=#{options[:province]}" if options[:province]
query_items << "city=#{options[:city]}" if options[:city]
query_items << "address=#{options[:address]}" if options[:address]
query_url += query_items.join('&')
query_url
end
end
end

For 1)
You API provider is expecting '+' because the API is expecting in a CGI formatted string instead of URL formatted string.
require 'cgi'
my_query = "hel lo"
CGI.escape(my_query)
this should give you
"hel+lo"
as you expect
for Question 2) I would do something like
query_items = options.keys.collect { |key| "#{key.to_s}=#{options[key]}" }

def self.query_url(input_options = {})
options = {
:api_key => "my_api_key",
}.merge(input_options)
query_url = "/content.json?"
query_items = []
options.each { |k, v| query_items << "#{k}=#{v.gsub(/\s/, '+')}" }
query_url += query_items.join('&')
end

I'm a developer at Fwix and wanted to help you with your url escaping issue. However, escaping with %20 works for me:
wget 'http://geoapi.fwix.com/content.xml?api_key=mark&province=ca&city=san%20francisco&query=gavin%20newsom'
I was hoping you could provide me with the specific request you're making that you're unable to escape with %20.

Related

How do I manually set cookie in rails app?

I have a situation where I need to send session id in custom header because safari does not allow sending cookie from iframe.
But now, I'm having trouble setting the cookie from rack middleware.
status, headers, body = #app.call(env)
session_id = headers['X-SESSION-ID'] || headers['HTTP_X_SESSION_ID'] || headers['x-session-id'] || ''
if !headers['Cookie'].present? && session_id.present?
headers['Cookie'] = {
'_session_id': {
value: session_id,
path: '/',
httpOnly: true
}
}
end
Or is there way to manually set session id without having to fetch it from cookie?
Update 1:
Modifying rack request files does work. But, I'm not sure how to proceed with this one. If nothing works then, I might manually update this file in all servers.
def cookies
hash = #env["rack.request.cookie_hash"] ||= {}
string = #env["HTTP_COOKIE"] || "_session_id=#{#env["HTTP_X_SESSION_ID"]}"
return hash if string == #env["rack.request.cookie_string"]
hash.clear
# According to RFC 2109:
# If multiple cookies satisfy the criteria above, they are ordered in
# the Cookie header such that those with more specific Path attributes
# precede those with less specific. Ordering with respect to other
# attributes (e.g., Domain) is unspecified.
cookies = Utils.parse_query(string, ';,') { |s| Rack::Utils.unescape(s) rescue s }
cookies.each { |k,v| hash[k] = Array === v ? v.first : v }
#env["rack.request.cookie_string"] = string
hash
end
Looks like modifying the cookie method as above do not work.
Finally I managed to solve it. So, I added rack_request.rb in initializers. And here's the code for it:
require 'rack'
require 'rack/request'
require 'rack/utils'
Rack::Request.class_eval do
def cookies
hash = #env["rack.request.cookie_hash"] ||= {}
string = #env["HTTP_COOKIE"] || "_session_id=#{#env['HTTP_X_SESSION_ID']}"
unless string =~ /\s*_session_id=/i
if #env['HTTP_X_SESSION_ID'].present?
string << "; _session_id=#{#env['HTTP_X_SESSION_ID']}"
end
end
# require 'colorize'
#
# Rails.logger.info 'from cookies'.green
# Rails.logger.info (string.blue)
return hash if string == #env["rack.request.cookie_string"]
hash.clear
# According to RFC 2109:
# If multiple cookies satisfy the criteria above, they are ordered in
# the Cookie header such that those with more specific Path attributes
# precede those with less specific. Ordering with respect to other
# attributes (e.g., Domain) is unspecified.
cookies = Rack::Utils.parse_query(string, ';,') { |s| Rack::Utils.unescape(s) rescue s }
cookies.each { |k, v| hash[k] = Array === v ? v.first : v }
#env["rack.request.cookie_string"] = string
hash
end
end
And I'm sending 'X-SESSION-ID' in my ajaxHeaders for session id.

Get Input from form for API url Request in rails?

I'm new to Rails and I'm trying to make a simple weather API to get weather by zipcode
is there a way to get the zipcode from user input from a simple form, this will be just for learning so I'm not trying to make users devise, or users model
require 'net/http'
require 'json'
#url = 'http://api.openweathermap.org/data/2.5/weather?zip=#{zipcode}&appid=APIKEY'
#uri = URI(#url)
#response = Net::HTTP.get(#uri)
#output = JSON.parse(#response)
actually I figured it out, i needed to add
def zipcode
#zip_query = params[:zipcode]
if params[:zipcode] == ""
#zip_query = "Hey you forgot to enter a zipcode!"
elsif params[:zipcode]
# Do Api stuff
require 'net/http'
require 'json'
#url = 'http://api.openweathermap.org/data/2.5/weather?zip='+ #zip_query +'&appid=APIKEY'
#uri = URI(#url)
#response = Net::HTTP.get(#uri)
#output = JSON.parse(#response)
#name = #output['name']
# Check for empty return result
if #output.empty?
#final_output = "Error"
elsif !#output
#final_output = "Error"
else
#final_output = ((#output['main']['temp'] - 273.15) * 9/5 +32).round(2)
end
end
end
in the controller.rb file
and add
post "zipcode" => 'home#zipcode'
get "home/zipcode"
in the routes file
but I'm sure this is not the best practice

Converting python to ruby params issue

I'm converting some python code to ruby. It's going ok so far, except I'm running into some issues with parameters. The python code is:
def sign_policy(policy):
signed_policy = base64.b64encode(policy)
signature = base64.b64encode(hmac.new(
app.config.get('AWS_CLIENT_SECRET_KEY'), signed_policy, hashlib.sha1).
digest())
return { 'policy': signed_policy, 'signature': signature }
def sign_headers(headers):
headers = bytearray(headers, 'utf-8') # hmac doesn't want unicode
return {
'signature': base64.b64encode(hmac.new(
app.config.get('AWS_CLIENT_SECRET_KEY'), headers, hashlib.sha1).
digest())
}
def s3_signature():
request_payload = request.get_json()
if request_payload.get('headers'):
response_data = sign_headers(request_payload['headers'])
else:
response_data = sign_policy(request.data)
return jsonify(response_data)
My ruby version so far is:
def create
puts params[:headers]
if params[:headers].present?
response_data = sign_headers(params[:headers])
else
response_data = sign_policy(params)
end
render json: response_data
end
private
def sign_policy(policy)
signed_policy = Base64.encode64(policy).gsub("\n","")
signature = Base64.encode64(
OpenSSL::HMAC.digest(
OpenSSL::Digest.new('sha1'),
AWS_SECRET_KEY, signed_policy)
).gsub("\n","")
return { 'policy': signed_policy, 'signature': signature }
end
def sign_headers(headers)
#headers = bytearray(headers, 'utf-8')
return {
'signature': Base64.encode64(
OpenSSL::HMAC.digest(
AWS_SECRET_KEY, headers, OpenSSL::Digest.new('sha1')
))
}
end
I'm running into the following issue: no implicit conversion of ActionController::Parameters into String, which makes it obvious whats wrong (Params is a hash and it needs to be a string)..However, what is being passed in the python code? I'm missing what I should be passing?
Most probably you need to pass a single value, String or any other, it depends on the data type you need to pass to use Base64.encode64(policy).
As you're passing params[:headers] in the sign_headers method call, in the case of the sign_policy call, you're passing params which is the whole ActionController::Parameters, try debugging the values you sent on params to send the needed value.

AWS::S3::Errors::NoSuchKey: No Such Key error

I'm trying to create a method that deletes files on an S3 instance, but I am getting a AWS::S3::Errors::NoSuchKey: No Such Key error when I try to call .head or .read on an object.
app/models/file_item.rb
def thumbnail
{
exists: thumbnailable?,
small: "http://#{bucket}.s3.amazonaws.com/images/#{id}/small_thumb.png",
large: "http://#{bucket}.s3.amazonaws.com/images/#{id}/large_thumb.png"
}
end
lib/adapters/amazons3/accessor.rb
module Adapters
module AmazonS3
class Accessor
S3_BUCKET = AWS::S3.new.buckets[ENV['AMAZON_BUCKET']]
...
def self.delete_file(thumbnail)
prefix_pattern = %r{http://[MY-S3-HOST]-[a-z]+.s3.amazonaws.com/}
small_path = thumbnail[:small].sub(prefix_pattern, '')
large_path = thumbnail[:large].sub(prefix_pattern, '')
small = S3_BUCKET.objects[small_path]
large = S3_BUCKET.objects[large_path]
binding.pry
S3_BUCKET.objects.delete([small, large])
end
end
end
end
example url1
"http://projectname-staging.s3.amazonaws.com/images/994/small_thumb.png"
example url2
"http://projectname-production.s3.amazonaws.com/images/994/large_thumb.png"
assuming awssdk v1 for ruby.
small = S3_BUCKET.objects[small_path]
does not actually get any objects.
from: https://docs.aws.amazon.com/AWSRubySDK/latest/AWS/S3/Bucket.html
bucket.objects['key'] #=> makes no request, returns an S3Object
bucket.objects.each do |obj|
puts obj.key
end
so you would need to alter your code to something like:
to_delete = []
S3_BUCKET.objects[small_path].each do |obj|
to_delete << obj.key
end
S3_BUCKET.objects[large_path].each do |obj|
to_delete << obj.key
end
S3_BUCKET.objects.delete(to_delete)
just banged out the code, so the idea is there, you might need to correct/polish it a bit
I was able to come of with a kind of different solution thanks to your answer of #Mircea above.
def self.delete_file(thumbnail)
folder = thumbnail[:small].match(/(\d+)(?!.*\d)/)
to_delete = []
S3_BUCKET.objects.with_prefix("images/#{folder}").each do |thumb|
to_delete << thumb.key
end
# binding.pry
S3_BUCKET.objects.delete(to_delete)
end

Pretty print webservice response on ruby

I have to call a webservice but I don't know the format of the response. In any case (xml, json or html) I have to pretty print the response.
For example, if it is a xml I have to indent and show it properly. Same thing if it is a json. I have two problems here:
Detecting the format
Apply a format depending on the type.
I think that (1) is the most challenging problem.
Any help?
As several of the comments have suggested, the http header will contain the content type.
net/http has methods for this: http://ruby-doc.org/stdlib-2.0.0/libdoc/net/http/rdoc/Net/HTTP.html#method-i-head
require 'net/http'
require 'json'
require 'rexml/document'
response = nil
Net::HTTP.start('www.google.com', 80) {|http|
response = http.get('/index.html')
}
header = response['content-type'].split(';').first # => "text/html"
body = response.read_body
then you can conditionally operate:
if header == "text/html"
puts response.read_body
elsif header == "application/json"
puts JSON.pretty_generate(JSON.parse(body))
elsif header == "text/xml"
xml = REXML::Document.new body
out = ""
xml.write(out, 1)
puts out
end
Most of this was pulled form other SO posts:
pretty JSON: How can I "pretty" format my JSON output in Ruby on Rails?
pretty XML: How to beautify xml code in rails application
This is the code that I finally used:
raw_response = response.body
response_html = ''
if response.header['Content-Type'].include? 'application/json'
tokens = CodeRay.scan(raw_response, :json)
response_html = tokens.div
elsif response.header['Content-Type'].include? 'application/xml'
tokens = CodeRay.scan(raw_response, :xml)
response_html = tokens.div
elsif response.header['Content-Type'].include? 'text/html'
tokens = CodeRay.scan(raw_response, :html)
response_html = tokens.div
else
response_html = '<div>' + raw_response + '</div>'
end
It's using the coderay gem.

Resources