padrino && websockets - ruby

i'm looking for a way to open and use websockets from within a Padrino application. i know Padrino works with a single thread but i'm looking for a way to open websockets and share variables between its "onopen" "onclose" "onmessage" methods and Padrino controllers.
any idea how it's done ?
links i looked into:
Examples of Eventmachine usage with Padrino and Sinatra (only Sinatra worked for me)
em-websocket on GitHub
UPDATE 1:
this is my main.rb:
require 'rubygems' # <-- Added this require
require 'em-websocket'
require 'padrino-core'
require 'thin'
require File.expand_path("../config/boot.rb", __FILE__)
SOCKETS = []
EventMachine.run do # <-- Changed EM to EventMachine
# class App < Sinatra::Base
# get '/' do
# SOCKETS.each {|s| s.send "fooooo"}
# return "foo"
# end
# end
EventMachine::WebSocket.start(:host => '0.0.0.0', :port => 8080) do |ws| # <-- Added |ws|
# Websocket code here
ws.onopen {
ws.send "connected!!!!"
SOCKETS << ws
}
ws.onmessage { |msg|
puts "got message #{msg}"
ws.send "ECHO: #{msg}"
}
ws.onclose {
ws.send "WebSocket closed"
SOCKETS.delete ws
}
end
# You could also use Rainbows! instead of Thin.
# Any EM based Rack handler should do.
#App.run!({:port => 3000}) # <-- Changed this line from Thin.start to App.run!
Thin::Server.start Padrino.application, '0.0.0.0', 3000
end
i'm getting this exception:
/home/cstore/.rvm/gems/ruby-1.9.2-p290#runtime/gems/thin-1.2.11/lib/thin/daemonizing.rb:2:in `require': no such file to load -- daemons (LoadError)
from /home/cstore/.rvm/gems/ruby-1.9.2-p290#runtime/gems/thin-1.2.11/lib/thin/daemonizing.rb:2:in `<top (required)>'
from /home/cstore/.rvm/gems/ruby-1.9.2-p290#runtime/gems/thin-1.2.11/lib/thin/server.rb:50:in `<class:Server>'
from /home/cstore/.rvm/gems/ruby-1.9.2-p290#runtime/gems/thin-1.2.11/lib/thin/server.rb:48:in `<module:Thin>'
from /home/cstore/.rvm/gems/ruby-1.9.2-p290#runtime/gems/thin-1.2.11/lib/thin/server.rb:1:in `<top (required)>'
from main.rb:39:in `block in <main>'
from /home/cstore/.rvm/gems/ruby-1.9.2-p290#runtime/gems/eventmachine-0.12.10/lib/eventmachine.rb:256:in `call'
from /home/cstore/.rvm/gems/ruby-1.9.2-p290#runtime/gems/eventmachine-0.12.10/lib/eventmachine.rb:256:in `run_machine'
from /home/cstore/.rvm/gems/ruby-1.9.2-p290#runtime/gems/eventmachine-0.12.10/lib/eventmachine.rb:256:in `run'
from main.rb:9:in `<main>'
UPDATE 2:
Resolved thanks to Nathan !
I just added 'daemons' to Gemfile and reloaded my application.

Maybe you need to install daemons:
Edit your Gemfile:
# Adding this
gem 'daemons'
Install missing gems:
$ bundle install

What in particular from this example: https://github.com/igrigorik/em-websocket and Any success with Sinatra working together with EventMachine WebSockets? didn't work with Padrino but did with Sinatra? Can you explain the errors you got and why those examples failed (stacktraces)? Maybe we can help investigate.

I ran across this post and it helped me a bit, but I wanted to offer an alternative solution to anyone else who might stumble upon it. I chose to just directly modify the config.ru and mount a websocket-rack application.
Here's my config.ru where WSApp is a subclass of Rack::WebSocket::Application and is placed in the lib/ directory (therefore being automatically loaded by Padrino):
#!/usr/bin/env rackup
# encoding: utf-8
# This file can be used to start Padrino,
# just execute it from the command line.
require File.expand_path("../config/boot.rb", __FILE__)
# Setup routes
map '/' do
run Padrino.application
end
map '/ws' do
run WSApp.new
end

Since this is the top hit in Google right now, I'd like to link it to padrino-websockets, a clean DSL for writing websockets applications in Padrino.

Related

Does "vanilla" Ruby dotenv pick up any file other than .env?

I want to use environment-specific variables in my non-Rails Ruby application.
I tried different file names like .env.test.local, .env.local, .env.test
I tried using the Dotenv.load and require 'dotenv/load' approaches
This is how I wrap the task
require 'rspec'
require 'rack/test'
require 'rspec/core/rake_task'
require 'dotenv/tasks'
task test: :dotenv do
RSpec::Core::RakeTask.new(:spec).run_task(verbose: true)
end
This is my server
require 'dotenv/load'
require 'sinatra'
require 'sinatra/reloader' if development?
set :bind, '0.0.0.0'
get ENV['API_URL'] do
'Hello World!'
end
My .env.test file
API_URL=/api/v1
Expected behavior
The API_URL variable should be available to the code run by the :test task (using bundle exec rake test).
Observed behavior
An error caused because ENV['API_URL'] is null.
/Users/mariogil/.rvm/rubies/ruby-2.6.3/bin/ruby -I/Users/mariogil/.rvm/gems/ruby-2.6.3/gems/rspec-core-3.8.0/lib:/Users/mariogil/.rvm/gems/ruby-2.6.3/gems/rspec-support-3.8.0/lib /Users/mariogil/.rvm/gems/ruby-2.6.3/gems/rspec-core-3.8.0/exe/rspec --pattern spec/\*\*\{,/\*/\*\*\}/\*_spec.rb
An error occurred while loading ./spec/server_spec.rb.
Failure/Error:
get ENV['API_URL'] do
'Hello World!'
end
TypeError:
NilClass can't be coerced into Mustermann::Pattern
# /Users/mariogil/.rvm/gems/ruby-2.6.3/gems/mustermann-1.0.3/lib/mustermann.rb:73:in `new'
# /Users/mariogil/.rvm/gems/ruby-2.6.3/gems/sinatra-2.0.5/lib/sinatra/base.rb:1641:in `compile'
# /Users/mariogil/.rvm/gems/ruby-2.6.3/gems/sinatra-2.0.5/lib/sinatra/base.rb:1629:in `compile!'
# /Users/mariogil/.rvm/gems/ruby-2.6.3/gems/sinatra-2.0.5/lib/sinatra/base.rb:1604:in `route'
# /Users/mariogil/.rvm/gems/ruby-2.6.3/gems/sinatra-2.0.5/lib/sinatra/base.rb:1386:in `get'
# /Users/mariogil/.rvm/gems/ruby-2.6.3/gems/sinatra-2.0.5/lib/sinatra/base.rb:1925:in `block (2 levels) in delegate'
# ./lib/server.rb:9:in `<top (required)>'
# ./spec/server_spec.rb:6:in `require'
# ./spec/server_spec.rb:6:in `<top (required)>'
No examples found.
Finished in 0.00026 seconds (files took 0.18951 seconds to load)
0 examples, 0 failures, 1 error occurred outside of examples
/Users/mariogil/.rvm/rubies/ruby-2.6.3/bin/ruby -I/Users/mariogil/.rvm/gems/ruby-2.6.3/gems/rspec-core-3.8.0/lib:/Users/mariogil/.rvm/gems/ruby-2.6.3/gems/rspec-support-3.8.0/lib /Users/mariogil/.rvm/gems/ruby-2.6.3/gems/rspec-core-3.8.0/exe/rspec --pattern spec/\*\*\{,/\*/\*\*\}/\*_spec.rb failed
See the repo
I came up with this workaround
require 'dotenv'
require 'sinatra'
require 'sinatra/reloader' if development?
Dotenv.load(".env.#{ENV['APP_ENV']}") # Remember to set your app environment
set :bind, '0.0.0.0'
get ENV['API_URL'] do
'Hello World!'
end
It only works for .env.<ENVIROMENT> files. Maybe this could be wrapped in a function but I definitely would like this to be handled by the gem as in Rails applications, it makes more sense to me.

How to pass Puma::Configuration to Sinatra?

This is my web app:
class Front < Sinatra::Base
configure do
set :server, :puma
end
get '/' do
'Hello, world!'
end
end
I start it like this (don't suggest to use Rack, please):
Front.start!
Here is my configuration object for Puma, which I don't know how to pass to it:
require 'puma/configuration'
Puma::Configuration.new({ log_requests: true, debug: true })
Seriously, how?
Configuration is tightly connected to a way in which you run puma server.
The standard way to run puma - puma CLI command. In order to configure puma config file config/puma.rb or config/puma/<environment>.rb should be provided (see example).
But you asked how to pass Puma::Configuration object to puma. I wonder why you need it but AFAIK you need to run puma server programmatically in your application code with Puma::Launcher(see source code)
conf = Puma::Configuration.new do |user_config|
user_config.threads 1, 10
user_config.app do |env|
[200, {}, ["hello world"]]
end
end
Puma::Launcher.new(conf, events: Puma::Events.stdio).run
user_config.app may be any callable object (compatible with Rack interface) like Sinatra application.
Hope it's helpful.
Do you want to pass exactly an object or just a configuration in general? For the last option it's possible, but Puma will not log anything anyway (I'm not sure, but seems like you worry exactly about logging settings for Puma).
#!/usr/bin/env ruby
# frozen_string_literal: true
require 'bundler/inline'
gemfile(true) do
gem 'sinatra'
gem 'puma'
gem 'openssl'
end
require 'sinatra/base'
class Front < Sinatra::Base
configure do
set :server, :puma
set :server_settings, log_requests: true, debug: true, environment: 'foo'
end
get '/' do
'Hello, world!'
end
end
Front.start!

event machine with em http gem cannot load middleware oauth

I am trying to get the stock price streaming to work using TradeKing API
https://developers.tradeking.com/documentation/ruby-streaming
or the copied and pasted codes below
require 'em-http'
require 'em-http/middleware/oauth'
credentials = {
:consumer_key => "<CONSUMER_KEY>",
:consumer_secret => "<CONSUMER_SECRET>",
:access_token => "<ACCESS_TOKEN>",
:access_token_secret => "<ACCESS_TOKEN_SECRET>"
}
EM.run do
conn = EventMachine::HttpRequest.new('https://stream.tradeking.com/v1/market/quotes.json?symbols=F')
conn.use EventMachine::Middleware::OAuth, credentials
http = conn.get
http.stream { |chunk| puts chunk }
http.errback do
EM.stop
end
trap("INT") { http.close; EM.stop }
trap("TERM") { http.close; EM.stop }
end
After getting key, secret, and token, I built a simple Ruby app to play with the codes but I get an error that says it cannot load
require 'em-http/middleware/oauth'
If I disable this, the code EventMachine::Middleware::OAuth will not work.
Here is the error message:
c:/tools/ruby215/lib/ruby/site_ruby/2.1.0/rubygems/core_ext/
kernel_require.rb:54:in `require': cannot load such file --
simple_oauth (LoadError)
from c:/tools/ruby215/lib/ruby/site_ruby/2.1.0/rubyg
ems/core_ext/kernel_require.rb:54:in `require'
from c:/tools/ruby215/lib/ruby/gems/2.1.0/gems/em-ht
tp-request-1.1.2/lib/em-http/middleware/oauth.rb:1:in `<top
(required)>'
from c:/tools/ruby215/lib/ruby/site_ruby/2.1.0/rubyg
ems/core_ext/kernel_require.rb:54:in `require'
from c:/tools/ruby215/lib/ruby/site_ruby/2.1.0/rubyg
ems/core_ext/kernel_require.rb:54:in `require'
from app.rb:2:in `<main>'
I am new to event machine and em-http gem. I looked at their documentation but couldn't find information about this error. Can someone help me figure out why the file cannot load?
OK, so I solved this myself. I haven't worked with vanilla Ruby in a while so I got a bit rusty with reading the error messages. The error says that it cannot load the file and points to line 1 of the the oauth.rb file which is in the em-http-request gem. I looked into the file and the first line says require simple_oauth which I haven't installed it yet. I would think installing em-http-request gem would install simple_oauth as a dependency, but I guess not (well the em-http-request hasn't been updated for several years).
The fix to this problem is to install simple_oauth gem.
gem install simple_oauth
and run those codes again and it should work.
https://rubygems.org/gems/simple_oauth/versions/0.3.1
https://github.com/laserlemon/simple_oauth
I hope this helps anyone who is having the same problem (since TradeKing API doc isn't as clear).

Sinatra Heroku app: `start_server': undefined method `run' for HTTP:Module (NoMethodError) [duplicate]

when i try to start sinatra, i'm getting following error
/var/lib/gems/1.9.1/gems/sinatra-1.4.4/lib/sinatra/base.rb:1488:in start_server': undefined methodrun' for HTTP:Module (NoMethodError)
require 'sinatra/base'
require_relative "twt.rb"
class SinatraApp < Sinatra::Base
set :static, true
set :public_folder, File.dirname(__FILE__) + '/static'
get '/getuserinfo' do
#user = twit.getuserinfo
erb :userInfo
end
end
SinatraApp.run!
in "twt.rb" i require twitter (5.7.1)
require 'twitter'
class Twit
attr_accessor :client
def initialize(consumer_key,consumer_secret,access_token,access_token_secret)
#client = Twitter::REST::Client.new do |config|
config.consumer_key = consumer_key
config.consumer_secret = consumer_secret
config.access_token = access_token
config.access_token_secret = access_token_secret
end
end
def getUserInfo
return user = {
"name"=> client.current_user.name,
"id" => client.current_user.id
}
end
def showAllFriends
client.friends.each { |item| puts item.name }
end
def showFollowers
client.followers.each { |item| puts item.screen_name }
end
def showAllTweets
client.user_timeline.each {|item| puts item.text}
end
def showAllUserTweets(userScreenName)
client.user_timeline(userScreenName).each {|item| puts item.text}
end
def sendTweet(content)
client.update(content)
end
end
if i remove require_relative "twt.rb" line sinatra works fine.
When you run a Sinatra app using the built-in web server (as you do with SinatraApp.run!), Sinatra tries to determine which server to use by checking a list of servers in turn to see which is available. The actual list depends on the version of Ruby you are using, but one server that it always checks is net-http-server, which is simply named HTTP.
The way Sinatra checks for the availability of a server is by using a rack method that calls const_get to try and find the constant Rack::Handler::<server-name>. However, due to the way const_get works, if that constant is not available, but a top level constant with the same name as server-name is, then that will be returned, whatever class it is. (This is arguably a bug in Rack).
The Twitter gem depends on the http gem, and that in turn defines a HTTP module. (Naming a top-level module with something as generic as HTTP is arguably not a good idea).
So what is happening in this case is Sinatra is checking to see if the HTTP server is available, but Rack is returning the HTTP module from the http gem, which isn’t a server. Not being a Rack server it doesn’t have a run method, so when Sinatra tries to use it as one you get the error start_server': undefined method `run' for HTTP:Module.
One workaround is not to use the built-in server, such as the way you have discovered using a config.ru file and starting the app with rackup.
Another solution is to explicitly specify the server to use in your Sinatra app. For example you could install Thin, and then use:
set :server, 'thin'
In fact simply installing Thin would be sufficient as Thin is searched for before HTTP, but you are probably better explicitly setting the server to use. If you cannot install any other server for any reason you could use Webrick instead:
set :server, 'webrick'
i found the solution.
i launch sinatra with config.ru and it works now.
rack config.ru

Sinatra does not start with twitter gem

when i try to start sinatra, i'm getting following error
/var/lib/gems/1.9.1/gems/sinatra-1.4.4/lib/sinatra/base.rb:1488:in start_server': undefined methodrun' for HTTP:Module (NoMethodError)
require 'sinatra/base'
require_relative "twt.rb"
class SinatraApp < Sinatra::Base
set :static, true
set :public_folder, File.dirname(__FILE__) + '/static'
get '/getuserinfo' do
#user = twit.getuserinfo
erb :userInfo
end
end
SinatraApp.run!
in "twt.rb" i require twitter (5.7.1)
require 'twitter'
class Twit
attr_accessor :client
def initialize(consumer_key,consumer_secret,access_token,access_token_secret)
#client = Twitter::REST::Client.new do |config|
config.consumer_key = consumer_key
config.consumer_secret = consumer_secret
config.access_token = access_token
config.access_token_secret = access_token_secret
end
end
def getUserInfo
return user = {
"name"=> client.current_user.name,
"id" => client.current_user.id
}
end
def showAllFriends
client.friends.each { |item| puts item.name }
end
def showFollowers
client.followers.each { |item| puts item.screen_name }
end
def showAllTweets
client.user_timeline.each {|item| puts item.text}
end
def showAllUserTweets(userScreenName)
client.user_timeline(userScreenName).each {|item| puts item.text}
end
def sendTweet(content)
client.update(content)
end
end
if i remove require_relative "twt.rb" line sinatra works fine.
When you run a Sinatra app using the built-in web server (as you do with SinatraApp.run!), Sinatra tries to determine which server to use by checking a list of servers in turn to see which is available. The actual list depends on the version of Ruby you are using, but one server that it always checks is net-http-server, which is simply named HTTP.
The way Sinatra checks for the availability of a server is by using a rack method that calls const_get to try and find the constant Rack::Handler::<server-name>. However, due to the way const_get works, if that constant is not available, but a top level constant with the same name as server-name is, then that will be returned, whatever class it is. (This is arguably a bug in Rack).
The Twitter gem depends on the http gem, and that in turn defines a HTTP module. (Naming a top-level module with something as generic as HTTP is arguably not a good idea).
So what is happening in this case is Sinatra is checking to see if the HTTP server is available, but Rack is returning the HTTP module from the http gem, which isn’t a server. Not being a Rack server it doesn’t have a run method, so when Sinatra tries to use it as one you get the error start_server': undefined method `run' for HTTP:Module.
One workaround is not to use the built-in server, such as the way you have discovered using a config.ru file and starting the app with rackup.
Another solution is to explicitly specify the server to use in your Sinatra app. For example you could install Thin, and then use:
set :server, 'thin'
In fact simply installing Thin would be sufficient as Thin is searched for before HTTP, but you are probably better explicitly setting the server to use. If you cannot install any other server for any reason you could use Webrick instead:
set :server, 'webrick'
i found the solution.
i launch sinatra with config.ru and it works now.
rack config.ru

Resources