Is a framework required for Ruby to accept POST requests? - ruby-on-rails

Is there a way to use a flat .rb file to accept POST requests, or do I need to use a framework like Rails or Sinatra to accept the request?
I'm thinking along the lines of how I can use a flat .php file to accept POST requests, and use the $_REQUEST[] variable to access passed data.
Even more specifically, I'm trying to learn a bit of Ruby by porting over one of my Twilio apps from PHP. The app accepts SMS, processes the message sent, and sends a reply based on the body of the message received.
While using PHP, I can set the SMS Request URL in the Twilio site to my PHP file. The PHP file uses the $_REQUEST[] array to use the message that was received. (It seems like the Ruby equivalent to this is params[].)
Here's a quick example of the PHP version of what I'm talking about:
<?php
require "twilio.php"; // Twilio Library
$ApiVersion = "2010-04-01"; // Twilio API Version
$AccountSid = "SID"; // Twilio SID
$AuthToken = "TOKEN"; // Twilio Token
// Instantiate a new Twilio Rest Client
$client = new TwilioRestClient($AccountSid, $AuthToken);
// Get message body & who it's from, for the SMS that was just received
$SMSbody = mysql_real_escape_string($_REQUEST['Body']);
if ($SMScode == "codeword"){
$SMSresponse = "You know the code.";
}
else{
$SMSresponse = "You do not know the code.";
}
// Twilio response to the sender
header("content-type: text/xml");
echo "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
?>
<Response>
<Sms><?php echo $SMSresponse;?></Sms>
</Response>
Here's my attempt at a Ruby equivalent, which is probably offensively bad:
require "rubygems"
require "twilio-ruby"
#account_sid = "SID"
#auth_token = "TOKEN"
smsbody = params['body']
#client = Twilio::REST::Client.new(#account_sid, #auth_token)
#account = #client.accounts.get(#account_sid)
if smsbody == "codeword"
smsreply = "You know the code"
else
smsreply = "You do not know the code"
response = Twilio::TwiML::Response.new do |r|
r.Sms smsreply
end
# print the result
puts response.text
This results in the Twilio debugging dashboard stating that the reply was more than 160 characters. This is because the reply is the full Ruby code, not the result of having ran the Ruby code. This makes me think that the POST request isn't being accepted correctly...

You don't need to use a framework, and based on your description Rails would definitely be overkill for you. However, using a lightweight framework can make some aspects a bit nicer. I'd recommend looking at Camping if you haven't already - it is intended for single file apps.

Based on the existing answers, and all the other research I've done, it doesn't seem like there's one single Answer to this question. It's another one of those TIMTOWTDI situations. Here's a summary of what I've learned so far, though…
The ability to drop PHP files into Apache & have it work properly is made possible by mod_php, which is enabled by default (making it seem seamless).
The params[] array is actually a Rails-specific helper. To get the same functionality, one would have to parse the request body (STDIN) on their own (perhaps with the CGI.parse function provided by the CGI module).
There seem to be a few options in my case:
Use Passenger to let Apache run a Rack-based Ruby app (like Camping,
Sinatra, or Rack)
Use a pure Ruby web server like Unicorn or Thin
Call the Ruby script via PHP's passthru function
The way forward for someone who wants to stick to strictly-Ruby (not using the PHP passthru function) without straying too far away from the familiarity of Apache might be to use Passenger with either Camping or Sinatra.

How do you execute that code? As far as I know the params hash is Rails specific, you can't use it in a simple script like that.
Coming to your question you can't simply drop a ruby file inside your server and expect that it will be executed. It will be simply returned as a text file to the browser.
To execute ruby code in a webserver you need at least rack and a server capable of executing rack applications or an apache module which process ruby. This is not a simple setup like php.
You have two choices here:
Play with ruby console and/or ruby command line, i.e. from the shell run ruby your-script-name to execute it or type irb to start a ruby console. It's one of most powerful ruby feature, one of the thing that make me love ruby.
If you really want to execute your script in a webserver context and you have php installed, you can exec your ruby script calling it from php.
This is an example on how to do that supposing your file is named program.rb, add the shebang line as first line and use the ARGV array instead of the params hash:
#!/usr/bin/env ruby
^^^^ Add this line as first line of your script ^^^^
require "rubygems"
require "twilio-ruby"
...
smsbody = ARGV[0] # ARGV[0] is the first command line parameter
Make it executable with chmod +x program.rb, and then call it from your php script with
passthru("/path/to/your/ruby/program.rb ". escapeshellarg($_REQUEST['Body']));
in this way the output from passthru (the output of your ruby program) will be sent to the browser.

Related

Reading JavaScript variable in Ruby through curl request

I need to read the GBP rate from this javascript file: http://cdn.shopify.com/s/javascripts/currencies.js. I want to be able to get the js variable as JSON so that I can easily access the variable I need with its index. I tried a couple of ways as follows with eventually no success.
Way 1
Source: https://docs.ruby-lang.org/en/2.0.0/Net/HTTP.html
My code:
uri = URI('http://cdn.shopify.com/s/javascripts/currencies.js')
#response = Net::HTTP.get(uri) # => String
Result: I get the result as a string and reading the GBP rate from the string is difficult and probably not the correct way.
Way 2
Source: curl request in ruby
My Code:
url = 'http://cdn.shopify.com/s/javascripts/currencies.js'
mykey = 'demo'
uri = URI(url)
request = Net::HTTP::Get.new(uri.path)
request['Content-Type'] = 'application/xml'
request['Accept'] = 'application/xml'
request['X-OFFERSDB-API-KEY'] = mykey
#response = Net::HTTP.new(uri.host,uri.port) do |http|
http.request(request)
end
Result: This returns me Net::HTTP:0x007f2480874050 which looks like a memory address, definitely not what I want.
In addition, I've included require 'net/http', require 'json' in my controller in either case.
I am very new to Ruby and I don't know how to figure this out. So looking for someone who can help.
This is a bit of a weird request, IMO, but Rails can do it. Rails comes with a library called execjs automatically, which lets you run javascript from ruby. So, you have some javascript you want to run in that file, but you also want to return specific key from that javascript, so something like this should do it:
# Expanding upon 'Way 1', which got you the javascript as a string
uri = URI('http://cdn.shopify.com/s/javascripts/currencies.js')
response = Net::HTTP.get(uri)
gbp_rate = ExecJS.exec "#{response}; return Currency.rates.GBP;"
p gbp_rate # => 1.40045
I just want to reiterate (from their FAQ in the README) though:
Can ExecJS be used to sandbox scripts?
No, ExecJS shouldn't be used for any security related sandboxing. Since runtimes are automatically detected, each runtime has different sandboxing properties. You shouldn't use ExecJS.eval on any inputs you wouldn't feel comfortable Ruby eval()ing.
This file looks safe, but just keep it in mind, you are actually executing this javascript.
Personally, I would look to see if there's an API somewhere that can give you this value more easily, or if it doesn't change often (I have never used Shopify so don't know how much this changes) just hardcode it in the app as a config value and update it manually. Just feels cleaner, to me.

Rails: Convert REST API to websocket client

I have a typical Rails REST Api written for a http consumers. However, it turns out they need web socket API because of the integration POS Machines.
The typical API looks like this;
class Api::Pos::V1::TransactionsController < ApplicationController
before_action :authenticate
def index
#transactions = #current_business.business_account.business_deposits.last(5)
render json: {
status: 200,
number: #transactions.count,
transactions: #transactions.as_json(only: [:created_at, :amount, :status, :client_card_number, :client_phone_number])
}
end
private
def request_params
params.permit(:account_number, :api_key)
end
def authenticate
render status: 401, json: {
status: 401,
error: "Authentication Failed."
} unless current_business
end
def current_business
account_number = request_params[:account_number].to_s
api_key = request_params[:api_key].to_s
if account_number and api_key
account = BusinessAccount.find_by(account_number: account_number)
if account && Business.find(account.business_id).business_api_key.token =~ /^(#{api_key})/
#current_business = account.business
else
false
end
end
end
end
How can i serve the same responses using web-sockets?
P.S: Never worked with sockets before
Thank you
ActionCable
I would second Dimitris's reference to ActionCable, as it's expected to become part of Rails 5 and should (hopefully) integrate with Rails quite well.
Since Dimitris suggested SSE, I would recommend against doing so.
SSE (Server Sent Events) use long polling and I would avoid this technology for many reasons which include the issue of SSE connection interruptions and extensibility (websockets allow you to add features that SSE won't support).
I am almost tempted to go into a rant about SSE implementation performance issues, but... even though websocket implementations should be more performant, many of them suffer from similar issues and the performance increase is often only in thanks to the websocket connection's longer lifetime...
Plezi
Plezi* is a real-time web application framework for Ruby. You can either use it on it's own (which is not relevant for you) or together with Rails.
With only minimal changes to your code, you should be able to use websockets to return results from your RESTful API. Plezi's Getting Started Guide has a section about unifying the backend's RESTful and Websocket API's. Implementing it in Rails should be similar.
Here's a bit of Demo code. You can put it in a file called plezi.rb and place it in your application's config/initializers folder...
Just make sure you're not using any specific Servers (thin, puma, etc'), allowing Plezi to override the server and use the Iodine server, and remember to add Plezi to your Gemfile.
class WebsocketDemo
# authenticate
def on_open
return close unless current_business
end
def on_message data
data = JSON.parse(data) rescue nil
return close unless data
case data['msg']
when /\Aget_transactions\z/i
# call the RESTful API method here, if it's accessible. OR:
transactions = #current_business.business_account.business_deposits.last(5)
write {
status: 200,
number: transactions.count,
# the next line has what I think is an design flaw, but I left it in
transactions: transactions.as_json(only: [:created_at, :amount, :status, :client_card_number, :client_phone_number])
# # Consider, instead, to avoid nesting JSON streams:
# transactions: transactions.select(:created_at, :amount, :status, :client_card_number, :client_phone_number)
}.to_json
end
end
# don't disclose inner methods to the router
protected
# better make the original method a class method, letting you reuse it.
def current_business
account_number = params[:account_number].to_s
api_key = params[:api_key].to_s
if account_number && api_key
account = BusinessAccount.find_by(account_number: account_number)
if account && Business.find(account.business_id).business_api_key.token =~ /^(#{api_key})/
return (#current_business = account.business)
end
false
end
end
end
Plezi.route '/(:api_key)/(:account_number)', WebsocketDemo
Now we have a route that looks something like: wss://my.server.com/app_key/account_number
This route can be used to send and receive data in JSON format.
To get the transaction list, the client side application can send:
JSON.stringify({msg: "get_transactions"})
This will result in data being send to the client's websocket.onmessage callback with the last five transactions.
Of course, this is just a short demo, but I think it's a reasonable proof of concept.
* I should point out that I'm biased, as I'm Plezi's author.
P.S.
I would consider moving the authentication into a websocket "authenticate" message, allowing the application key to be sent in a less conspicuous manner.
EDIT
These are answers to the questions in the comments.
Capistrano
I don't use Capistrano, so I'm not sure... but, I think it would work if you add the following line to your Capistrano tasks:
Iodine.protocol = false
This will prevent the server from auto-starting, so your Capistrano tasks flow without interruption.
For example, at the beginning of the config/deploy.rb you can add the line:
Iodine.protocol = false
# than the rest of the file, i.e.:
set :deploy_to, '/var/www/my_app_name'
#...
You should also edit your rakefile and add the same line at the beginning of the rakefile, so your rakefile includes the line:
Iodine.protocol = false
Let me know how this works. Like I said, I don't use Capistrano and I haven't tested it out.
Keeping Passenger using a second app
The Plezi documentation states that:
If you really feel attached to your thin, unicorn, puma or passanger server, you can still integrate Plezi with your existing application, but they won't be able to share the same process and you will need to utilize the Placebo API (a guide is coming soon).
But the guide isn't written yet...
There's some information in the GitHub Readme, but it will be removed after the guide is written.
Basically you include the Plezi application with the Redis URL inside your Rails application (remember to make sure to copy all the gems used in the gemfile). than you add this line:
Plezi.start_placebo
That should be it.
Plezi will ignore the Plezi.start_placebo command if there is no other server defined, so you can put the comment in a file shared with the Rails application as long as Plezi's gem file doesn't have a different server.
You can include some or all of the Rails application code inside the Plezi application. As long as Plezi (Iodine, actually) is the only server in the Plezi GEMFILE, it should work.
The applications will synchronize using Redis and you can use your Plezi code to broadcast websocket events inside your Rails application.
You may want to have a look at https://github.com/rails/actioncable which is the Rails way to deal with WebSockets, but currently in Alpha.
Judging from your code snippet, the client seems to only consume data from your backend. I'm skeptical whether you really need WebSockets. Ιf the client won't push data back to the server, Server Sent Events seem more appropriate.
See relevant walk-through and documentation.

Ruby library with least footprint to host a very simple single endpoint API

I have a very simple number crunching Ruby function that I want to make available via a web API. The API is essentially a single endpoint, e.g. http://example.com/crunch/<number> and it returns JSON output.
I can obviously install Rails and implement this quickly. I require no more help from a 'framework' other than to handle HTTP for me. No ORM, MVC and other frills.
On the far end, I can write some Ruby code to listen on a port and accept GET request and parse HTTP headers etc. etc. I don't want to re-invent that wheel either.
What can I use to expose a minimal API to the web using something with the least footprint/dependencies. I read about Sinatra, Ramaze, etc., but I believe there can be a way to do something even simpler. Can I just hack some code on top of Rack to do what I am trying to do?
Or in other words, what will be the simplest Ruby equivalent of the following code in nodejs:
var http = require('http');
http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
var ans = crunch(number);
res.end(ans);
}).listen(1337, "127.0.0.1");
console.log('Server running at http://127.0.0.1:1337/');
You seem like you want to use Rack directly. "Rack from the Beginning" is a decent tutorial that should get you started.
It'll probably look something like this:
class CrunchApp
def self.crunch(crunchable)
# top-secret crunching
end
def self.call(env)
crunchy_stuff = input(env)
[200, {}, crunch(crunchy_stuff)]
end
private
def self.input(env)
request = Rack::Request.new(env)
request.params['my_input']
end
end
Rack::Server.start app: CrunchApp
But I must say, using that instead of something like Sinatra seems silly unless this is just a fun project to play with things. See their 'Hello World':
require 'sinatra'
get '/hi' do
"Hello World!"
end
Ruby-Grape is a good option for your use case. It has a minimal implementation over Rack that allow the creation of simple REST-API endpoints.
Cuba is another good option with a thin layer over Rack itself.sample post
If you are familiar with Rails you can use the Rails API gem which is very well documented with minor overhead. Remember also that Rails-API will be part of Rails 5.
Last, but not last you can implement it on Rack directly.

Re-entrant subrequests in Rack/Rails

I've got a couple Engine plugins with metal endpoints that implement some extremely simple web services I intend to share across multiple applications. They work just fine as they are, but obviously, while loading them locally for development and testing, sending Net::HTTP a get_response message to ask localhost for another page from inside the currently executing controller object results in instant deadlock.
So my question is, does Rails' (or Rack's) routing system provide a way to safely consume a web service which may or may not be a part of the same app under the same server instance, or will I have to hack a special case together with render_to_string for those times when the hostname in the URI matches my own?
It doesn't work in development because it's only serving one request at a time, and the controller's request gets stuck. If you need this you can run multiple server locally behind a load balancer. I recommend using Passenger even for development (and the prefpane if you are on OS X).
My recommendation for you is to separate the internal web services and the applications that use them. This way you do not duplicate the code and you can easily scale and control them individually.
This is in fact possible. However, you need to ensure that the services you call are not calling each other recursively.
A really simple "reentrant" Rack middleware could work like this:
class Reentry < Struct.new(:app)
def call(env)
#current_env = env
app.call(env.merge('reentry' => self)
end
def call_rack(request_uri)
env_for_recursive_call = #current_env.dup
env_for_recursive_call['PATH_INFO'] = request_uri # ...and more
status, headers, response = call(env_for_recursive_call)
# for example, return response as a String
response.inject(''){|part, buf| part + buf }
end
end
Then in the calling code:
env['reentry'].call_rack('/my/api/get-json')
A very valid use case for this is sideloading API responses in JSON
format within your main page.
Obviously the success of this technique will depend on the sophistication
of your Rack stack (as some parts of the Rack env will not like being reused).

How do I view the HTTP response to an ActiveResource request?

I am trying to debug an ActiveResource call that is not working.
What's the best way to view the HTTP response to the request ActiveResource is making?
Monkey patch the connection to enable Net::HTTP debug mode. See https://gist.github.com/591601 - I wrote it to solve precisely this problem. Adding this gist to your rails app will give you Net::HTTP.enable_debug! and Net::HTTP.disable_debug! that you can use to print debug info.
Net::HTTP debug mode is insecure and shouldn't be used in production, but is extremely informative for debugging.
Add a new file to config/initializers/ called 'debug_connection.rb' with the following content:
class ActiveResource::Connection
# Creates new Net::HTTP instance for communication with
# remote service and resources.
def http
http = Net::HTTP.new(#site.host, #site.port)
http.use_ssl = #site.is_a?(URI::HTTPS)
http.verify_mode = OpenSSL::SSL::VERIFY_NONE if http.use_ssl
http.read_timeout = #timeout if #timeout
# Here's the addition that allows you to see the output
http.set_debug_output $stderr
return http
end
end
This will print the whole network traffic to $stderr.
It's easy. Just look at the response that comes back. :)
Two options:
You have the source file on your computer. Edit it. Put a puts response.inspect at the appropriate place. Remember to remove it.
Ruby has open classes. Find the right method and redefine it to do exactly what you want, or use aliases and call chaining to do this. There's probably a method that returns the response -- grab it, print it, and then return it.
Here's a silly example of the latter option.
# Somewhere buried in ActiveResource:
class Network
def get
return get_request
end
def get_request
"I'm a request!"
end
end
# Somewhere in your source files:
class Network
def print_request
request = old_get_request
puts request
request
end
alias :old_get_request :get_request
alias :get_request :print_request
end
Imagine the first class definition is in the ActiveRecord source files. The second class definition is in your application somewhere.
$ irb -r openclasses.rb
>> Network.new.get
I'm a request!
=> "I'm a request!"
You can see that it prints it and then returns it. Neat, huh?
(And although my simple example doesn't use it since it isn't using Rails, check out alias_method_chain to combine your alias calls.)
I like Wireshark because you can start it listening on the web browser client end (usually your development machine) and then do a page request. Then you can find the HTTP packets, right click and "Follow Conversation" to see the HTTP with headers going back and forth.
This only works if you also control the server:
Follow the server log and fish out the URL that was called:
Completed in 0.26889 (3 reqs/sec) | Rendering: 0.00036 (0%) | DB: 0.02424 (9%) | 200 OK [http://localhost/notifications/summary.xml?person_id=25738]
and then open that in Firefox. If the server is truely RESTful (ie. stateless) you will get the same response as ARes did.
Or my method of getting into things when I don't know the exact internals is literally just to throw in a "debugger" statement, start up the server using "script/server --debugger" and then step through the code until I'm at the place I want, then start some inspecting right there in IRB.....that might help (hey Luke btw)
Maybe the best way is to use a traffic sniffer.
(Which would totally work...except in my case the traffic I want to see is encrypted. D'oh!)
I'd use TCPFlow here to watch the traffic going over the wire, rather than patching my app to output it.
the firefox plugin live http headers (http://livehttpheaders.mozdev.org/) is great for this. Or you can use a website tool like http://www.httpviewer.net/

Resources