I am using for the first time redis to put chat functionality in my rails app, following this
I have in my javascript`
$(document).ready ->
source = new EventSource('/messages/events')
source.addEventListener 'messages.create', (e) ->
message = $.parseJSON(e.data).message
console.log(message)
$(".chat-messages").append "#some code"
and in my message controller
def create
response.headers["Content-Type"] = "text/javascript"
attributes = params.require(:message).permit(:content, :sender_id, :sendee_id)
#message = Message.create(attributes)
respond_to do |format|
format.js { render 'messages/create.js.erb' }
end
$redis.publish('messages.create', #message.to_json)
end
def events
response.headers["Content-Type"] = "text/event-stream"
redis = Redis.new
redis.subscribe('messages.*') do |on|
on.message do |pattern, event, data|
response.stream.write("event: #{event}\n")
response.stream.write("data: #{data}\n\n")
end
end
rescue IOError
logger.info "Stream closed"
ensure
redis.quit
response.stream.close
end
The problem is that first, nothing is logged in my console, and second I get numbers of random ConnectionTimeoutError errors. Some one hava an idea what's going on
Pre-Reqs:
Ruby 2.0.0+
Rails 4.0.0+
Redis
Puma
Initializer:
Create a redis.rb initializer file in the config/initializers directory, globalizing an instance of redis. It's also a good idea to set up a heartbeat thread (Anything from 5 seconds to 5 minutes is okay, depending on your requirements):
$redis = Redis.new
heartbeat_thread = Thread.new do
while true
$redis.publish("heartbeat","thump")
sleep 15.seconds
end
end
at_exit do
# not sure this is needed, but just in case
heartbeat_thread.kill
$redis.quit
end
Controller:
You need to add two methods to your ChatController, pub and sub. The role of pub is to publish chat events and messages to redis, and sub to subscribe to these events. It should look something like this:
class ChatController < ApplicationController
include ActionController::Live
skip_before_filter :verify_authenticity_token
def index
end
def pub
$redis.publish 'chat_event', params[:chat_data].to_json
render json: {}, status: 200
end
def sub
response.headers["Content-Type"] = "text/event-stream"
redis = Redis.new
redis.subscribe(['chat_event']) do |on|
on.message do |event, data|
response.stream.write "event: #{event}\ndata: #{data}\n\n"
end
end
rescue IOError
logger.info "Stream Closed"
ensure
redis.quit
response.stream.close
end
end
In your routes, make pub a POST and sub a GET, and match the path to something like /chat/publish and /chat/subscribe.
Coffeescript / Javascript:
Assuming your actual webpage for the chat app is at /chat, you need to write some Javascript to actually send and receive chat messages.
For ease of understanding, let's suppose your webpage only has a textbox and a button. Hitting the button should publish the content of the textbox to the chat stream, we can do that using AJAX:
$('button#send').click (e) ->
e.preventDefault()
$.ajax '/chat/publish',
type: 'POST'
data: {
chat_data: {
message: $("input#message").val(),
timestamp: $.now()
}
}
error: (jqXHR, textStatus, errorThrown) ->
console.log "Failed: " + textStatus
success: (data, textStatus, jqXHR) ->
console.log "Success: " + textStatus
Now, you need to be able to subscribe and receive the chat messages as well. You need to use EventSource for this. Using EventSource, open a channel for SSE so that you can receive events, and use that data to update the view. In this example, we will only log them to the javascript console.
The code should look something like this:
$(document).ready ->
source = new EventSource('/chat/subscribe')
source.addEventListener 'chat_event', (e) ->
console.log(e.data)
Enable Parallel Requests:
In your development environment, you'll have to enable parallel requests by adding these two lines to your config/environments/development.rb:
config.preload_frameworks = true
config.allow_concurrency = true
Now fire up your browser, browse to /chat and see the magic. When you type a message and click the button, the message will be received by all instances of that webpage.
Well this is how you make a basic chat application in rails using ActionController::Live and Redis. The final code would obviously be very different depending on your requirements but this should get you started.
Some more resources you should check out:
Tender Love Making - Is it Live?
Railscasts - #401 - ActionController::Live
SitePoint - Mini Chat with Rails and SSEs
Github - mohanraj-ramanujam / live-stream
Thoughtbot - Chat Example using SSEs
Although I've not used redis in this capacity (a mediator for "live" data), I managed to get this functionality working with Pusher
Redis
I don't understand how you're keeping the connection open between your app & Redis. You'll need some sort of web socket or concurrent-connection tech in place to handle the updates -- and to my knowledge, Redis does not handle this directly
If you look at this example, it uses a server called Goliath to handle the asynchronous connectivity:
When tiny-chat connects to the server it sends a GET request to
/subscribe/everyone where everyone is the name of the channel and with
the “Accept” header set to text/event-stream. The streaming middleware
(above) receives this request and subscribes to a redis Pub/Sub
channel. Since Goliath is non-blocking multiple clients can be
listening for events without tying up a Heroku dyno. The payload of a
server sent event looks like this:
That basically uses Middleware to connect you to the redis server -- allowing you to receive updates as required
Code
Although I can't pinpoint any errors specifically, I can give you some code we're using (using Pusher):
#config/initializers/pusher.rb
Pusher.url = ENV["PUSHER_URL"]
Pusher.app_id = ENV["PUSHER_APP_ID"]
Pusher.key = ENV["PUSHER_KEY"]
Pusher.secret = ENV["PUSHER_SECRET"]
#app/controllers/messages_controller.rb
def send_message
id = params[:id]
message = Message.find(id).broadcast!
public_key = self.user.public_key
Pusher['private-user-' + public_key].trigger('message_sent', {
message: "Message Sent"
})
end
#app/views/layouts/application.html.erb
<%= javascript_include_tag "http://js.pusher.com/2.1/pusher.min.js" %>
#app/assets/javascripts/application.js
$(document).ready(function(){
#Pusher
pusher = new Pusher("************",
cluster: 'eu'
)
channel = pusher.subscribe("private-user-#{gon.user}")
channel.bind "multi_destroy", (data) ->
alert data.message
channel.bind "message_sent", (data) ->
alert data.message
});
Related
How can I send with javascript a global message to all of our subscribed websocket connections without the need of a channel etc. (like the ping message that the actioncable is sending by default globally to all open connections)?
As far as I know, you cannot do it directly from JavaScript without channels (it needs to go over Redis first).
I would suggest you do that as a normal post action, and then send the message in Rails.
I would do something like this:
JavaScript:
$.ajax({type: "POST", url: "/notifications", data: {notification: {message: "Hello world"}}})
Controller:
class NotificationsController < ApplicationController
def create
ActionCable.server.broadcast(
"notifications_channel",
message: params[:notification][:message]
)
end
end
Channel:
class NotificationsChannel < ApplicationCable::Channel
def subscribed
stream_from("notifications_channel", coder: ActiveSupport::JSON) do |data|
# data => {message: "Hello world"}
transmit data
end
end
end
Listen JavaScript:
App.cable.subscriptions.create(
{channel: "NotificationsChannel"},
{
received: function(json) {
console.log("Received notification: " + JSON.stringify(json))
}
}
)
I want to push data to client using WebSocket. I dont now how to return data(from Redis) to client. Could somebody explain me that?
sockets_controller.rb
class SocketsController < WebsocketRails::BaseController
def create
send_message :new_event, {:message => 'TEST'}
end
end
events.rb
WebsocketRails::EventMap.describe do
subscribe :new_event, 'sockets#create'
end
websocket_rails.rb
WebsocketRails.setup do |config|
config.redis_options = { driver: :ruby }
config.standalone = false
config.synchronize = true
end
Client side:
$socket.on('test', function(data) {
console.log(data);
$scope.events.upshift(data);
if($scope.events.length>50){
$scope.events.length=50;
EventsList =$scope.events;
}
});
routerApp.config(["$socketProvider", function ($socketProvider) {
$socketProvider.setUrl("http://172.16.20.179:3000/events");
}]);
On the client side issue looks like:
GET http://172.16.20.179:3000/socket.io/?
EIO=3&transport=polling&t=1439215071732-9
XMLHttpRequest cannot load http://172.16.20.179:3000/socket.io/?
EIO=3&transport=polling&t=1439215056291-6. No 'Access-Control-Allow-
Origin' header is present on the requested resource. Origin
'http://localhost:4000' is therefore not allowed access. The response
had HTTP status code 404.
It seems that you are trying to use Socket.IO for web-sockets. There are no supported implementation of Socket.IO for Rails.
If you want an example on how to interact with websocket-rails from Angular.JS, you can use, for example, the following guide:
https://medium.com/#karimbutt/integrating-websocket-rails-with-angularjs-or-other-js-frameworks-256729e95a86
I am writing a chat application, using Jabber, on Ruby on Rails. Sending messages was quite easy to implement, but receiving messages in a loop is quite a challenge.
I want to get callbacks from the server without browser refreshes. I tried to use ActionController::Live for this.
In my client side, my code is:
var source = new EventSource('/chat/events');
source.addEventListener('refresh', function(event)
{
AddMessage(event.data);
});
My controller code is:
def chat_events
# SSE expects the `text/event-stream` content type
response.headers['Content-Type'] = 'text/event-stream'
sse = Reloader::SSE.new(response.stream)
puts "Starting XMMP call"
begin
#xmmpClient = XmppClient.new
#xmmpClient.loginUsingNameAndPassword(****, ***)
#xmmpClient.listen_connections sse
rescue IOError
puts "Error"
# When the client disconnects, we'll get an IOError on write
ensure
puts "closing stream"
sse.close
end
end
And, my XMMP client is:
def listen_connections(writer = nil)
if not #logged_in
puts "Not logged"
raise "Not logged"
end
#robot.send(Jabber::Presence.new.set_show(nil))
loop do
puts "Listening callback"
#robot.add_message_callback do |message|
puts "Got message " + message.inspect
if not writer.nil?
writer.write({ :message => message.body }, :event => 'refresh')
end
end
sleep 2
end
end
What I got:
The chat_events method of the controller is called every second or so.
Because of this, several callbacks are executed at once.
When I got a message, I got it four or five times.
{"message":"fffdsfd"}
{"message":"fffdsfd"}
{"message":"fffdsfd"}
{"message":"fffdsfd"}
And the worst stuff - my server is not responding to other responses, although I use the Puma multi-threaded server.
What is the correct way to implement functionality like this?
I get the solution
puts "."
client.add_message_callback do |message|
if message.type != :error
arr = message.from.to_s.split('#')
puts arr[0]
puts message.body
end
end
while 1
end
I'm building an app for receiving some info every second using websockets-rails gem.
Right now, it seems like all messages are send after method is fully executed.
My websockets controller:
class DbTestsController< WebsocketRails::BaseController
def run_tests_on_all
dbtsch = DbTestsScheduler.new
dbtsch.run(1, 10, message['shard'], :push) do |ops|
send_message 'db_test.run_tests_on_all', ops
Rails.logger.info(ops)
end
end
end
'run' method looks like
def run(ecfs, fafs, shard, operation)
st = tep_t = Time.now
while st + fafs.second > Time.now
Octopus.using(shard) do
send(operation)
end
if tep_t + ecfs.second <= Time.now
tep_t = tep_t + 1.second
yield(#ops) if block_given?
#ops = 0
end
end
end
In console I see Rails.logger.info(ops) outputs message every second, but send_message sends all 10 results at once when method execution is completed.
I think what you want to do is use a a gem like sync
Real-time partials with Rails. Sync lets you render partials for models that, with minimal code, update in realtime in the browser when changes occur on the server.
you can check out a example here
Im experementing with the new Rails 4 feature ActionControllerLive.
I try to build up a system with many users who are notified when somebody clicks a specific link for example enters messages#index controller.
My problem is that at the time all users are notified when somebody uses messages#index controller even the user who entered the controller!
Im searching for a solution so that i can only inform specific users!
In all my controlles i have #current_user but i dont really know how i should avoid that he also gets a notifcation about what he is actually doing!
One possible solution would be that i sort the notifications with jquery at the frontend but this also would mean that you can spyout notifications that are privat.
Another solution is that every user has its on channel but i dont know i this really makes sens and how i should transpose it!
Here is my actual code: Thanks!
def events
response.headers["Content-Type"] = "text/event-stream"
redis = Redis.new
redis.subscribe('gaga') do |on|
on.message do |event, data|
response.stream.write("data: #{data }\n\n")
end
end
rescue IOError
logger.info "Stream closed"
ensure
response.stream.close
end
def index
#message = Message.new
#departments = Department.all.where.not(id: #current_department.id).last(4)
$redis = Redis.new
data = {"user" => #current_user.name}
$redis.publish "gaga", data.merge('msg' => "#{#current_user.name} entered messages index").to_json
end
And forntend:
source = new EventSource('/formular')
source.addEventListener 'gaga', (e) ->
alert e
Had this problem :)
We solved it by using a private channel
The ActionController::Live component doesn't know who is listening to whatever it sends, so the best way to keep your code efficient is to send to private channels which are dependent on the user
Here's an example from our live code. It uses Pusher, which is websockets, but is the same principle:
#app/controllers/controller.rb
Pusher['private-user-' + current_user.id.to_s].trigger('my_event', {
message: "Removed"
})
#app/assets/javascripts/javascript.js
channel = pusher.subscribe("private-user-#{gon.user_id}")
channel.bind "my_event", (data) ->
alert data.message