I'm currently using Rails 6 and Linux mint Cinnamon 20.2, and I have problems with Redis configuration / setupping in my Rails application.
My problem is that my ActionCable doesn't work well.
I would like to build a real time chat app, I created a channel, called "Room".
Here how my room_channel.js file looks like:
import consumer from "./consumer"
consumer.subscriptions.create({ channel: "RoomChannel", room_id: 3 }, {
connected() {
console.log('Connected successfully!')
},
disconnected() {
// Called when the subscription has been terminated by the server
},
received(data) {
console.log(data)
}
});
Here's my room_channel.rb:
class RoomChannel < ApplicationCable::Channel
def subscribed
stream_from "room_channel_#{params[:room_id]}"
end
def unsubscribed
# Any cleanup needed when channel is unsubscribed
end
end
Here's the important segment of my messages_controller.rb file (The ActionCable command is the important):
def create
#message = Message.new(message_params)
#message.user = User.generate
#message.save
ActionCable.server.broadcast("room_channel_#{#message.room_id}",{message:"hello"})
end
When I join to Room 3 (3 is the ID of that room), I get my "Connected successfully" message, but when I send a message, it doesn't output "hello" in the console. It means it doesn't receive successfully datas, right?
What can i do with Redis to be able to receive data?
Finally, I changed the development section of my config/cable.yml file to, but it doesn't solved anything :(.
development:
adapter: redis
url: redis://localhost:6379/1
Anybody can help me out with this?
Thanks for your help!
Related
We facing issues with actioncable. The issue is , Client is getting disconnected due to internet issue but that status not communicated to the server(disconnect event not calling)
The below method is failing due to inactive client,
ActionCable.server.broadcast "project_123_channel", {
"request" => "show_title"
}
Ruby version:2.5.3p105
Rails version:5.2.3
actin cable version: 5.2.3
disconnect callback:
App.live_data = App.cable.subscriptions.create({ channel: "ProjectDataChannel", project_id: project_id }, {
collection: function() {
},
connected: function() {
console.log("Live data connected");
},
disconnected: function() {
console.log("disconnected");
return this.perform('unfollow');
}
But this disconnect callback not working.
Is there any way to detect the client active status from server side? or there any callback to track the disconnect status if the client is disconnect due to internet kind of things
This is a known issue.
Here is a blog post from Vlad Dyachenko that describes the problem and a solution...
https://wowinter13.medium.com/actioncable-handling-client-connection-errors-on-server-side-93ea74178d03
This is the code solution from the post...
module ApplicationCable
class Channel < ActionCable::Channel::Base
after_subscribe :connection_monitor
CONNECTION_TIMEOUT = 10.seconds
CONNECTION_PING_INTERVAL = 5.seconds
periodically every: CONNECTION_PING_INTERVAL do
#driver&.ping
if Time.now - #_last_request_at > #_timeout
connection.disconnect
end
end
def connection_monitor
#_last_request_at ||= Time.now
#_timeout = CONNECTION_TIMEOUT
#driver = connection.instance_variable_get('#websocket').possible?&.instance_variable_get('#driver')
#driver.on(:pong) { #_last_request_at = Time.now }
end
end
end
It's worked fine as suggestion given by #dbugger
A vanilla Rails 6 application is giving this error:
"WebSocket connection to 'ws://localhost:3000/cable' failed: WebSocket is closed due to suspension"
Actioncable Channel:
#app/javascript/channels/chat_channel.js
import consumer from "./consumer"
consumer.subscriptions.create("ChatChannel", {
connected() {
// Called when the subscription is ready for use on the server
},
disconnected() {
// Called when the subscription has been terminated by the server
},
received(data) {
console.log(data)
// Called when there's incoming data on the websocket for this channel
}
});
Rails Controller:
#app/controller/messages_controller:
puts "*******Broadcasting"
ActionCable.server.broadcast("chat_channel", content: #message.id)
ActionCable Channel:
# app/channele/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
def subscribed
puts "**** ChatChannel Subscribed"
stream_from "chat_channel"
end
def unsubscribed
# Any cleanup needed when channel is unsubscribed
end
end
Where did I slip up?
Make sure in your routes.rb has this mount ActionCable.server => '/cable'
And in your development.rb file
config.action_cable.url = "ws://localhost:3000/cable"
config.action_cable.allowed_request_origins = [/http:\/\/*/,
/https:\/\/*/]
I currently have a Rails 5 application acting as my backend, we can call this the "Core." I also have another Rails 5 application acting as my frontend, which is serving up AngularJS client side, we can call this the "Front". These are two completely separate Rails 5 applications with completely different domains.
Basically, I am trying to integrate Action Cable through the Core and have it talk to the Front. I'm using this service here for the Front: https://www.npmjs.com/package/angular-actioncable. As far as the Core, that's just basic Action Cable set up.
Problem: I'm having some trouble getting the handshake to work across two separate domains. I've read through everything I can find online, unfortunately there is not much information. If you've done this before, please help!
Note: I do have Redis server running and I'm using separate ports to mimic the separate domains in development.
Core:
chat_channel.rb
class ChatChannel < ApplicationCable::Channel
def subscribed
stream_from 'http://localhost:2000/#/chat'
end
def unsubscribed
stop_all_streams
end
def receive(data)
ActionCable.server.broadcast('http://localhost:2000/#/chat', data)
end
def speak
params.fetch('data').fetch('chat')
end
end
route.js
mount ActionCable.server => '/cable'
cable.yml
development:
adapter: redis
url: redis://localhost:6379
test:
adapter: async
production:
adapter: redis
url: redis://localhost:6379
config/environments.rb
config.action_cable.disable_request_forgery_protection = true
Front:
ChatCtrl.js
app.controller('ChatCtrl', ['$scope', 'ActionCableChannel',
function($scope, ActionCableChannel) {
$scope.inputText;
$scope.chatData = [];
var consumer = new ActionCableChannel("ChatChannel");
var callback = function(message) {
$scope.chatData.push(message);
};
consumer.subscribe(callback).then(function() {
$scope.sendToMyChannel = function(message) {
consumer.send(message, 'speak');
};
$scope.$on("$destroy", function() {
consumer.unsubscribe().then(function() {
$scope.sendToMyChannel = undefined;
});
});
});
}
]);
// Action Cable Configuration
app.run(function (ActionCableConfig) {
ActionCableConfig.wsUri = 'localhost:4000';
});
Error Message in Console:
Try
ActionCableConfig.wsUri = 'ws://localhost:4000';
I have two websocket clients, and I want to exchange information between them.
Let's say I have two instances of socket servers, and 1st is retrieve private information, filter it and send to the second one.
require 'em-websocket'
EM.run do
EM::WebSocket.run(host: '0.0.0.0', port: 19108) do |manager_emulator|
# retrieve information. After that I need to send it to another port (9108)
end
EM::WebSocket.run(host: '0.0.0.0', port: 9108) do |fake_manager|
# I need to send filtered information here
end
end
I've tried to do something, but I got usual dark code and I don't know how to implement this functionality.
I'm not sure how you would do that using EM.
I'm assuming you will need to have the fake_manager listen to an event triggered by the manager_emulator.
It would be quite easy if you'd be using a websocket web-app framework. For instance, on the Plezi web-app framework you could write something like this:
# try the example from your terminal.
# use http://www.websocket.org/echo.html in two different browsers to observe:
#
# Window 1: http://localhost:3000/manager
# Window 2: http://localhost:3000/fake
require 'plezi'
class Manager_Controller
def on_message data
FakeManager_Controller.broadcast :_send, "Hi, fake! Please do something with: #{data}\r\n- from Manager."
true
end
def _send message
response << message
end
end
class FakeManager_Controller
def on_message data
Manager_Controller.broadcast :_send, "Hi, manager! This is yours: #{data}\r\n- from Fake."
true
end
def _send message
response << message
end
end
class HomeController
def index
"use http://www.websocket.org/echo.html in two different browsers to observe this demo in action:\r\n" +
"Window 1: http://localhost:3000/manager\r\nWindow 2: http://localhost:3000/fake\r\n"
end
end
# # optional Redis URL: automatic broadcasting across processes or machines:
# ENV['PL_REDIS_URL'] = "redis://username:password#my.host:6379"
# starts listening with default settings, on port 3000
listen
# Setup routes:
# They are automatically converted to the RESTful route: '/path/(:id)'
route '/manager', Manager_Controller
route '/fake', FakeManager_Controller
route '/', HomeController
# exit terminal to start server
exit
Good Luck!
P.S.
If you're going to keep to EM, you might consider using Redis to push and subscribe to events between the two ports.
I've found a way how to do it through em-websocket gem! You need just define variables outside of eventmachine block. Something like that
require 'em-websocket'
message_sender = nil
EM.run do
# message sender
EM::WebSocket.run(host: '0.0.0.0', port: 19108) do |ws|
ws.onopen { message_sender = ws }
ws.onclose { message_sender = nil }
end
# message receiver
EM::WebSocket.run(host: '0.0.0.0', port: 9108) do |ws|
ws.onmessage { |msg| message_sender.send(msg) if message_sender }
end
end
There are three parts in my Rails application:
A part which listens for HTML5 web sockets (em-websocket)
a piece of JavaScript which connects to them
and a part which task is to connect to these sockets from inside of the same web application (em-websocket-client) (Yes, I am trying to do some IPC in Phusion Passenger environment)
JavaScript code connects fine and Web sockets server is happy with such a client, but when I connect from em-websocket-client a strange thing is happening: onclose handler is being called without calling onopen, and moreover - it is called for a socket which had been opened by web browser, not em-websocket-client.
The same em-websocket-client code, when executed in separate Ruby script through command line, works as planned. Here is the sample of em-websocket-client code:
require 'em-websocket-client'
class WebSocketsClient
def initialize
Thread.new do
log 'In a thread'
EventMachine.run do
log 'EM run'
#conn = EventMachine::WebSocketClient.connect("ws://localhost:5050?user_id=1&page_token=JYUTbfYDTTliglififi")
#conn.callback do
log 'Callback'
#conn.send_msg({ message_type: 'phone_call', user_id: 1, order_id: 1}.to_json)
#conn.close_connection
end
#conn.errback do |e|
log 'Errback'
puts "Got error: #{e}"
end
#conn.stream do |msg|
#log 'Stream'
#puts "<#{msg}>"
#if msg.data == 'done'
# #conn.close_connection
#end
end
#conn.disconnect do
puts 'gone'
EventMachine::stop_event_loop
end
end
end
end
def send_phone_call(order_id, user_id)
#conn.send_msg({ message_type: 'phone_call', user_id: user_id, order_id: order_id}.to_json)
end
def log(text)
puts "WebSocketsClient: #{text}\n"
end
end
WebSocketsClient.new
onclose on server side is being callsd as soon as EventMachine::WebSocketClient.connect is executed on client side. It doesn't even come to #conn.disconnect call.
One more thing I can conjecture is that this behaviour is due to using same EventMachine mechanism by server and client inside of same Rails application.