1

Maybe it is a good example for server push system. There are many users in the system, and users can talk with each other. It can be accomplished like this: one user sends message(through websocket) to the server, then the server forward the message to the other user. The key is to find the binding between the ws(websocket object) and the user. The example code like below:

EM.run {
  EM::WebSocket.run(:host => "0.0.0.0", :port => 8080, :debug => false) do |ws|
    ws.onopen { |handshake|
      # extract the user id from handshake and store the binding between user and ws
    }
    ws.onmessage { |msg|
      # extract the text and receiver id from msg
      # extract the ws_receiver from the binding
      ws_receiver.send(text)
    }
  end
}

I want to figure out following issues:

  1. The ws object can be serialized so it can be stored into disk or database? Otherwise I can only store the binding into memory.

  2. What the differences between em-websocket and websocket-rails?

  3. Which gem do you recommend for websocket?

Run
  • 876
  • 6
  • 21

2 Answers2

1

You're approaching a use case that websockets are pretty good for, so you're on the right track.

  1. You could serialize the ws object with Marshal, but think of websocket objects as being a bit like http request objects in that they are abstractions for a type of communication. You are probably best off marshaling/storing the data.
  2. em-websocket is a lower(ish) lever websocket library built more or less directly on web-machine. websocket-rails is a higher level abstraction on websockets, with a lot of nice tools built in and pretty ok docs. It is built on top of faye-websocket-rails which is itself built on web machine. *Note, action cable which is the new websocket library for Rails 5 is built on faye.
  3. I've use websocket-rails in the past and rather like it. It will take care of a lot for you. However, if you can use Rails 5 and Action Cable, do that, its the future.
Chase
  • 2,748
  • 2
  • 23
  • 32
  • 2
    Thank you for sharing your experiences. I can find the trend of the websocket in rails from your answer. Maybe rails 5 is being developed, I still use rails 4.2.5. But the rails 5 is the trend so finally I will use it with Action Cable. Thank you! – Run Dec 29 '15 at 08:53
  • 1
    You might do well trying websocket-rails or faye, as they will both get you in the right frame of reference for Rails 5. – Chase Dec 29 '15 at 16:22
  • @ChaseGilliam - I noticed that websocket-rails hand't been updated since Jun 5, 2014... Rails moved from 4.1.x to 4.2.x since then (although I have no idea how much that might effect the integrability of the gem)... But it might be that Faye, em-websocket, ActionCable and Plezi are the only games in town. – Myst Dec 29 '15 at 17:46
1

The following is in addition to Chase Gilliam's succinct answer which included references to em-websocket, websocket-rails (which hadn't been maintained in a long while), faye-websocket-rails and ActionCable.

I would recommend the Plezi framework. It works both as an independent application framework as well as a Rails Websocket enhancement.

I would consider the following points as well:

  1. do you need the message to persist between connections (i.e. if the other user if offline, should the message wait in a "message box"? for how long should the message wait?)...?

  2. Do you wish to preserve message history?

These points would help yo decide if to use a persistent storage (i.e. a database) for the messages or not.

i.e., to use Plezi with Rails, create an init_plezi.rb in your application's config/initializers folder. use (as an example) the following code:

class ChatDemo
    # use JSON events instead of raw websockets
    @auto_dispatch = true
    protected #protected functions are hidden from regular Http requests
    def auth msg
        @user = User.auth_token(msg['token'])
        return close unless @user
        # creates a websocket "mailbox" that will remain open for 9 hours.
        register_as @user.id, lifetime: 60*60*9, max_connections: 5
    end
    def chat msg, received = false
        unless @user # require authentication first
           close
           return false
        end
        if received
           # this is only true when we sent the message
           # using the `broadcast` or `notify` methods
           write msg # writes to the client websocket
        end
        msg['from'] = @user.id
        msg['time'] = Plezi.time # an existing time object
        unless msg['to'] && registered?(msg['to'])
           # send an error message event
           return {event: :err, data: 'No recipient or recipient invalid'}.to_json
        end
        # everything was good, let's send the message and inform
        # this will invoke the `chat` event on the other websocket
        # notice the `true` is setting the `received` flag.
        notify msg['to'], :chat, msg, true
        # returning a String will send it to the client
        # when using the auto-dispatch feature
        {event: 'message_sent', msg: msg}.to_json
    end
end
# remember our route for websocket connections.
route '/ws_chat', ChatDemo
# a route to the Javascript client (optional)
route '/ws/client.js', :client

Plezi sets up it's own server (Iodine, a Ruby server), so remember to remove from your application any references to puma, thin or any other custom server.

On the client side you might want to use the Javascript helper provided by Plezi (it's optional)... add:

<script src='/es/client.js' />
<script>

    TOKEN = <%= @user.token %>;
    c = new PleziClient(PleziClient.origin + "/ws_chat") // the client helper
    c.log_events = true // debug
    c.chat = function(event) {
       // do what you need to print a received message to the screen
       // `event` is the JSON data. i.e.: event.event == 'chat'           
    }
    c.error = function(event) {
       // do what you need to print a received message to the screen
       alert(event.data);
    }
    c.message_sent = function(event) {
       // invoked after the message was sent
    }
    // authenticate once connection is established
    c.onopen = function(event) {
       c.emit({event: 'auth', token: TOKEN});
    }
    //  //  to send a chat message:
    //  c.emit{event: 'chat', to: 8, data: "my chat message"}
</script>

I didn't test the actual message code because it's just a skeleton and also it requires a Rails app with a User model and a token that I didn't want to edit just to answer a question (no offense).

Community
  • 1
  • 1
Myst
  • 18,516
  • 2
  • 45
  • 67