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>

Готово.

Другие вопросы по тегам