How can I past a variable in request with HTTParty? - ruby

I need to generate a value using Post and pass this value in the query and delete. How to do this?
Is it possible to pass the value of a variable directly in the def retrieve method of request get or delete?
I want to use the same value generated in the var that stores the faker gem and pass both get and delete.
require 'HTTParty'
require 'httparty/request'
require 'httparty/response/headers'
class Crud
include HTTParty
def create
##codigo = Faker::Number.number(digits: 5)
#nome = Faker::Name.first_name
#salario = Faker::Number.decimal(l_digits: 4, r_digits: 2)
#idade = Faker::Number.number(digits: 2)
#base_url = 'http://dummy.restapiexample.com/api/v1/create'
#body = {
"id":##codigo,
"name":#nome,
"salary":#salario,
"age":#idade
}.to_json
#headers = {
"Accept": 'application/vnd.tasksmanager.v2',
'Content-Type': 'application/json'
}
##request = Crud.post(#base_url, body: #body, headers: #headers)
end
def retrieve
self.class.get('http://dummy.restapiexample.com/api/v1/employee/1')
end
end

Just parse response from API and use fetched id. You don't need to pass id when create an employee, it is generated automatically
class Crud
include HTTParty
base_uri 'http://dummy.restapiexample.com/api/v1'
def create
nome = Faker::Name.first_name
salario = Faker::Number.decimal(l_digits: 4, r_digits: 2)
idade = Faker::Number.number(digits: 2)
#note, you should pass body as JSON string
body = { name: nome, salary: salario, age: idade }.to_json
headers = {
'Accept' => 'application/vnd.tasksmanager.v2',
'Content-Type' => 'application/json'
}
self.class.post('/create', body: body, headers: headers)
end
def retrieve(id)
self.class.get("/employee/#{ id }")
end
end
> client = Crud.new
> response = client.create
> id = JSON.parse(response)['id']
> client.retrieve(id)
Please, read about variables in ruby - what is the difference between local, instance and global variables. Global variables should be used in rare case, more often you need instance/local ones.

Related

How can i retrieve query params from get request

I'm kind of new to Ruby and I stuck with a fairly simple task.
I would like to pass GET parameters to Faraday's request.
Here is my request function
def request
#request ||= Faraday.new do |conn|
conn.url_prefix = BASE_URL
conn.headers = ##headers
conn.request :json
conn.response :json, parser_options: { object_class: OpenStruct }
end
Then this request method is being called like so
params = {"date_to": "2021.07.07", "date_from": "2022.01.30"}
request.get(url, params).body
I've tried to retrieve params in the request method using conn.params = #request.params
But that didn' work.
When you want to use params in the request method then you have to apss it to the method like this:
def request(params)
#request ||= Faraday.new do |conn|
conn.url_prefix = BASE_URL
conn.headers = ##headers
conn.params = params
conn.request :json
conn.response :json, parser_options: { object_class: OpenStruct }
end
And then call the methods like this:
params = { "date_to": "2021.07.07", "date_from": "2022.01.30" }
request(params).get(url).body

Ruby base_auth with net/http - undefined method `user' for String

I've got pure Ruby app where I want to create request to external API. To do so I'm using standard Ruby Net::HTTP like below:
require 'net/http'
require 'uri'
class Api
BASE_URI = 'https://staging.test.com'
WORKFLOW = 'tests'
QUIZ_PATH = "/v3/accounts/workflows/#{WORKFLOW}/conversations"
def initialize(payload:)
#payload = payload
end
def post_quiz
handle_response(Net::HTTP.post_form("#{BASE_URI}#{QUIZ_PATH}", options))
end
attr_reader :payload
private
def options
{
basic_auth: basic_auth,
body: payload.to_json,
headers: headers
}
end
def basic_auth
{
username: Settings.ln_username,
password: Settings.ln_password
}
end
def headers
{
'User-Agent' => 'Mozilla/5.0',
'Accept-Language' => 'en-US,en;q=0.5',
'Content-Type' => 'application/json'
}
end
def handle_response(response)
return response.body if response.success?
end
end
But instead of response I'm getting an error:
NoMethodError: undefined method `user' for #String:0x00007f80eef9e6f8
Did you mean? super
/Users/usr/.rvm/rubies/ruby-2.7.0/lib/ruby/2.7.0/net/http.rb:527:in `post_form'
I don't have any user there, what is it?
Net::HTTP.post_form is used to send FormData pairs - its not what you want to send JSON and it doesn't even allow you to send headers (You're actually putting them in the request body!).
If you want to send a POST request with HTTP Basic auth and custom headers and JSON body you need to create the request object manually:
require 'net/http'
require 'uri'
class Api
BASE_URI = 'https://staging.test.com'
WORKFLOW = 'tests'
QUIZ_PATH = "/v3/accounts/workflows/#{WORKFLOW}/conversations"
attr_reader :payload
def initialize(payload:)
#payload = payload
end
def post_quiz
url = URI.join(BASE_URI, QUIZ_PATH)
request = Net::HTTP::Post.new(url, headers)
request.basic_auth = Settings.ln_username, Settings.ln_password
request.body = #payload.to_json
# open a connection to the server
response = Net::HTTP.start(url.hostname, url.port, use_ssl: true) do |http|
http.request(request)
end
handle_response(response)
end
private
def headers
{
'User-Agent' => 'Mozilla/5.0',
'Accept-Language' => 'en-US,en;q=0.5',
'Content-Type' => 'application/json'
}
end
# How to respond from an API client is a whole topic in itself but a tuple or hash might
# be a better choice as it lets consumers decide what to do with the response and handle stuff like logging
# errors
def handle_response(response)
# Net::HTTP doesn't have a success? method - you're confusing it with HTTParty
case response
when Net::HTTPSuccess, Net::HTTPCreated
response.body
else
false
end
end
end
Here is the source code that raises the error:
def HTTP.post_form(url, params)
req = Post.new(url)
req.form_data = params
>> req.basic_auth url.user, url.password if url.user
start(url.hostname, url.port,
:use_ssl => url.scheme == 'https' ) {|http|
http.request(req)
}
end
From the docs:
post_form(url, params)
Posts HTML form data to the specified URI object. The form data must be provided as a Hash mapping from String to String.
That means Net::HTTP.post_form(URI("#{BASE_URI}#{QUIZ_PATH}"), options) fixes it. You are currently sending a string as url instead of a URI.

Coinbase API Request in Ruby always returns Invalid API Key

I am trying to learn how to make simple requests to the Coinbase API in Ruby. This is mostly for my own entertainment and education. The Ruby GEM is so out of date I thought I'd start working on my own system instead of relying on it. To that end I wanted to get the basics working before I tried to turn it into a gem.
Here is the beginnings of my module.
And no matter what, when I try to get /accounts I get a 401 response.
#status=401 #reason_phrase="Unauthorized" #response_body="{\"message\":\"Invalid API Key\"}
# Simple Coinbase Requests
require 'base64'
require 'openssl'
require 'json'
require 'faraday'
module Coinbase
module Request
class API
def initialize key = '', secret = '', passphrase = '', sandbox = false
#url = sandbox ? 'https://api-public.sandbox.pro.coinbase.com' : 'https://api-public.pro.coinbase.com'
#key = key
#secret = secret
#passphrase = passphrase
#content_type = 'application/json'
end
# Get Accounts
def accounts
self.get '/accounts'
end
# Do the work of a HTTP Get
def get path
timestamp = Time.now.to_i.to_s
headers = auth_headers path, '', 'GET', timestamp
connection = Faraday.new(url: 'https://api-public.sandbox.pro.coinbase.com', headers: headers, ssl: { :verify => false })
# puts connection.inspect
connection.get path
end
# Auth Headers
# CB-ACCESS-KEY The api key as a string.
# CB-ACCESS-SIGN The base64-encoded signature (see Signing a Message).
# CB-ACCESS-TIMESTAMP A timestamp for your request.
# CB-ACCESS-PASSPHRASE The passphrase you specified when
def auth_headers path = '', body = '', method = 'GET', timestamp = nil
{
'Content-Type': 'Application/JSON',
'CB-ACCESS-SIGN': self.signature(path, body, method, timestamp),
'CB-ACCESS-TIMESTAMP': timestamp,
'CB-ACCESS-KEY': #key,
'CB-ACCESS-PASSPHRASE': #passphrase
}
end
# Generate a signature
def signature path = '', body = '', method = 'GET', timestamp = nil
body = body.to_json if body.is_a?(Hash)
secret = Base64.decode64 #secret
hash = OpenSSL::HMAC.digest 'sha256', secret, "#{timestamp}#{method}#{path}#{body}"
Base64.strict_encode64 hash
end
end
end
end
I'm calling it using the following (THE KEY/SECRET/PASSPHRASE ARE FAKE for this example)
coinbase = Coinbase::Request::API.new('123426bc3a583fb8393141fb7777fake',
'+FAKEbGoG1eT1WVFWNJxFtTE/y4kIYq2Lbf6FAKEw5j2756GXgaqg5iXTsqPJXKkStZ7nPoTT2RGKwiJfRFAKE==',
'FAKEPASSPHRASE',
true)
puts coinbase.accounts.inspect
The signature "what" (as defined as "#{timestamp}#{method}#{path}#{body}" in your docs) for this simple request would be something like 1624063589GET/accounts
The headers come out to {:"Content-Type"=>"Application/JSON", :"CB-ACCESS-SIGN"=>"rs29GSZuRspthioywb5IkaHQmPIwH5DRDW5LHoYUvw8=", :"CB-ACCESS-TIMESTAMP"=>"1624063726", :"CB-ACCESS-KEY"=>"123426bc3a583fb8393141fb22328113", :"CB-ACCESS-PASSPHRASE"=>"FAKEPASSPHRASE"}
Once connected the headers come out as seen below in the faraday response object.
This is the full response object output which is what I would expect to get with fake keys as used but I get the same thing when I use the keys I generated here: https://pro.coinbase.com/profile/ap
#<Faraday::Response:0x00000001621a8e58 #on_complete_callbacks=[], #env=#<Faraday::Env #method=:get #url=#<URI::HTTPS https://api-public.sandbox.pro.coinbase.com/accounts> #request=#<Faraday::RequestOptions (empty)> #request_headers={"Content-type"=>"Application/JSON", "Cb-access-sign"=>"vEfjUnFy+3qQqRa2lxvEC5O32xOa6t7NgGAxO8OYrpo=", "Cb-access-timestamp"=>"1624063280", "Cb-access-key"=>"123426bc3a583fb8393141fb22328113", "Cb-access-passphrase"=>"FAKEPASSPHRASE", "User-Agent"=>"Faraday v1.4.2"} #ssl=#<Faraday::SSLOptions (empty)> #response=#<Faraday::Response:0x00000001621a8e58 ...> #response_headers={"date"=>"Sat, 19 Jun 2021 00:41:21 GMT", "content-type"=>"application/json; charset=utf-8", "content-length"=>"29", "connection"=>"keep-alive", "access-control-allow-headers"=>"Content-Type, Accept, cb-session, cb-fp, cb-form-factor", "access-control-allow-methods"=>"GET,POST,DELETE,PUT", "access-control-allow-origin"=>"*", "access-control-expose-headers"=>"cb-before, cb-after, cb-gdpr", "access-control-max-age"=>"7200", "cache-control"=>"no-store", "etag"=>"W/\"1d-mmRSeO9uba2rhQtGfy4YjixIkt4\"", "strict-transport-security"=>"max-age=15552000; includeSubDomains", "x-content-type-options"=>"nosniff", "x-dns-prefetch-control"=>"off", "x-download-options"=>"noopen", "x-frame-options"=>"SAMEORIGIN", "x-xss-protection"=>"1; mode=block", "cf-cache-status"=>"MISS", "cf-request-id"=>"0ac3501f6300005ae1c99c3000000001", "expect-ct"=>"max-age=604800, report-uri=\"https://report-uri.cloudflare.com/cdn-cgi/beacon/expect-ct\"", "vary"=>"Accept-Encoding", "server"=>"cloudflare", "cf-ray"=>"6618b6123ba75ae1-IAD"} #status=401 #reason_phrase="Unauthorized" #response_body="{\"message\":\"Invalid API Key\"}">>
I've been plugging away at this for days and I just can't seem to get it to not return Invalid API Key. I feel like I'm so very close.. but no cigar..
Thoughts?
NOTE: On a whim before submitting this I tried /currencies and it worked fine.
It took me some trial and error with this too, but I was hitting invalid signature errors rather than invalid API key errors. Maybe this will help anyway:
Working example:
Gemfile
source 'https://rubygems.org'
ruby '2.6.7'
gem 'httparty'
coinbase.rb
require 'base64'
require 'httparty'
API_KEY = ''
API_PASSPHRASE = ''
API_SECRET = ''
key = Base64.decode64(API_SECRET)
url = "https://api.exchange.coinbase.com"
requestPath = "/accounts"
method = "GET"
body = ""
timestamp = Time.now.to_i.to_s
message = timestamp + method + requestPath + body
hmac = OpenSSL::HMAC.digest('sha256', key, message)
signature = Base64.strict_encode64(hmac)
headers = {
'Content-Type': 'application/json',
'CB-ACCESS-SIGN': signature,
'CB-ACCESS-TIMESTAMP': timestamp,
'CB-ACCESS-KEY': API_KEY,
'CB-ACCESS-PASSPHRASE': API_PASSPHRASE
}
result = HTTParty.get(url+requestPath, headers: headers)
puts result.response.body
Run:
bundle install
bundle exec coinbase.rb

Ruby HTTP sending API key Basic_auth

I have been following a tutorial on GitHub Pages and
I am trying to pass an Apikey to a webservice as basic auth 'apiKey' => 'huda7da97hre3rhr1yrh0130409u1u' for example but I cannot work out how to implement it into the method, or even if that is the proper place for it.
I have a class called connection with my request method in it. I need to post 'apiKey' as header and not in the body. I have read the ruby docs but I cannot work out how to apply it to this specific class.
require "net/http"
require "uri"
require "ostruct"
require "json"
class Connection
ENDPOINT = "http://localhost"
APP_LOCATION = "/task_manager/v1/"
VERB_MAP = {
:get => Net::HTTP::Get,
:post => Net::HTTP::Post,
:put => Net::HTTP::Put,
:delete => Net::HTTP::Delete
}
def initialize(endpoint = ENDPOINT)
uri = URI.parse(endpoint)
#http = Net::HTTP.new(uri.host, uri.port)
end
def get(path, params)
request_json :get, path, params
end
def post(path, params)
request_json :post, APP_LOCATION + path, params
end
def put(path, params)
request_json :put, path, params
end
def delete(path, params)
request_json :delete, path, params
end
private
def request_json(method, path, params)
response = request(method, path, params)
body = JSON.parse(response.body)
OpenStruct.new(:code => response.code, :body => body)
rescue JSON::ParserError
response
end
def request(method, path, params = {})
case method
when :get
full_path = encode_path_params(path, params)
request = VERB_MAP[method.to_sym].new(full_path)
else
request = VERB_MAP[method.to_sym].new(path)
request.set_form_data(params)
end
#http.request(request)
end
def encode_path_params(path, params)
encoded = URI.encode_www_form(params)
[path, encoded].join("?")
end
end
If I post to the server using Advanced Rest Client and put the apikey in the
http://localhost/task_manager/v1/tasks?=
header
Authorization: 9c62acdda8fe12507a435345bb9b2338
and in the body
email=free%40mail.com&password=free&task=test
then I get
{
error: false
message: "Task created successfully"
task_id: 5
}
So how can I post it using this class?.
connection = Connection.new
result = connection.post("task", {'task' => 'task'})
Basic Authentication example:
req = Net::HTTP::Get.new(uri)
req.basic_auth 'user', 'pass'
http://docs.ruby-lang.org/en/trunk/Net/HTTP.html#class-Net::HTTP-label-Basic+Authentication
Or if you want to add a raw Authorization header in your request method you can do
request.add_field 'Authorization', 'huda7da97hre3rhr1yrh0130409u1u'
But basic authentication normally means that there is a user name and a password. With your API key - I am not sure you actually need basic authentication. I do not know what you API actually requires but if you have not tried it yet you can try sending the api key as an additional parameter
result = connection.post("register", {'email' => email, 'name' => name, 'password' => password, 'apiKey' => 'huda7da97hre3rhr1yrh0130409u1u' })

Ruby mechanize post with header

I have page with js that post data via XMLHttpRequest and server side script check for this header, how to send this header?
agent = WWW::Mechanize.new { |a|
a.user_agent_alias = 'Mac Safari'
a.log = Logger.new('./site.log')
}
agent.post('http://site.com/board.php',
{
'act' => '_get_page',
"gid" => 1,
'order' => 0,
'page' => 2
}
) do |page|
p page
end
I found this post with a web search (two months later, I know) and just wanted to share another solution.
You can add custom headers without monkey patching Mechanize using a pre-connect hook:
agent = WWW::Mechanize.new
agent.pre_connect_hooks << lambda { |p|
p[:request]['X-Requested-With'] = 'XMLHttpRequest'
}
ajax_headers = { 'X-Requested-With' => 'XMLHttpRequest', 'Content-Type' => 'application/json; charset=utf-8', 'Accept' => 'application/json, text/javascript, */*'}
params = {'emailAddress' => 'me#my.com'}.to_json
response = agent.post( 'http://example.com/login', params, ajax_headers)
The above code works for me (Mechanize 1.0) as a way to make the server think the request is coming via AJAX, but as stated in other answers it depends what the server is looking for, it will be different for different frameworks/js library combos.
The best thing to do is use Firefox HTTPLiveHeaders plugin or HTTPScoop and look at the request headers sent by the browser and just try and replicate that.
Seems like earlier that lambda had one argument, but now it has two:
agent = Mechanize.new do |agent|
agent.pre_connect_hooks << lambda do |agent, request|
request["Accept-Language"] = "ru"
end
end
Take a look at the documentation.
You need to either monkey-patch or derive your own class from WWW::Mechanize to override the post method so that custom headers are passed through to the private method post_form.
For example,
class WWW::Mechanize
def post(url, query= {}, headers = {})
node = {}
# Create a fake form
class << node
def search(*args); []; end
end
node['method'] = 'POST'
node['enctype'] = 'application/x-www-form-urlencoded'
form = Form.new(node)
query.each { |k,v|
if v.is_a?(IO)
form.enctype = 'multipart/form-data'
ul = Form::FileUpload.new(k.to_s,::File.basename(v.path))
ul.file_data = v.read
form.file_uploads << ul
else
form.fields << Form::Field.new(k.to_s,v)
end
}
post_form(url, form, headers)
end
end
agent = WWW::Mechanize.new
agent.post(URL,POSTDATA,{'custom-header' => 'custom'}) do |page|
p page
end

Resources