igrigorik / em-websocket

EventMachine based WebSocket server
http://www.igvita.com/2009/12/22/ruby-websockets-tcp-for-the-browser/
MIT License
1.69k stars 187 forks source link

Help request: how to store attributes against a specific websocket #143

Closed AJFaraday closed 8 years ago

AJFaraday commented 8 years ago

I've written this minimal server based on examples/multicast.rb

require 'em-websocket'

EventMachine.run {
  @channel = EventMachine::Channel.new

  EM::WebSocket.start(:host => "0.0.0.0", :port => 8080) do |ws|
    ws.onopen { |handshake|
      sid = @channel.subscribe { |msg| ws.send msg }
      puts "WebSocket connection open"
      ws.send "Hello Client, you connected to #{handshake.path}"
    }

    ws.onclose {
      puts "Connection closed"
      @channel.unsubscribe(sid)
    }

    ws.onmessage { |msg|
      puts msg
      @channel.push "<#{sid}>: #{msg}"
    }
  end
}

However, this errors on the first message sent from a socket, because the sid attribute doesn't persist between onopen and onmessage.

server.rb:24:in `block (3 levels) in <main>': undefined local variable or method `sid' for main:Object (NameError)

If I create the attribute as an instance variable, the second socket opened overrides @sid for the first.

It looks, from the example, like it was expected to persist between blocks, in the scope of a single web socket. Does anyone know how I can set a variable within this scope?

AJFaraday commented 8 years ago

I found the solution, and I could kick myself for raising the issue. Just in case anyone finds this here's what the problem was.

The local variables will persist if the onclose and onmessage events are set within the onopen block.

e.g.

require 'em-websocket'

EventMachine.run {
  @channel = EventMachine::Channel.new

  EM::WebSocket.start(:host => "0.0.0.0", :port => 8080) do |ws|
    ws.onopen { |handshake|
      sid = @channel.subscribe { |msg| ws.send msg }
      puts "WebSocket connection open"
      ws.send "Hello Client, you connected"

      ws.onclose {
        puts "Connection closed"
        @channel.unsubscribe(sid)
      }

      ws.onmessage { |msg|
        puts msg
        @channel.push "<#{sid}>: #{msg}"
      }
    }
  end
}

This script will send any message by any open websocket out to every websocket which is currently connected.