Im trying to reproduce the following request using httparty:
curl --verbose --data '{"username":"my_user#mail.com", "password":"my_password"}' http://my_page/api/user/authenticate
This is what I'm doing:
class RequestParty
include HTTParty
def initialize(base_uri)
self.class.base_uri base_uri
end
def post(endpoint, options)
self.class.post(endpoint, options)
end
end
RequestParty.new("http://my_page/api").post("/user/authenticate",{"username" => "my_user#mail.com", "password" =>"my_password"})
But I keep getting 500 Internal Server Error.
Whay am I doing wrong?
Related
I'm using Ruby on Rails 5 and I need to execute the following command in my application:
curl -F 'client_id=126581840734567' -F 'client_secret=678ebe1b3b8081231aab27dff738313' -F 'grant_type=authorization_code' -F 'redirect_uri=https://uri.com/' -F 'code=AQBi4L2Ohy3Q_N3V48OygFm0zb3gEsL985x5TIyDTNDJaLs93BwXiT1tyGYWoCg1HlBDU7ZRjUfLL5HVlzw4G-7YkVEjp6Id2WuqOz0Ylt-k2ADwDC5upH3CGVtHgf2udQhLlfDnQz5NPsnmxjg4bW3PJpW5FaQs8fn1ztgYp-ssfAf6IRt2-sI45ZC8cqqr5K_12y0Nq_Joh0H-tTfVyNLKatIxHPCqRDb3tfqgmxim1Q' https://api.instagram.com/oauth/access_token
so that it returns something like:
{"access_token": "IGQVJYS0k8V6ZACRC10WjYxQWtyMVRZAN8VXamh0RVBZAYi34RkFlOUxXZnTJsbjlEfnFJNmprQThmQ4hTckpFUmJEaXZAnQlNYa25aWURnX3hpO12NV1VMWDNMWmdIT3FicnJfZAVowM3VldlVWZAEViN1ZAidHlyU2VDMUNuMm2V", "user_id": 17231445640157812}
Is there a way to make Rails execute those types of commands? I was trying the following:
uri = URI.parse('https://api.instagram.com/oauth/access_token')
http = Net::HTTP.new(uri.host, uri.port)
request = Net::HTTP::Post.new(uri.request_uri)
request.set_form_data({
"client_id" => "126581840734567",
"client_secret" => "678ebe1b3b8081231aab27dff738313",
"grant_type" => "authorization_code",
"redirect_uri" => "http://nace.network/",
"code" => params[:code]
})
res = Net::HTTP.start(uri.hostname, uri.port) do |http|
http.request(request)
end
but I get the following error:
end of file reached
in this line:
res = Net::HTTP.start(uri.hostname, uri.port) do |http|
http.request(request)
end
You're using HTTPS, so you need to add this to your code:
Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
res = http.request(request)
end
But if you don't need persistent connections, you could also use this:
res = Net::HTTP.post_form(uri,
"client_id" => "126581840734567",
"client_secret" => "678ebe1b3b8081231aab27dff738313",
"grant_type" => "authorization_code",
"redirect_uri" => "http://nace.network/",
"code" => params[:code]
)
Also, you could consider using a library like Faraday, which is a lot easier to deal with.
Edit
This is from TinMan's comment below, sound points.
Using cURL from inside Ruby or Rails is extremely valuable. There is an incredible amount of functionality inside cURL that isn't implemented in Rails or Ruby; Even Ruby's HTTP clients have a hard time replicating it, so cURL is very acceptable depending on the needs of the application. And, depending on the application, because cURL is in compiled C, it could easily outrun pure Ruby clients.
Curl is a means of issuing HTTP (or HTTPs) requests from the command line.
You don't want to use CURL in Rails. You want to issue HTTP requests from within Rails. Using curl is okay, it's one way to issue HTTP requests from with Rails.
We can refine that down further to, you want to issue HTTP requests from Ruby. Narrowing/distilling down to the most basic version of the problem is always good to do.
We knew all this already probably - still worth writing down for us all to benefit from!
Use HTTP in Ruby
We want to use a HTTP Client. There are many but, for this I'm going to use Faraday (a gem) 'cause I like it.
You've made a good start with Ruby's built in NET:HTTP but I prefer Faraday's DSL. It results in more readable and extendable code.
So, here is a class! I barely tested this so, use as a starting point. Make sure you write some unit tests for it.
# This is a Plain Old Ruby Object (PORO)
# It will work in Rails but, isn't Rails specific.
require 'faraday' # This require is needed as it's a PORO.
class InstagramOAuth
attr_reader :code
# The code parameter will likely change frequently, so we provide it
# at run time.
def initialize(code)
#code = code
end
def get_token
connection.get('/oauth/access_token') do |request|
request.params[:code] = code
end
end
private
def connection
#connection ||= Faraday.new(
url: instagram_api_url,
params: params,
ssl: { :ca_path => https_certificate_location }
)
end
def instagram_api_url
#url ||= 'https://api.instagram.com'
end
# You need to find out where these are for your self.
def https_certificate_location
'/usr/lib/ssl/certs'
end
def params
# These params likely won't change to often so we set a write time
# in the class like this.
{
client_id: '126581840734567',
client_secret: '678ebe1b3b8081231aab27dff738313',
grant_type: 'authorization_code',
redirect_uri: 'https://uri.com/'
}
end
end
# How do we use it? Like so
# Your big old authorisation code from your question
code = 'AQBi4L2Ohy3Q_N3V48OygFm0zb3gEsL985x5TIyDTNDJaLs93BwXiT1tyGYWoCg1HlBDU'\
'7ZRjUfLL5HVlzw4G-7YkVEjp6Id2WuqOz0Ylt-k2ADwDC5upH3CGVtHgf2udQhLlfDnQz'\
'5NPsnmxjg4bW3PJpW5FaQs8fn1ztgYp-ssfAf6IRt2-sI45ZC8cqqr5K_12y0Nq_Joh0H'\
'-tTfVyNLKatIxHPCqRDb3tfqgmxim1Q'
# This will return a Faraday::Response object but, what is in it?
response = InstagramOAuth.new(code).get_token
# Now we've got a Hash
response_hash = response.to_hash
puts 'Request made'
puts "Request full URL: #{response_hash[:url]}"
puts "HTTP status code: #{response_hash[:status]}"
puts "HTTP response body: #{response_hash[:body]}"
When I ran the snippet above I got the following. The class works, you just need to tweak the request params until you get what you want. Hopefully the class demonstrates how to send HTTP requests in Ruby/Rails.
Request made
Request full URL: https://api.instagram.com/oauth/access_token?client_id=126581840734567&client_secret=678ebe1b3b8081231aab27dff738313&code=AQBi4L2Ohy3Q_N3V48OygFm0zb3gEsL985x5TIyDTNDJaLs93BwXiT1tyGYWoCg1HlBDU7ZRjUfLL5HVlzw4G-7YkVEjp6Id2WuqOz0Ylt-k2ADwDC5upH3CGVtHgf2udQhLlfDnQz5NPsnmxjg4bW3PJpW5FaQs8fn1ztgYp-ssfAf6IRt2-sI45ZC8cqqr5K_12y0Nq_Joh0H-tTfVyNLKatIxHPCqRDb3tfqgmxim1Q&grant_type=authorization_code&redirect_uri=https%3A%2F%2Furi.com%2F
HTTP status code: 405
HTTP response body:
Additional Reading
. https://lostisland.github.io/faraday/usage/
. https://github.com/lostisland/faraday/wiki/Setting-up-SSL-certificates
I spent one day trying several approaches, but still haven't quite got there so decided to ask now...
I have a Rails 5 app which is mainly a JSON API (using the actual JSON API specs), but also a "normal" Rails app with transactional emails and account related pages (reset password, etc).
What I'd like to achieve is that Rails always returns a JSON response with some meaningful error response to all API calls, rather than the default HTML error page or a header only 400 error.
The main cases I'm trying to handle are JSON parsing issues and Ruby exceptions (500 errors).
I tried:
using rescue_from on the ActionController level – seems the framework handles these exceptions before they would reach the controller
Handling them on the Rack level with a middleware – this worked in test but not in dev despite setting consider_all_requests_local to false in both
Registering a new Mime-type and a parser as JSON API Resources gem does it – looked promising, but the parser code is never hit
I'm really at my wit's end, something which sounded so simple ended up being deceptively complicated with me trying to hunt down where are these exceptions get handled in the framework without much success...
Well I managed to work it out in the end, so thought I should share what worked.
What I missed before is that I had to fiddle a bit with MIME types so that Rails would understand and properly use JSON API:
config/initializers/mime_types.rb
JSON_API_MIME_TYPES = %w[
application/vnd.api+json
text/x-json
application/json
].freeze
Mime::Type.unregister :json
Mime::Type.register 'application/json', :json, JSON_API_MIME_TYPES
Mime::Type.register_alias 'application/json', :json, %i[json_api jsonapi]
After this I could finally handle 500 errors in the base controller:
rescue_from StandardError,
with: :render_standard_error
def render_standard_error
render json: {
status: 500,
error: 'Unhandled error',
message: 'An unexpected error occurred.'
}, status: :internal_server_error
end
Then for handling JSON parse errors, this was the solution:
app/middleware/catch_json_parse_errors
class CatchJsonParseErrors
def initialize(app)
#app = app
end
def call(env)
#app.call(env)
rescue ActionDispatch::Http::Parameters::ParseError => error
if JSON_API_MIME_TYPES.include?(env['CONTENT_TYPE']) ||
JSON_API_MIME_TYPES.include?(env['HTTP_ACCEPT'])
return [
400, { 'Content-Type' => 'application/json' },
[{
status: 400,
error: 'JSON parse error',
message: "There was a problem in the JSON you submitted: #{error}"
}.to_json]
]
else
raise error
end
end
end
config/application.rb
require './app/middleware/catch_json_parse_errors'
...
config.middleware.use CatchJsonParseErrors
I have the following curl request -
`curl --socks5 #{proxy} --connect-timeout 10 -H "Accept: application/json" #{url}`
I want to write a faraday request instead of a curl call. I am doing this -
faraday = Faraday.new("#{url}", ssl:{verify: false} do |f|
f.proxy "#{proxy}"
end
faraday.get
I am getting a reponse but the response has no body or headers. Following is the response -
#<Faraday::Response:0x0056075d353ad0 #on_complete_callbacks=[], #env=#<Faraday::Env #method=:get #url=#<URI::HTTP:0x0056075d358328 URL:main_url> #request=#<Faraday::RequestOptions proxy=#<Faraday::ProxyOptions uri=#<URI::HTTP:0x0056075dce69d0 URL:proxy_url>>> #request_headers={"User-Agent"=>"Faraday v0.9.2"} #ssl=#<Faraday::SSLOptions (empty)> #response=#<Faraday::Response:0x0056075d353ad0 ...>>>
What am I doing wrong here?
The hardest issue with the conversion to Faraday is that you need to use a SOCKS5 proxy. Faraday does not support SOCKS proxies (there is an open pull-request for this).
The only way around this is to monkey-patch Faraday to use the socksify gem which adds support for SOCKS proxies to Net::HTTP (the default Faraday network adapter). The procedure is nicely described in this gist and I mostly copy-paste a slightly altered version of it here.
Basically you need to follow these steps:
Install the faraday and socksify gems
Monkey-patch Faraday to support SOCKS. Put this code into a Rails initializer. Note that the patch only applies if you don't need to authenticate to the SOCKS proxy (as your curl command suggests). If you need proxy authentication, see the gist for a patch version supporting that. The patch is as follows:
class Faraday::Adapter::NetHttp
def net_http_connection(env)
if proxy = env[:request][:proxy]
if proxy[:socks]
Net::HTTP::SOCKSProxy(proxy[:uri].host, proxy[:uri].port)
else
Net::HTTP::Proxy(proxy[:uri].host, proxy[:uri].port, proxy[:uri].user, proxy[:uri].password)
end
else
Net::HTTP
end.new(env[:url].host, env[:url].port)
end
end
Create the request. I noticed you are probably trying to make a HTTPS request so I took this into account, as well as the timeouts you have in the curl parameters:
PROXY_OPTS = {
uri: URI.parse('https://proxy_url:1080'),
socks: true
}
SSL_OPTS = { verify: false }
connection = Faraday.new(url: "https://example.com",
ssl: SSL_OPTS,
request: { proxy: PROXY_OPTS }) do |faraday|
faraday.options.timeout = 10 # open/read timeout in seconds
faraday.options.open_timeout = 10 # connection open timeout in seconds
faraday.response :logger # log debug info
faraday.adapter :net_http # use the Net:HTTP adapter
faraday.headers['Accept'] = 'application/json' # your custom headers
end
response = connection.get
response.body
Finally, please note that ignoring peer verification (verify: false in the SSL options) is insecure! You should instead properly configure Faraday to use a certificate store to verify peer certificates against. This is fully documented here.
This is how I use faraday for a post request to get an access token from Microsoft Exchange API for example.
url = "https://login.microsoftonline.com/#{ENV['TENANT']}/oauth2/token"
conn = Faraday.new url: url do |faraday|
faraday.request :url_encoded # form-encode POST params
faraday.response :logger # log requests to STDOUT
faraday.adapter Faraday.default_adapter # make requests with Net::HTTP
end
response = conn.post do |req|
req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
req.body = {
client_id: URI::encode(ENV['CLIENT_ID']),
client_secret: URI::encode(ENV['CLIENT_SECRET']),
resource: URI::encode('https://graph.microsoft.com'),
grant_type: URI::encode('client_credentials'),
}
Rails.logger.info "Body #{req.body.inspect}"
end
if response.status.to_i == 200
response_body = ActiveSupport::JSON.decode(response.body)
return response_body['access_token']
else
return false
end
Hope it helps.
I have rails app and i have event controller
def create
#event = Event.create(event_params)
#event.location_id = Location.find_by_address(params[:address]).id
#event.owner = current_user
if params[:clot_id].present?
#event.clot_id = params[:clot_id]
end
render json: #event, status: 201, location: #event
end
def event_params
params.require(:event).permit(:venue, :clot_id, :contact, :cost, :description, :name, :tag_list,:location_id,
compound_started_at_attributes: [:date, :time],
compound_ended_at_attributes: [:date, :time], location_attributes: [:id, :address])
end
When i try to make curl request on localhost its working fine
curl -i -H 'Authorization: Token token'="NXNW8kxzZ2z8czHF6" -X POST -d 'event[venue]=indore&event[contact]=9977966105&event[cost]=52&event[name]=forencis&event[compound_started_at_attributes][date]= 2015-08-20&event[compound_started_at_attributes][time]= 09.05&event[compound_ended_at_attributes][date]= 2015-08-25&event[compound_ended_at_attributes][time]= 10.05&event[location_id]=10&event[clot_id]=26' http://localhost:3000/api/events
curl to server
curl -i -H 'Authorization: Token token'="oKSMrppRXEiRSH61okn5" -X POST -d 'event[venue]=indore&event[contact]=9977966105&event[cost]=52&event[name]=forencis&event[compound_started_at_attributes][date]= 2015-08-20&event[compound_started_at_attributes][time]= 09.05&event[compound_ended_at_attributes][date]= 2015-08-25&event[compound_ended_at_attributes][time]= 10.05&event[location][address]=indore&event[clot_id]=1' 104.236.233.57/api/events
But when i m trying to make same curl request to production server its giving error in production.log
F, [2015-07-02T14:20:47.041280 #29752] FATAL -- :
NoMethodError (undefined method `id' for nil:NilClass):
app/controllers/api/events_controller.rb:34:in `create
i.e error in line
#event.location_id = Location.find_by_address(params[:address]).id
I checked the database for the presence of location_id = 10 in production database and i also checked other values in database before sending api request.
Please help me solve the error.
you're using the :address param and in the query string, address is inside location param: event[location][address]=indore.
Shouldn't be event[address]=indore?
And your code assumes that always 'Location' will be found. You shouldn't assume that and add some validation to Event class (or other workaround that makes sense to you):
#event.location_id = Location.find_by_address(params[:address]).try(:id)
And in Event class:
class Event
validates_presence_of :location_id
end
SOLVED: I had done a few things wrong, all of which involved my controller RECEIVING the data. There was not anything wrong with the methods below on SENDING the data.
1: I was not using #report.save in my reportController#create
2: I was not passing params[:report] in my controller
3: I added "skip_before_filter :verify_authenticity_token" to my applicaiton controller to stop warnings in the logs.
Solved. data insertion successful.
=====ORIG. Question Below=====
I need an external program to issue a command that inserts stuff into a Ruby on Rails database.
I understand the security implications of this, but because this application is not public facing, it is not really an issue.
This is the workflow i am looking to achieve:
REST client > RAILS > create new DB TABLE row
For purposes of example: my route.rb file contains
resources :reports
so i am able to CRUD using those routes. I just cant seem to get my rest client to work correctly.
UPDATE:
I have tried a RUBY rest client AND curl command in ONE, to no avail.
require 'rest_client'
require 'json'
hash_to_send = {:test_name => 'Fake Name', :pass_fail => 'pass',:run_id => 1111, :category => 'Fake Category'}
#formulate CURL attempt
myCommand = "curl -H 'Content-Type: application/json' -X POST http://localhost:8889/report.json -d #{hash_to_send.to_json} > deleteme.html"
#execute CURL attempt
`#{myCommand}` # RESULT--> 795: unexpected token at 'test_name:Fake Name'
#Make Ruby rest client attempt
response = RestClient.post( "http://localhost:8889/report.json",
hash_to_send.to_json,
:content_type => :json, :accept => :json
)
#debug info
puts myCommand # Returns --> {"test_name":"Fake Name","pass_fail":"pass","run_id":1111,"category":"Fake Category"}
Instead of curl in command-line, use ruby script and handle REST calls and JSON conversion by gems. For example, using rest-client gem (https://github.com/archiloque/rest-client) and standard json gem you can write:
require 'rest_client'
require 'json'
response = RestClient.post( "http://localhost:8889/report.json",
params_in_hash.to_json,
{ :content_type => :json, :accept => :json }
)