I have a chat application here . I was going through the wiki of WebSockets . There the code is described in the ChatController like this :
class ChatController < WebsocketRails::BaseController
def initialize_session
# perform application setup here
controller_store[:message_count] = 0
end
end
My Question : how should i implement this in my chatcontroller
which is exteding the ApplicationController
? .
Whether i should create a new controller for using the websockets
or modify the existing chatcontroller
which should extend the WebsocketRails
? . I am new to WebSockets so any help regarding this will really help .
Thank you .
The answer, with relation to the websocket-rails gem, as well as Faye and Plezi is YES, you have to create a new controller class, different than the one used by Rails.
Your websocket-rails must inherit WebsocketRails::BaseController
, while your Rails controller must inherit ActionController::Base
(usually by inhering you ApplicationController, which inherits this class).
Ruby doesn't support double class inheritance (although Mixins are possible when using modules).
Faye, on the other hand doesn't use the Controller in the same object oriented way and there are more options there. For instance, you COULD map websocket events to your controller CLASS methods, but you might have an issue initializing a Rails controller for each websocket connection, since some of the Controller's internal mechanisms might break. Session info, for instance, will NOT be available and you would probably have to avoid any and all Rails specific methods.
With Plezi, these inheritance issues don't exit, but Plezi automatically creates Http routes to any public methods your Controller has - so your Rails methods will be exposed in a way you didn't intend. Plezi Controllers, on the other hand, can answer both Http and Websockets.
The reason I wrote also about Faye and Plezi is because the websocket-rails gem had last been updated (according the the CHANGELOG) on March 2014...
I don't know how well it will survive (or had survived) the latest updates to Rails, but I would recommend moving on.
For now, November 2015, Faye is the more common option and Plezi is a new player in the field. I'm Plezi's author.
Both Faye and Plezi should allow one user to send a message to another.
I think Plezi is easiyer to use, but this is because I wrote Plezi. I am sure the person that wrote Faye will think that Faye is easier.
There are a few options about the best way(s) to implement a chat, depending on how you want to do so.
You can look at the plezi application demo code if you install Plezi and run (in your terminal):
$ plezi mini my_chat
Here's a quick hack for adding Plezi websocket broadcasting to your existing application.
If I had access to your database, I would have done it a little differently... but it's good enough as a proof of concept... for now.
Add the following line to your Gemfile
:
gem 'plezi'
Create a plezi_init.rb
file and add it to your config/initializers
folder. Here is what it hold for now (most of it is hacking the Rails cookie, because I don't have access to your database and I can't add fields):
class WebsocketController
def on_open
# this is a Hack - replace this with a database token and a cookie.
return close unless cookies[:_linkedchats_session] # refuse unauthenticated connections
# this is a Hack - get the user
@user_id = decrypt_session_cookie(cookies[:_linkedchats_session].dup)['warden.user.user.key'][0][0].to_s
puts "#{@user_id} is connected"
end
def on_message data
# what do you want to do when you get data?
end
protected
# this will inform the user that a message is waiting
def message_waiting msg
write(msg.to_json) if msg[:to].to_s == @user_id.to_s
end
# this is a Hack - replace this later
# use a token authentication instead (requires a database field)
def decrypt_session_cookie(cookie)
key ='4f7fad1696b75330ae19a0eeddb236c123727f2a53a3f98b30bd0fe33cfc26a53e964f849d63ad5086483589d68c566a096d89413d5cb9352b1b4a34e75d7a7b'
cookie = CGI::unescape(cookie)
# Default values for Rails 4 apps
key_iter_num = 1000
key_size = 64
salt = "encrypted cookie"
signed_salt = "signed encrypted cookie"
key_generator = ActiveSupport::KeyGenerator.new(key, iterations: key_iter_num)
secret = key_generator.generate_key(salt)
sign_secret = key_generator.generate_key(signed_salt)
encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: JSON)
encryptor.decrypt_and_verify(cookie)
end
end
# IMPORTANT - create the Plezi, route for websocket connections
Plezi.route '/ws', WebsocketController
That's almost all the Plezi application you need for this one.
Just add the following line in to your ChatsController#create
method, just before the respond_to
:
WebsocketController.broadcast :message_waiting,
from: @msg.sender_id,
to: @msg.receiver_id,
text: @msg.text,
msg: :chat
That's it for the server... Now, the client.
Add the following script to your chat.html.erb
template (or, because turbo-links might mess up your script's initialization, add the script to your application.js
file... but you will be refusing a lot of connections until your users log in):
<script type="text/javascript">
// Your websocket URI should be an absolute path. The following sets the base URI.
// remember to update to the specific controller's path to your websocket URI.
var ws_controller_path = '/ws'; // change to '/controller/path'
var ws_uri = (window.location.protocol.match(/https/) ? 'wss' : 'ws') + '://' + window.document.location.host + ws_controller_path
// websocket variable.
var websocket = NaN
// count failed attempts
var websocket_fail_count = 0
// to limit failed reconnection attempts, set this to a number.
var websocket_fail_limit = NaN
function init_websocket()
{
if(websocket && websocket.readyState == 1) return true; // console.log('no need to renew socket connection');
websocket = new WebSocket(ws_uri);
websocket.onopen = function(e) {
// reset the count.
websocket_fail_count = 0
// what do you want to do now?
};
websocket.onclose = function(e) {
// If the websocket repeatedly you probably want to reopen the websocket if it closes
if(!isNaN(websocket_fail_limit) && websocket_fail_count >= websocket_fail_limit) {
// What to do if we can't reconnect so many times?
return
};
// you probably want to reopen the websocket if it closes.
if(isNaN(websocket_fail_limit) || (websocket_fail_count <= websocket_fail_limit) ) {
// update the count
websocket_fail_count += 1;
// try to reconect
init_websocket();
};
};
websocket.onerror = function(e) {
// update the count.
websocket_fail_limit += 1
// what do you want to do now?
};
websocket.onmessage = function(e) {
// what do you want to do now?
console.log(e.data);
msg = JSON.parse(e.data)
alert("user id: " + msg.from + " said:\n" + msg.text)
};
}
// setup the websocket connection once the page is done loading
window.addEventListener("load", init_websocket, false);
</script>
Done.