WebSockets в Rails: нужно ли создавать новый WebSocketController в нашем существующем приложении при использовании веб-сокетов?
У меня есть приложение чата здесь. Я шел через вики WebSockets . Там код описан в ChatController следующим образом:
class ChatController < WebsocketRails::BaseController
def initialize_session
# perform application setup here
controller_store[:message_count] = 0
end
end
Мой вопрос: как мне реализовать это в моем chatcontroller
который выходит за пределы ApplicationController
?, Должен ли я создать новый контроллер для использования websockets
или изменить существующий chatcontroller
который должен продлить WebsocketRails
?, Я новичок в WebSockets, поэтому любая помощь по этому вопросу действительно поможет.
Спасибо.
1 ответ
Ответ, касающийся драгоценного камня websocket-rails, а также Faye и Plezi - ДА, вы должны создать новый класс контроллера, отличный от того, который используется Rails.
Ваши рельсы должны быть унаследованы WebsocketRails::BaseController
в то время как ваш контроллер Rails должен наследовать ActionController::Base
(обычно наследуя ваш ApplicationController, который наследует этот класс).
Ruby не поддерживает наследование двойного класса (хотя Mixins возможны при использовании модулей).
Фэй, с другой стороны, не использует контроллер в том же объектно-ориентированном виде, и там есть больше вариантов. Например, вы МОЖЕТЕ сопоставить события веб-сокета с методами контроллера CLASS, но у вас может возникнуть проблема с инициализацией контроллера Rails для каждого соединения веб-сокета, поскольку некоторые внутренние механизмы контроллера могут выйти из строя. Например, информация о сеансе НЕ будет доступна, и вам, вероятно, придется избегать любых методов Rails.
С Plezi эти проблемы наследования не исчезают, но Plezi автоматически создает маршруты Http для любых открытых методов, которые есть у вашего Контроллера - так что ваши методы Rails будут отображаться так, как вы не предполагали. Контроллеры Plezi, с другой стороны, могут отвечать как Http, так и Websockets.
Причина, по которой я также написал о Фэй и Плези, заключается в том, что камень websocket-rails последний раз обновлялся (согласно CHANGELOG) в марте 2014 года...
Я не знаю, насколько хорошо он переживет (или пережил) последние обновления Rails, но я бы порекомендовал двигаться дальше.
На данный момент, ноябрь 2015 года, Faye - более распространенный вариант, а Plezi - новый игрок на поле. Я автор Плези.
Изменить (отвечая на комментарий)
И Фэй, и Плези должны позволять одному пользователю отправлять сообщения другому.
Я думаю, что Plezi проще в использовании, но это потому, что я написал Plezi. Я уверен, что человек, который написал Фэй, будет думать, что Фэй легче.
В зависимости от того, как вы хотите это сделать, есть несколько вариантов того, как лучше всего реализовать чат.
Вы можете посмотреть демонстрационный код приложения Plezi, если вы установите Plezi и запустите (в своем терминале):
$ plezi mini my_chat
Вот быстрый взлом для добавления трансляции Plezi websocket в ваше существующее приложение.
Если бы у меня был доступ к вашей базе данных, я бы сделал это немного по-другому... но это достаточно хорошо в качестве доказательства концепции... пока.
Добавьте следующую строку в ваш Gemfile
:
gem 'plezi'
Создать plezi_init.rb
файл и добавьте его в свой config/initializers
папка. Вот что он сейчас держит (в основном это взлом куки-файла Rails, потому что у меня нет доступа к вашей базе данных и я не могу добавить поля):
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
Это почти все приложение Plezi, которое вам нужно для этого.
Просто добавьте следующую строку в ваш ChatsController#create
метод, непосредственно перед respond_to
:
WebsocketController.broadcast :message_waiting,
from: @msg.sender_id,
to: @msg.receiver_id,
text: @msg.text,
msg: :chat
Вот и все для сервера... Теперь клиент.
Добавьте следующий скрипт к вашему chat.html.erb
шаблон (или, поскольку турбо-ссылки могут испортить инициализацию вашего скрипта, добавьте скрипт в ваш application.js
файл... но вы будете отказываться от множества подключений, пока ваши пользователи не войдут в систему):
<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>
Готово.