The stratigy of build a talk-to-talk system using em-websocket in rails? - ruby-on-rails

Maybe it is a good example for server push system. There are many users in the system, and users can talk with each other. It can be accomplished like this: one user sends message(through websocket) to the server, then the server forward the message to the other user. The key is to find the binding between the ws(websocket object) and the user. The example code like below:
EM.run {
EM::WebSocket.run(:host => "0.0.0.0", :port => 8080, :debug => false) do |ws|
ws.onopen { |handshake|
# extract the user id from handshake and store the binding between user and ws
}
ws.onmessage { |msg|
# extract the text and receiver id from msg
# extract the ws_receiver from the binding
ws_receiver.send(text)
}
end
}
I want to figure out following issues:
The ws object can be serialized so it can be stored into disk or database? Otherwise I can only store the binding into memory.
What the differences between em-websocket and websocket-rails?
Which gem do you recommend for websocket?

You're approaching a use case that websockets are pretty good for, so you're on the right track.
You could serialize the ws object with Marshal, but think of websocket objects as being a bit like http request objects in that they are abstractions for a type of communication. You are probably best off marshaling/storing the data.
em-websocket is a lower(ish) lever websocket library built more or less directly on web-machine. websocket-rails is a higher level abstraction on websockets, with a lot of nice tools built in and pretty ok docs. It is built on top of faye-websocket-rails which is itself built on web machine. *Note, action cable which is the new websocket library for Rails 5 is built on faye.
I've use websocket-rails in the past and rather like it. It will take care of a lot for you. However, if you can use Rails 5 and Action Cable, do that, its the future.

The following is in addition to Chase Gilliam's succinct answer which included references to em-websocket, websocket-rails (which hadn't been maintained in a long while), faye-websocket-rails and ActionCable.
I would recommend the Plezi framework. It works both as an independent application framework as well as a Rails Websocket enhancement.
I would consider the following points as well:
do you need the message to persist between connections (i.e. if the other user if offline, should the message wait in a "message box"? for how long should the message wait?)...?
Do you wish to preserve message history?
These points would help yo decide if to use a persistent storage (i.e. a database) for the messages or not.
i.e., to use Plezi with Rails, create an init_plezi.rb in your application's config/initializers folder. use (as an example) the following code:
class ChatDemo
# use JSON events instead of raw websockets
#auto_dispatch = true
protected #protected functions are hidden from regular Http requests
def auth msg
#user = User.auth_token(msg['token'])
return close unless #user
# creates a websocket "mailbox" that will remain open for 9 hours.
register_as #user.id, lifetime: 60*60*9, max_connections: 5
end
def chat msg, received = false
unless #user # require authentication first
close
return false
end
if received
# this is only true when we sent the message
# using the `broadcast` or `notify` methods
write msg # writes to the client websocket
end
msg['from'] = #user.id
msg['time'] = Plezi.time # an existing time object
unless msg['to'] && registered?(msg['to'])
# send an error message event
return {event: :err, data: 'No recipient or recipient invalid'}.to_json
end
# everything was good, let's send the message and inform
# this will invoke the `chat` event on the other websocket
# notice the `true` is setting the `received` flag.
notify msg['to'], :chat, msg, true
# returning a String will send it to the client
# when using the auto-dispatch feature
{event: 'message_sent', msg: msg}.to_json
end
end
# remember our route for websocket connections.
route '/ws_chat', ChatDemo
# a route to the Javascript client (optional)
route '/ws/client.js', :client
Plezi sets up it's own server (Iodine, a Ruby server), so remember to remove from your application any references to puma, thin or any other custom server.
On the client side you might want to use the Javascript helper provided by Plezi (it's optional)... add:
<script src='/es/client.js' />
<script>
TOKEN = <%= #user.token %>;
c = new PleziClient(PleziClient.origin + "/ws_chat") // the client helper
c.log_events = true // debug
c.chat = function(event) {
// do what you need to print a received message to the screen
// `event` is the JSON data. i.e.: event.event == 'chat'
}
c.error = function(event) {
// do what you need to print a received message to the screen
alert(event.data);
}
c.message_sent = function(event) {
// invoked after the message was sent
}
// authenticate once connection is established
c.onopen = function(event) {
c.emit({event: 'auth', token: TOKEN});
}
// // to send a chat message:
// c.emit{event: 'chat', to: 8, data: "my chat message"}
</script>
I didn't test the actual message code because it's just a skeleton and also it requires a Rails app with a User model and a token that I didn't want to edit just to answer a question (no offense).

Related

The application goes offline when trying to send more than thousands of emails in Rails with AWS SES

I have implemented a platform using rails, and the goal is to send thousands of emails to customers with one click. The concept is that an email array runs each loop and inside each loop runs send email functionality like below.
#emails = ['abc#gmai.com', 'abc#example.com'] # More than 3 thousands
#emails.each do |email|
aws_email_sender(email, #email_subject, #email_body_html)
end
And the email function is like below:
def aws_email_sender(recipient, subject, htmlbody)
sender = "hello#example.com"
awsregion = "ap-west-1"
# The HTML body of the email.
htmlbodycontent = "#{htmlbody}"
# The email body for recipients with non-HTML email clients.
textbody = "This email was sent with Amazon SES using the AWS SDK for Ruby."
# Specify the text encoding scheme.
encoding = "UTF-8"
# Create a new SES resource and specify a region
ses = Aws::SES::Client.new(region: awsregion)
# Try to send the email.
begin
# Provide the contents of the email.
resp = ses.send_email({
destination: {
to_addresses: [recipient]
},
message: {
body: {
html: {
charset: encoding,
data: htmlbodycontent
},
text: {
charset: encoding,
data: textbody,
},
},
subject: {
charset: encoding,
data: subject,
},
},
source: sender,
});
# If something goes wrong, display an error message.
rescue Aws::SES::Errors::ServiceError => error
puts "Email not sent. Error message: #{error}"
end
end
The email is sending well by AWS but my rails application has gone down like
A timeout occurred, error code 524
I couldn't get the breaking point, why has my application gone down every time?
Thanks in Advance
If 524 is an HTTP status code then it means...
Cloudflare was able to make a TCP connection to the website behind them, but it did not reply with an HTTP response before the connection timed out.
Meaning your Rails app is behind a Cloudflare proxy. Cloudflare received an HTTP request, forwarded it to your app, waited around for your app to respond, but your app never did. A more detailed explanation can be found here.
Probably because it's trying to send emails to 3000 people one-by-one.
There's two strategies to fix this.
Use Bulk Email
Since the content of the email is the same for everyone, use an email template to send bulk email using the #send_bulk_templated_email method.
You can send to up to 50 addresses at a time, so use #each_slice to loop through emails in slices of 50.
This will be more efficient, but your app will still be waiting around for 3000/50 = 60 AWS API calls. At worst it will still time out. At best the user will be waiting around for a form submission.
Use A Background Job
Anytime your app needs to do something that might take a lot of time, like using a service or a large database query, consider putting it into a background job. The Rails app queues up a job to send the emails, and then it can respond to the web request while the mailing is handled in the background. This has other advantages: errors calling the service won't cause an error for the user, and failed jobs due to a temporary service outage can automatically be retried.
In Rails this is done with ActiveJob and you could write a job class to send your mail.
Use ActionMailer
However, Rails also offers a class specifically for sending email in the background: ActionMailer. You can have ActionMailer use AWS with the aws-sdk-rails gem.
config.action_mailer.delivery_method = :ses

How to receive data in ActionCable Channel without JS?

I'm writing a Rails application that uses WebSockets to communicate with other machines (no browser and client side logic in this process). I have a channel:
class MachinesChannel < ApplicationCable::Channel
def subscribed
...
end
def unsubscribed
...
end
def handle_messages
...
end
end
To receive the data the only way I know about is the JavaScript client:
ActionCable.createConsumer('/cable').subscriptions.create 'MachinesChannel',
received: (message) ->
#perform('handle_messages')
I can call server side methods from JS via #perform() method.
Is there any way to omit the JS part and somehow directly handle the incoming data in MachinesChannel?
The ideal situation would be to have the handle_messages method accept a data argument and have this metod called on incoming data.
After looking into ActionCable source code I got the following solution. You just have to create a method in MachinesChannel that you want to be called, e.g. handle_messages(data). Then, in the client that connects to your websocket, you need to send a message in the following format (example in ruby):
id = { channel: 'MachinesChannel' }
ws = WebSocket::Client::Simple.connect(url)
ws.send(JSON.generate(command: 'message', identifier: JSON.generate(id), data: JSON.generate(action: 'handle_messages', foo: 'bar', biz: 'baz')))
action has to be the name of the method you want to be called in MachinesChannel. The rest of key-values are whatever you want. This the date you can receive in the ActionCable channel.
Recently a gem action_cable_client has been release which seems exactly perfect for this kind of usage. I haven't used it, so I don't know how it really works.
Instead of:
def handle_messages
...
end
This works for me:
def receive(data)
puts data
...
end

Rails push EM-Socket

I'm writing rails app and i want user to receive notification as soon as new message is saved to DB. For websocket i'm using em-websocket gem. After connection, i store client id and socket instance in array.
Question: How to push data to the client from controller\model action (before_save for example)? Is it able to do so with em-websocket?
chat.rb
EVENTCHAT_CONFIG = YAML.load_file("#{Rails.root}/config/eventchat.yml")[Rails.env].symbolize_keys
#escape html/xss
include ERB::Util
Thread.abort_on_exception = true
Thread.new {
EventMachine.run {
#sockets = Array.new
EventMachine::WebSocket.start(EVENTCHAT_CONFIG) do |ws|
ws.onopen do
end
ws.onclose do
index = #sockets.index {|i| i[:socket] == ws}
client = #sockets.delete_at index
#sockets.each {|s| s[:socket].send h("#{client[:id]} has disconnected!")}
end
ws.onmessage do |msg|
client = JSON.parse(msg).symbolize_keys
case client[:action]
when 'connect'
#sockets.push({:id=>client[:id], :socket=>ws})
#sockets.each {|s| s[:socket].send h("#{client[:id]} has connected!")}
when 'say'
#sockets.each {|s| s[:socket].send h("#{client[:id]} says : #{client[:data]}")}
end
end
end
}
}
When deploying - you may have problems related to having a separate EM reactor in each rails worker (for example errors about some failing to bind to socket or not all users receiving all messages)
Rails controllers in relation to websocket reactor are just another type of client, easiest way is to open a connection with special client id, push some data and close it afterwards. More efficient way - is to keep a open connection from each rails worker
If a EM-based single-process server is used(for example - thin), you can use EM::Queue to deliver messages to websocket worker in-process or even write to websocket directly from controller

Manually invoking stream action in server sent event

How do I send update notifications to clients using server sent event?
What I want to accomplish is that when a client ajax calls an action, the server then would send relevant data to all connecting clients through my stream action.
I'm trying to know if this would be possible without websockets or pub/subs.
From what I can gather, you're looking for a generalized approach, rather than specific code?
--
SSE's
Server Sent Events are an HTML5 technology, meaning that if you do it correctly, it shouldn't matter whether you use Rails or another framework -- they should just work
One drawback to SSE's is they act very similar to Ajax long-polling, meaning they send constant "pings" / requests to your server, relaying back any response they find. And they'll still use the pub/sub pattern too
-
Simply, SSE's are when you have a Javascript "event listener", which will listen to an "endpoint" (URL). The endpoint, in the case of Rails, will be a controller#action, from which you can send the relevant text/event-stream updates, which is what ActionController::Live::SSE is there to do
--
Setup
#config/routes.rb
resources :your_controller do
collection do
get :endpoint
end
end
#app/assets/javascripts/application.js
var source = new EventSource('your_controller/endpoint');
source.addEventListener('message', function(e) {
console.log(e.data);
}, false);
#app/controllers/your_controller.rb
Class YourController < ActionController::Base
include ActionController::Live
def endpoint
response.headers['Content-Type'] = 'text/event-stream'
sse = SSE.new(response.stream, retry: 300, event: "event-name")
sse.write({ name: 'John'})
ensure
sse.close
end
end
This will send the relevant updates for you every time

Playing around with mails in Rails

I`m trying to create the following feature: You register and receive an email like vouldjeff+ewr#myapp.com and when you send something to this email it automatically appears in something like your wall... So my problem is how to realize the creation of the email and the receiving of the mail itself.
Any ideas?
Ruby provides Net/IMAP and Net/POP3 you can use to login into your email account.
Here's a small tutorial.
POP3
pop = Net::POP3.new("pop.gmail.com", port)
pop.enable_ssl
pop.start('YourAccount', 'YourPassword')
if pop.mails.empty?
puts 'No mail.'
else
i = 0
pop.each_mail do |m|
File.open("inbox/#{i}", 'w') do |f|
f.write m.pop
end
m.delete
i += 1
end
puts "#{pop.mails.size} mails popped."
end
pop.finish
IMAP
imap = Net::IMAP.new('imap.gmail.com')
imap.authenticate('LOGIN', 'username', 'password')
imap.select('INBOX')
imap.search(['ALL']).each do |message_id|
msg = imap.fetch(message_id,'RFC822')[0].attr['RFC822']
MailReader.receive(msg)
imap.store(message_id, "+FLAGS", [:Deleted])
end
imap.expunge()
There might be other options but that's how we do it:
Postfix
Rails Cron Job
Postfix allows you to specify a MySQL table/view to check whether an email address exists or not. You can also define Mail Forwardings.
Create a DB View to match the requirements on Postfix
This View should contain all the email addresses and forward them to a different mail account, like mailparser.
Now your Rails can either
use a POP3/IMAP frontend to the mailserver (you should install Dovecot or Courier then) to fetch the mails and process them
or go to the place on the disk where all the mails are located (check Postfix config for that) and parse the files as TMail objects and process them.
A different option is to make Postfix call script/runner with the Mail data, but rails boot-up can take long and a lot of memory, so I prefer having a Cronjob/Backgroundjob/Worker to do this.
P.S. The Creation of the E-Mail will be done by creating a Model for your Rails app which the View will use as a basis.
Sending E-Mails is simple as pie. Simply have a look at the ActionMailer Basics. If you also want to receive E-Mail, you should write a daemon that fetches Mails from the mailserver continuously in the background.
Here a snippet that fetches Mails via POP:
require 'net/pop'
config = {
:host => "mail.example.com",
:user => "foobar#example.com",
:password => "…",
:port => 110,
:timeout => 10
}
pop = Net::POP3.new(config[:host])
pop.start(config[:user], config[:password])
if pop.mails.empty?
puts "No mails…"
else
pop.mails.each do |mail|
# do stuff with mail
end
end
This is pure Ruby-Code, Rails is not needed for this snippet.

Resources