I have a simple Sinatra App running on EventMachine, like this example.
The app is working, now I'd like to allow the routes I'm defining in Sinatra to access the websocket using the EventMachine channel that is created. I naively tried the following, but of course within the Sinatra App, the #channel variable isn't defined, so this doesn't work.
require 'em-websocket'
require 'sinatra'
EventMachine.run do
#channel = EM::Channel.new
class App < Sinatra::Base
get '/' do
erb :index
end
post '/test' do
#channel.push "Post request hit endpoint"
status 200
end
end
EventMachine::WebSocket.start :host => '0.0.0.0', :port => 8080 do |socket|
socket.onopen do
sid = #channel.subscribe { |msg| socket.send msg }
#channel.push "Subscriber ID #{sid} connected!"
socket.onmessage do |msg|
#channel.push "Subscriber <#{sid}> sent message: #{msg}"
end
socket.onclose do
#channel.unsubscribe(sid)
end
end
end
App.run! :port => 3000
end
How could I access the EventMachine channel I've got open within my Sinatra app?
In case others don't know what we're talking about in the comments, here's an example of using a class instance variable in the way I suggested. This runs, but I don't know if it does what's expected:
require 'em-websocket'
require 'sinatra'
require 'haml'
module Example
def self.em_channel
#em_channel ||= EM::Channel.new
end
EventMachine.run do
class App < Sinatra::Base
configure do
enable :inline_templates
end
get '/' do
haml :index
end
get '/test' do
Example.em_channel.push "Test request hit endpoint"
status 200
end
end
EventMachine::WebSocket.start :host => '0.0.0.0', :port => 8080 do |socket|
socket.onopen do
sid = Example.em_channel.subscribe { |msg| socket.send msg }
Example.em_channel.push "Subscriber ID #{sid} connected!"
socket.onmessage do |msg|
Example.em_channel.push "Subscriber <#{sid}> sent message: #{msg}"
end
socket.onclose do
Example.em_channel.unsubscribe(sid)
end
end
end
App.run!
end
end
__END__
## layout
%html
= yield
## index
%div.title Hello world.
Related
Need to build chat bot with whatsapp, so i use Whats app cloud api + Sinatrarb.
When i need to send the session its sends perfectually, but its doesnt work
class WhatsAppSender < Sinatra::Base
configure :development do
register Sinatra::Reloader
enable :sessions
set :session_secret, "secret"
set :session_store, Rack::Session::Pool
end
configure do
enable :sessions
set :session_secret, "secret"
set :session_store, Rack::Session::Pool
end
post '/bot' do
request.body.rewind
body = JSON.parse request.body.read
# puts body
puts session[:answer]
if body['entry'][0]['changes'][0]['value'].include?("messages")
user_text = body['entry'][0]['changes'][0]['value']['messages'][0]['text']['body']
case
when user_text == "hi"
# puts session[:answer]
response = HTTP.auth("Bearer mytoken")
.headers(:accept => "application/json")
.post("https://graph.facebook.com/v14.0/myid/messages",
:json => { messaging_product: "whatsapp",
recipient_type: "individual",
to: "mynumber",
type: "text",
some_text: "some text",
text: { preview_url:
false,
body: "hi man"}
})
session[:answer] = "booking_amount"
puts response
when session[:answer] == "booking_amount"
puts "session works"
when user_text.downcase == "d"
session.clear
puts "cleared"
end
end
end
end
then i inspect request, sinatra session works fine, set the cookie
Set-Cookie rack.session=BAh7CEkiD3Nlc3Npb25faWQGOgZFVG86HVJhY2s6OlNlc3Npb246OlNlc3Npb25JZAY6D0BwdWJsaWNfaWRJIkViNThiYmJjMjdmYjI4MGU0ZTMxMDY4NzE4MDllOWVhYTBlNTVlM2UwMjg4ZWE3OWRiMjVmYTlkNThmZjczNzI3BjsARkkiCWNzcmYGOwBGSSIxbEJydTFnUm5tSTVCOVZUT1pZelNwSFY3a3ZUNGxiVmVHS2FlZVFvYVJsOD0GOwBGSSINdHJhY2tpbmcGOwBGewZJIhRIVFRQX1VTRVJfQUdFTlQGOwBUSSItNDJiNzI3MTFjNTdmZDA5YTk1MjY0NmY0N2Q0YWJjMjk0ODk5OTZhMQY7AEY%3D--f88a49537f00d65faf60da839b05d847d47f288d; path=/; HttpOnly
But its doesn't work... Maybe i should use another language or framework? please help.
I want to send an email every day with a different attachment. So I think I should pass the parameter to mail's initialize block. I read some articles about ruby block but I can not find the right way to implement it. How could I do it the right way and why does the mail's initialize method pass a block? Thank you.
require 'mail'
class MailSender
attr_accessor :created_at
def initialize
delivery_options = {
address: 'xxmail.com',
port: 25,
user_name: 'xxx#xxmail.com',
password: 'xxxxxx',
authentication: :login
}
Mail.defaults do
delivery_method :smtp, delivery_options
end
self.created_at = DateTime.now.prev_day.strftime("%F")
end
def notify
mail = Mail.new do
from 'xxx#xxmail.com'
to 'xxx#xxmail.com'
subject 'mailtest'
body 'The first mail.'
add_file :filename => "#{created_at}.txt", :content => File.read("#{created_at}.txt")
end
mail.deliver!
end
end
I am trying to send an email to example#gmail.com with Action Mailer (Ruby on Rails). The method sendactivation is correctly executed and the message "Email sent" is displayed. However, I never receive any email. Actually, the output "Test" is never printed. My webapp is hosted on Heroku Cedar-10.
class UsersController < ApplicationController
def sendactivation
UserMailer.welcome_email()
render :json => {
:result => "Email sent"
}
end
end
end
class UserMailer < ActionMailer::Base
def welcome_email()
mail(to: "example#gmail.com",
body: "Hello",
content_type: "text/html",
subject: "Already rendered!")
puts "Test"
end
end
This is the configuration I have on my config/environment/production.rb. Actually I wanted to send it with Office 365 but I suppose it is easier to debug with a Gmail account.
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
address: 'smtp.gmail.com',
port: 587,
user_name: 'example#gmail.com',
password: '########',
authentication: 'plain',
enable_starttls_auto: true }
What am I doing wrong? Is there anything I need to change on my Gmail configuration?
ANSWER: In addition to the marked answer, I needed to set the "from" address in the welcome_email method.
Call method deliver:
UserMailer.welcome_email.deliver
UserMailer.welcome_email.deliver_now
From the Rails Guide: See section 2.1.4
class SendWeeklySummary
def run
User.find_each do |user|
UserMailer.weekly_summary(user).deliver_now
end
end
end
I'm developing an api as a modular Sinatra web application and would like to standardize the responses that are returned without having to do so explicitly. I thought this could be achieved by using middleware but it fails in most scenarios. The below sample application is what I have so far.
config.ru
require 'sinatra/base'
require 'active_support'
require 'rack'
class Person
attr_reader :name, :surname
def initialize(name, surname)
#name, #surname = name, surname
end
end
class MyApp < Sinatra::Base
enable :dump_errors, :raise_errors
disable :show_exceptions
get('/string') do
"Hello World"
end
get('/hash') do
{"person" => { "name" => "john", "surname" => "smith" }}
end
get('/array') do
[1,2,3,4,5,6,7, "232323", '3245235']
end
get('/object') do
Person.new('simon', 'hernandez')
end
get('/error') do
raise 'Failure of some sort'
end
end
class ResponseMiddleware
def initialize(app)
#app = app
end
def call(env)
begin
status, headers, body = #app.call(env)
response = {'status' => 'success', 'data' => body}
format(status, headers, response)
rescue ::Exception => e
response = {'status' => 'error', 'message' => e.message}
format(500, {'Content-Type' => 'application/json'}, response)
end
end
def format(status, headers, response)
result = ActiveSupport::JSON.encode(response)
headers["Content-Length"] = result.length.to_s
[status, headers, result]
end
end
use ResponseMiddleware
run MyApp
Examples (in JSON):
/string
Expected: {"status":"success","data":"Hello World"}
Actual: {"status":"success","data":["Hello World"]}
/hash (works)
Expected: {"status":"success","data":{"person":{"name":"john","surname":"smith"}}}
Actual: {"status":"success","data":{"person":{"name":"john","surname":"smith"}}}
/array
Expected: {"status":"success","data": [1,2,3,4,5,6,7,"232323","3245235"]}
Actual: {"status":"error","message":"wrong number of arguments (7 for 1)"}
/object
Expected: {"status":"success","data":{"name":"simon","surname":"hernandez"}}
Actual: {"status":"success","data":[]}
/error (works)
Expected: {"status":"error","message":"Failure of some sort"}
Actual: {"status":"error","message":"Failure of some sort"}
If you execute the code, you will see that /hash and /error give back the required responses, but the rest do not. Ideally, I would not like to change anything in the MyApp class. It's currently being built on top of Sinatra 1.3.3, ActiveSupport 3.2.9 and Rack 1.4.1.
With some help from #sinatra on irc.freenode.org, I managed to get it down to what I want. I added the following to MyApp:
def route_eval
result = catch(:halt) { super }
throw :halt, {"result" => result}
end
I then changed the following line in ResponseMiddleware:
response = {'status' => 'success', 'data' => body}
to
response = {'status' => 'success', 'data' => body["result"]}
and all my test cases passed.
I'm using the casrack-the-authenticator gem for CAS authentication. My server is running Thin on top of Sinatra. I've gotten the CAS authentication bit working, but I'm not sure how to tell Rack to intercept "/index.html" requests to confirm the CAS login, and if the user is not allowed to view the page, return a HTTP 403 response instead of serving the actual page. Does anyone have experience with this? Thanks.
My app:
class Foo < Sinatra::Base
enable :sessions
set :public, "public"
use CasrackTheAuthenticator::Simple, :cas_server => "https://my.cas_server.com"
use CasrackTheAuthenticator::RequireCAS
get '/' do
puts "Hello World"
end
end
My rackup file:
require 'foo'
use Rack::CommonLogger
use Rack::Lint
run Foo
Initial attempt at getting Rack to understand authentication in its file service (comments and thoughts welcome):
builder = Rack::Builder.new do
map '/foo/index.html' do
run Proc.new { |env|
user = Rack::Request.new(env).session[CasrackTheAuthenticator::USERNAME_PARAM]
[401, { "Content-Type" => "text/html" }, "CAS Authentication Required"] unless user
# Serve index.html because we detected user
}
end
map '/foo' do
run Foo
end
end
run builder
Casrack-the-Authenticator will put the CAS information into the Rack session. You can pull that out in another piece of Rack middleware or in your Sinatra app.
The following is for a Rails application, but the concept is similar for Sinatra or a Rack middleware:
# in app/controllers/application_controller.rb:
protected
def require_sign_in!
render :nothing => true, :status => 403 unless signed_in?
end
def signed_in?
current_user.present?
end
def current_user
#current_user ||= Person.find_by_username(session[CasrackTheAuthenticator::USERNAME_PARAM])
end