Sending multiple string parameters to post request in Rails - ruby-on-rails

I am using Net::HTTP::Post to send a request to a pre-determined url, like so:
my_url = '/path/to/static/url'
my_string_parameter = 'objectName=objectInfo'
my_other_string_parameter = 'otherObjectName=otherObjectInfo'
request = Net::HTTP::Post.new(my_url)
request.body = my_string_parameter
However, I know that my_url expects two string parameters. I have both parameters ready (they're statically generated) to be passed in. Is there a way to pass in multiple strings - both my_string_parameter as well as my_other_string_parameter to a post request via Ruby on Rails?
EDIT: for clarity's sake, I'm going to re-explain everything in a more organized fashion. Basically, what I have is
my_url = 'path/to/static/url'
# POST requests made to this url require 2 string parameters,
# required_1 and required_2
param1 = 'required_1=' + 'param1_value'
param2 = 'requred_2=' + 'param2_value'
request = request.NET::HTTP::Post.new(my_url)
If I try request.body = param1, then as expected I get an error saying "Required String parameter 'required_2' is not present". Same with request.body=param2, the same error pops up saying 'required_1' is not present. I'm wondering if there is a way to pass in BOTH parameters to request.body? Or something similar?

Try this.
uri = URI('http://www.example.com')
req = Net::HTTP::Post.new(uri)
req.set_form_data('param1' => 'data1', 'param2' => 'data2')
Alternative
uri = URI('http://www.example.com/')
res = Net::HTTP.post_form(uri, 'param1' => 'data1', 'param2' => 'data2')
puts res.body
For more request like Get or Patch you can refer This offical doc.

You can send it like this.
data = {'params' => ['parameter1', 'parameter2']}
request = Net::HTTP::Post.new(my_url)
request.set_form_data(data)

If your params are string:
url = '/path/to/controller_method'
my_string_parameter = 'objectName=objectInfo'
my_other_string_parameter = 'otherObjectName=otherObjectInfo'
url_with_params = "#{url}?#{[my_string_parameter, my_other_string_parameter].join('&')}"
request = Net::HTTP::Post.new(url_with_params)
If your params are hash It would be easier
your_params = {
'objectName' => 'objectInfo',
'otherObjectName' => 'otherObjectInfo'
}
url = '/path/to/controller_method'
url_with_params = "#{url}?#{your_params.to_query}"
request = Net::HTTP::Post.new(url_with_params)

Related

Why does HERE oauth2 token request api return 401300 with Rails but works fine with Postman?

When calling the HERE authentication service (https://account.api.here.com/oauth2/token) from one of the controllers of the RoR APP (Rails 5.0.6/ruby 2.6.1) I get a 401: "401300 Signature mismatch. Authorization signature or client credential is wrong"
The Key, secret, Authorization header, content type, request body etc ... are the same as the ones used by Postman.
Postman always returns a 200 OK but the rails app systematically returns "401"
Any suggestions on what the problem is?
def fetch_new_token
# URL
api_url = 'https://account.api.here.com/oauth2/token'
# VERSION
api_version='1.0'
# GRANT TYPE
api_grant_type_for_req_body='grant_type=client_credentials'
#KEY
api_access_key_id = CGI.escape(ENV['my_access_key_id'])
#SECRET
api_access_key_secret = CGI.escape(ENV['my_access_key_secret'])
#NONCE
draft_api_nonce= [('a'..'z'), ('A'..'Z')].map(&:to_a).flatten
api_nonce=(0...20).map { draft_api_nonce[rand(draft_api_nonce.length)] }.join
#TMESTAMP
api_timestamp = (Time.now).strftime('%s')
#NORMALIZED URL
api_url_normalized = CGI.escape(api_url)
#SIGNING METHOD
api_signature_method= CGI.escape('HMAC-SHA256')
#OAUTH PARAMETERS BASE STRING
api_parameters_string=('consumer_key='+api_access_key_id+'&nonce='+api_nonce+'&signature_method='+api_signature_method+'&timestamp='+api_timestamp+'&'+'version=1.0')
#ENCODED BASE STRING
api_normalized_string = 'POST&'+api_url_normalized+'&'+api_grant_type_for_req_body+CGI.escape('&'+api_parameters_string)
#SIGNNG KEY
api_signing_key = api_access_key_secret+'&'
#SIGNATURE
digest = OpenSSL::Digest.new('sha256')
api_signature = OpenSSL::HMAC.hexdigest(digest, api_normalized_string, api_signing_key)
# convert the HASHING result to a URL ENCODED base64 string.
api_signature_encoded = (Base64.strict_encode64(api_signature))
# AUTHORIZATION STRING - ESCAPED
api_authorization_string = ('OAuth consumer_key="'+api_access_key_id+'",signature_method="'+api_signature_method+'",timestamp="'+CGI.escape(api_timestamp)+'",nonce="'+CGI.escape(api_nonce)+'",version="'+CGI.escape(api_version)+'",signature="'+CGI.escape(api_signature_encoded)+'"')
# FARADAY OBJECT
connect_token_request = Faraday.new(url: 'https://account.api.here.com') do |faraday|
faraday.response :logger, nil, bodies: true
faraday.request :json
faraday.headers['Accept'] = 'application/json'
faraday.headers['Content-Type'] = 'application/x-www-form-urlencoded'
faraday.headers['Authorization'] = api_authorization_string
faraday.adapter Faraday.default_adapter
end
# FARADAY POST
response_token_request= connect_token_request.post('/oauth2/token', 'grant_type=client_credentials' )
# CHECK THE RESULT
puts response_token_request.body
#json = JSON.parse(response_token_request.body)
req_status = #json['httpStatus']
puts "The status returned in the body is:::: #{req_status}"
puts "===== ///// ======"
puts "===== ///// ======"
req_error_code = #json['errorCode']
puts "The ERROR CODE returned in the body is:::: #{req_error_code}"
end
I don't know RoR but I had the same problem in Javascript and this script solved my problem:
const axios = require('axios')
const cryptoJS = require('crypto-js');
const btoa = require('btoa');
exports.getToken = (app_key, app_secret) => {
let url = "https://account.api.here.com/oauth2/token";
let key = encodeURI(app_key);
let secret = encodeURI(app_secret);
let nonce = btoa(Math.random().toString(36)).substring(2, 13);
let timestamp = Math.floor(Date.now()/1000);
let normalizedUrl = encodeURIComponent(url);
let signing_method = encodeURI("HMAC-SHA256");
let sig_string = "oauth_consumer_key="
.concat(key)
.concat("&oauth_nonce=")
.concat(nonce)
.concat("&oauth_signature_method=")
.concat(signing_method)
.concat("&oauth_timestamp=")
.concat(timestamp)
.concat("&").concat("oauth_version=1.0");
let normalised_string = "POST&".concat(normalizedUrl).concat("&").concat(encodeURIComponent(sig_string));
let signingKey = secret.concat("&");
let digest = cryptoJS.HmacSHA256(normalised_string, signingKey);
let signature = cryptoJS.enc.Base64.stringify(digest);
let auth = 'OAuth oauth_consumer_key="'
.concat(key)
.concat('",oauth_signature_method="')
.concat(signing_method)
.concat('",oauth_signature="')
.concat(encodeURIComponent(signature))
.concat('",oauth_timestamp="')
.concat(timestamp)
.concat('",oauth_nonce="')
.concat(nonce)
.concat('",oauth_version="1.0"')
return axios({
method: 'post',
url: url,
data: JSON.stringify({grantType: "client_credentials"}),
headers: {
'Content-Type': "application/json",
'Authorization': auth
}
});
}

Bulk Item Setup endpoint on Walmart no working with JSON

I am trying to submit a feed to Walmarts API following this guide and this api documentation
In their guide it says
Send an item feed to Walmart with payload in either XML or JSON format
I am sending this JSON
{
"MPItemFeedHeader":{
"version":"1.0",
"sellingChannel":"mpsetupbymatch",
"locale":"en"
},
"MPItem":[
{
"Item":{
"productIdentifiers":{
"productId":"042666047173",
"productIdType":"UPC"
},
"ShippingWeight":2,
"price":420.69,
"sku":"RICKS-12345"
}
}
]
}
Via a POST request like so:
# Submits a feed to Walmart
# #param feed_data [Hash] data that will be submited with the feed
# #param type [String] Enum: "item" "RETIRE_ITEM" "MP_ITEM" "MP_WFS_ITEM" "MP_ITEM_MATCH" "MP_MAINTENANCE" "SKU_TEMPLATE_MAP" "SHIPPING_OVERRIDES"
def submit_feed(feed_data, type)
# To add a param to a multipart POST request you need to append the params to the URL
endpoint = "https://marketplace.walmartapis.com/v3/feeds?feedType=" + type
headers = self.api_client.headers.with_indifferent_access
uri = URI(endpoint)
request = Net::HTTP::Post.new(uri)
# Add Required Headers
request['Authorization'] = headers["Authorization"]
request['WM_SEC.ACCESS_TOKEN'] = headers["WM_SEC.ACCESS_TOKEN"]
request['WM_QOS.CORRELATION_ID'] = headers["WM_QOS.CORRELATION_ID"]
request['WM_SVC.NAME'] = headers["WM_SVC.NAME"]
request['Accept'] = headers["Accept"]
# Set form wants an array of arrays, basically the first item is the key and the second the value
request.set_form([["file", feed_data]], 'multipart/form-data')
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
http.request(request)
end
end
The feed successfully submits, but when i check the status this is the response:
{
"feedId":"6DCFAA97311140358D6D842488B24333#AQMBCgA",
"feedStatus":"ERROR",
"shipNode":null,
"submittedBy":null,
"feedSubmissionDate":1627595359155,
"ingestionErrors":{
"ingestionError":[
{
"type":"DATA_ERROR",
"code":"ERR_EXT_DATA_0801001",
"field":"IB",
"description":"Unexpected character '{' (code 123) in prolog; expected '\u003c'\n at [row,col {unknown-source}]: [1,1]"
}
]
},
"itemsReceived":0,
"itemsSucceeded":0,
"itemsFailed":0,
"itemsProcessing":0,
"offset":0,
"limit":50,
"itemDetails":{
"itemIngestionStatus":[
]
},
"additionalAttributes":null
}
Judging by the error message
Unexpected character '{' (code 123) in prolog; expected '\u003c'\n at [row,col {unknown-source}]: [1,1]
It seems like they are expecting me to be sending an XML file, i can't figure out what it is i am doing wrong.
They require that the data is sent as multipart so i can't set the Content-Type to application/json
Is there anything i am missing to tell them in the request that the payload is JSON?
I figured it out with the help of this answer on another question.
You are better off referencing this than Walmarts official documentation
You need to submit a post request to the "https://marketplace.walmartapis.com/v3/feeds endpoint appending your type on the url ?feedType=[something]
You need to make sure that your "Content-Type" is "application/json" if you are sending them JSON.
You do not need to send it multipart like the documentation suggests, just send your entire JSON file as the body and it should work correctly.
Here is my new ruby code to get this done:
# Submits a feed to Walmart
# #param feed_data [Hash] data that will be submited with the feed
# #param type [String] Enum: "item" "RETIRE_ITEM" "MP_ITEM" "MP_WFS_ITEM" "MP_ITEM_MATCH" "MP_MAINTENANCE" "SKU_TEMPLATE_MAP" "SHIPPING_OVERRIDES"
def submit_feed(feed_data, type)
# To add a param to a multipart POST request you need to append the params to the URL
endpoint = "https://marketplace.walmartapis.com/v3/feeds?feedType=" + type
uri = URI.parse(endpoint)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.post(uri.path, feed_data, self.api_client.headers)
end
If someone from Walmart is looking at this question, please improve your documentation.

How to set fake request object for rails controller object

I need to use render_to_string method outside the controller.
So I create controller instance like:
controller = class_name.constantize.new # Rails controller instance
controller.params = params
controller.action_name = 'index'
controller.create_global_current_user user
index_render_options = controller.send('index_render_options')
controller.send('render_to_string', index_render_options)
But it fails because request object inside is nil. Could you help me with it? how to create fake request for controller object?
SOLVED.
Added
controller.request = ActionDispatch::Request.new({})
resp = controller.class.make_response!(controller.request)
controller.set_response!(resp)
so now it looks like
controller = class_name.constantize.new
controller.params = params
controller.action_name = 'index'
controller.request = ActionDispatch::Request.new({})
resp = controller.class.make_response!(controller.request)
controller.set_response!(resp)
controller.create_global_current_user user
index_render_options = controller.send('index_render_options')
controller.send('render_to_string', index_render_options)
Based on this blog post the env parameter looks like
env = {
'REQUEST_METHOD' => 'GET',
'PATH_INFO' => '/hello',
'HTTP_HOST' => 'skylight.io',
# ...
}
Since Rails does not allow an empty map as the env parameter, the minimum code for creating a request object would be
request = ActionDispatch::Request.new({ "REQUEST_METHOD" => "GET" })

Rails String Replace URL Parameters

Let's say I have a string like:
url = "https://example.com/user/tr_auth.php?key=34432&cmp_id=344&tr_id={user_id}"
I want to update the cmp_id=344 to be say cmp_id=44553. What's the best way to accomplish this? I can't gsub per say because I don't know what cmp_id might be equal, only that it will be a URL parameter in the string.
It seems like I want to do something like
uri = URI.parse(url)
params = CGI.parse(uri.query)
But then, how do I re-build the string swapping out the cmp_id parameter to be 44553?
Thanks!
If you are dealing with a web application (and/or Rails as the tag seems to indicate), then you certainly have Rack available. Rack::Utils has methods to parse and build a query.
url = "https://example.com/user/tr_auth.php?key=34432&cmp_id=344&tr_id={user_id}"
uri = URI.parse(url)
query = Rack::Utils.parse_query(uri.query)
# => {"key"=>"34432", "cmp_id"=>"344", "tr_id"=>"{user_id}"}
# Replace the value
query["cmp_id"] = 44553
uri.query = Rack::Utils.build_query(query)
uri.to_s
# => "https://example.com/user/tr_auth.php?key=34432&cmp_id=44553&tr_id=%7Buser_id%7D"
Also note that Rack, by default, escapes the query.
url = "https://example.com/user/tr_auth.php?key=34432&cmp_id=344&tr_id={user_id}"
uri = URI.parse(url)
params = CGI.parse(uri.query)
params['cmp_id'] = 44553
new_str = uri.host + uri.path + '?' + params.to_query
First, you can parse the url for params:
require 'cgi'
url = 'https://example.com/user/tr_auth.php?key=34432&cmp_id=344&tr_id={user_id}'
string_params = url.split('?')[1]
hash = CGI::parse(string_params)
Then you can iterate the hash by keys and change values:
hash.keys.each {|key| hash[key]='new value'}
url_params = hash.to_param

Stubbing result of Net::HTTP.new(url.host, url.port)

I have a funciton that calls Net::HTTP.new(google_url.host, google_url.port) and I am trying to figure out how to stub the result for testing. Basically I don't want to be hitting the google URL shortener every time I run my test.
def shorten_url(long_url)
google_url = URI.parse("https://www.googleapis.com/urlshortener/v1/url")
data = JSON.generate({"longUrl" => "#{long_url}"})
header = {"Content-Type" => "application/json"}
http = Net::HTTP.new(google_url.host, google_url.port)
http.use_ssl = true
short_url = ""
res = http.request_post(google_url.path, data, header)
jsonResponse = JSON.parse(res.body)
short_url = jsonResponse["id"]
end
Basically I want to be able to set the result of that function.
I've tried things like: Net::HTTP.any_instance.stubs(:HTTP.new).returns("www.test.com") but cannot figure out how to get it to work.
class HTTP < Protocol
....
# Creates a new Net::HTTP object.
# If +proxy_addr+ is given, creates an Net::HTTP object with proxy support.
# This method does not open the TCP connection.
def HTTP.new(address, port = nil, p_addr = nil, p_port = nil, p_user = nil, p_pass = nil)
h = Proxy(p_addr, p_port, p_user, p_pass).newobj(address, port)
h.instance_eval {
#newimpl = ::Net::HTTP.version_1_2?
}
h
end
....
end
Check out webmock - this sounds like it will do exactly what you're looking for.

Resources