Class: Strelka::WebSocketServer

Inherits:
Mongrel2::Handler
  • Object
show all
Extended by:
Discovery, MethodUtilities, PluginLoader
Defined in:
lib/strelka/websocketserver.rb

Overview

WebSocket (RFC 6455) Server base class.

class ChatServer < Strelka::WebSocketServer

    # Set up a Hash for participating users
    def initialize( * )
        super
        @users = {}
    end

    # Disconnect clients that don't answer a ping
    plugin :heartbeat
    heartbeat_rate 5.0
    idle_timeout 15.0

    # When a websocket is set up, add a new user to the table, but without a nick.
    def handle_websocket_handshake( request )
        @users[ request.socket_id ] = nil
        return request.response # accept the connection
    end

    plugin :routing

    # Handle incoming commands, which should be text frames
    on_text do |request|
        senderid = request.socket_id
        data = request.payload.read

        # If the input starts with '/', it's a command (e.g., /quit, /nick, etc.)
        output = nil
        if data.start_with?( '/' )
            output = self.command( senderid, data[1..-1] )
        else
            output = self.say( senderid, data )
        end

        response = request.response
        response.puts( output )
        return response
    end

end # class ChatServer

Defined Under Namespace

Modules: Heartbeat, Routing

Instance Attribute Summary collapse

Attributes included from PluginLoader

#loaded_plugins, #plugin_path_prefix, #plugins_installed_from

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Discovery

add_inherited_class, app_discovery_files, discover_data_dirs, discovered_apps, discovered_classes, inherited, load, load_file, loading_file, loading_file, register_app, register_apps

Methods included from MethodUtilities

attr_predicate, attr_predicate_accessor, singleton_attr_accessor, singleton_attr_reader, singleton_attr_writer, singleton_method_alias, singleton_predicate_accessor, singleton_predicate_reader

Methods included from PluginLoader

application_stack, dump_application_stack, extended, inherited, install_plugins, load_plugin, new, plugins, plugins_installed?, register_plugin

Constructor Details

#initializeWebSocketServer

Dump the application stack when a new instance is created.



111
112
113
114
115
116
117
118
# File 'lib/strelka/websocketserver.rb', line 111

def initialize( * )
	self.class.dump_application_stack

	@connections = Hash.new {|h, k| h[k] = Set.new }
	@connection_times = Hash.new {|h, k| h[k] = Hash.new }

	super
end

Instance Attribute Details

#connection_timesObject (readonly)

A Hash of [sender ID, connection ID] keys => connection Times



131
132
133
# File 'lib/strelka/websocketserver.rb', line 131

def connection_times
  @connection_times
end

#connectionsObject (readonly)

A Hash of sender ID => Set of connection IDs.



127
128
129
# File 'lib/strelka/websocketserver.rb', line 127

def connections
  @connections
end

Class Method Details

.default_app_instanceObject

Return an instance of the App configured for the handler in the currently-loaded Mongrel2 config that corresponds to the #default_appid.



99
100
101
102
# File 'lib/strelka/websocketserver.rb', line 99

def self::default_app_instance
	appid = self.default_appid
	return self.app_instance_for( appid )
end

.default_appidObject

Calculate a default application ID for the class based on either its ID constant or its name and return it.



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/strelka/websocketserver.rb', line 80

def self::default_appid
	self.log.info "Looking up appid for %p" % [ self.class ]
	appid = nil

	if self.const_defined?( :ID )
		appid = self.const_get( :ID )
		self.log.info "  app has an ID: %p" % [ appid ]
	else
		appid = ( self.name || "anonymous#{self.object_id}" ).downcase
		appid.gsub!( /[^[:alnum:]]+/, '-' )
		self.log.info "  deriving one from the class name: %p" % [ appid ]
	end

	return appid
end

.run(appid = nil) ⇒ Object

Overridden from Mongrel2::Handler – use the value returned from .default_appid if one is not specified.



71
72
73
74
75
# File 'lib/strelka/websocketserver.rb', line 71

def self::run( appid=nil )
	appid ||= self.default_appid
	self.log.info "Starting up with appid %p." % [ appid ]
	super( appid )
end

Instance Method Details

#broadcast(frame, except: nil) ⇒ Object

Send the specified frame to all current connections, except those listed in except. The except argument is a single [sender_id, conn_id] tuple.



195
196
197
198
199
200
201
202
203
204
205
# File 'lib/strelka/websocketserver.rb', line 195

def broadcast( frame, except: nil )
	self.connections.each do |sender_id, conn_ids|
		id_list = conn_ids.to_a.
			reject {|cid| except&.first == sender_id && except&.last == cid }

		self.log.debug "Broadcasting to %d connections for sender %s" %
			[ conn_ids.length, sender_id ]

		self.conn.broadcast( sender_id, id_list, frame.to_s )
	end
end

#handle_disconnect(request) ⇒ Object

Handle a disconnect notice from Mongrel2 via the given request. Its return value is ignored.



175
176
177
178
179
180
181
182
# File 'lib/strelka/websocketserver.rb', line 175

def handle_disconnect( request )
	self.log.info "Connection %d closed." % [ request.conn_id ]
	self.connection_times[ request.sender_id ].delete( request.conn_id )
	self.connections.delete( request.sender_id )
	self.log.debug "  connections remaining: %p" % [ self.connections ]

	return nil
end

#handle_websocket(request) ⇒ Object

Handle a WebSocket frame in request. If not overridden, WebSocket connections are closed with a policy error status.



146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/strelka/websocketserver.rb', line 146

def handle_websocket( request )
	response = nil

	self.connection_times[ request.sender_id ][ request.conn_id ] = Time.now

	# Dispatch the request
	response = catch( :close_websocket ) do
		self.log.debug "Incoming WEBSOCKET request (%p):%s" % [ request, request.headers.path ]
		self.handle_websocket_request( request )
	end

	return response
end

#handle_websocket_handshake(handshake) ⇒ Object

Handle a WebSocket handshake HTTP request. :TODO: Register/check for supported Sec-WebSocket-Protocol.



163
164
165
166
167
168
169
170
# File 'lib/strelka/websocketserver.rb', line 163

def handle_websocket_handshake( handshake )
	self.log.info "Incoming WEBSOCKET_HANDSHAKE request (%p)" % [ handshake.headers.path ]
	self.connections[ handshake.sender_id ].add( handshake.conn_id )
	self.connection_times[ handshake.sender_id ][ handshake.conn_id ] = Time.now
	self.log.debug "  connections: %p" % [ self.connections ]

	return handshake.response( handshake.protocols.first )
end

#last_connection_time(request) ⇒ Object

Return the Time of the last frame from the client associated with the given request.



187
188
189
190
# File 'lib/strelka/websocketserver.rb', line 187

def last_connection_time( request )
	table = self.connection_times[ request.sender_id ] or return nil
	return table[ request.conn_id ]
end

#runObject

Run the app – overriden to set the process name to something interesting.



136
137
138
139
140
141
# File 'lib/strelka/websocketserver.rb', line 136

def run
	procname = "%s %s: %p %s" % [ RUBY_ENGINE, RUBY_VERSION, self.class, self.conn ]
	$0 = procname

	super
end