strelka 0.15.0 → 0.16.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/ChangeLog +3293 -3058
- data/History.rdoc +17 -0
- data/Manifest.txt +3 -0
- data/Rakefile +2 -2
- data/contrib/hoetemplate/lib/file_name.rb.erb +3 -2
- data/contrib/hoetemplate/spec/file_name_spec.rb.erb +1 -1
- data/examples/apps/auth-demo +1 -2
- data/examples/apps/auth-demo2 +1 -2
- data/examples/apps/sessions-demo +1 -2
- data/examples/gen-config.rb +1 -2
- data/lib/strelka.rb +92 -17
- data/lib/strelka/app.rb +7 -6
- data/lib/strelka/app/auth.rb +5 -5
- data/lib/strelka/app/errors.rb +1 -1
- data/lib/strelka/app/filters.rb +1 -1
- data/lib/strelka/app/negotiation.rb +1 -1
- data/lib/strelka/app/parameters.rb +1 -1
- data/lib/strelka/app/restresources.rb +14 -21
- data/lib/strelka/app/routing.rb +5 -6
- data/lib/strelka/app/sessions.rb +3 -1
- data/lib/strelka/app/templating.rb +1 -1
- data/lib/strelka/authprovider.rb +1 -1
- data/lib/strelka/authprovider/basic.rb +1 -0
- data/lib/strelka/authprovider/hostaccess.rb +1 -0
- data/lib/strelka/behavior/plugin.rb +2 -2
- data/lib/strelka/cli.rb +2 -1
- data/lib/strelka/command/config.rb +2 -1
- data/lib/strelka/command/discover.rb +2 -1
- data/lib/strelka/command/start.rb +2 -1
- data/lib/strelka/constants.rb +1 -1
- data/lib/strelka/cookie.rb +1 -1
- data/lib/strelka/cookieset.rb +1 -1
- data/lib/strelka/discovery.rb +1 -1
- data/lib/strelka/httprequest.rb +4 -4
- data/lib/strelka/httprequest/acceptparams.rb +1 -1
- data/lib/strelka/httprequest/auth.rb +3 -1
- data/lib/strelka/httprequest/negotiation.rb +1 -1
- data/lib/strelka/httprequest/session.rb +3 -1
- data/lib/strelka/httpresponse.rb +2 -3
- data/lib/strelka/httpresponse/negotiation.rb +1 -1
- data/lib/strelka/httpresponse/session.rb +1 -1
- data/lib/strelka/mixins.rb +26 -5
- data/lib/strelka/multipartparser.rb +3 -3
- data/lib/strelka/paramvalidator.rb +4 -4
- data/lib/strelka/plugins.rb +14 -5
- data/lib/strelka/router.rb +1 -1
- data/lib/strelka/router/default.rb +1 -1
- data/lib/strelka/router/exclusive.rb +1 -1
- data/lib/strelka/session.rb +1 -0
- data/lib/strelka/session/db.rb +1 -0
- data/lib/strelka/session/default.rb +1 -0
- data/lib/strelka/testing.rb +454 -14
- data/lib/strelka/websocketserver.rb +150 -36
- data/lib/strelka/websocketserver/heartbeat.rb +163 -0
- data/lib/strelka/websocketserver/routing.rb +46 -19
- data/spec/constants.rb +1 -1
- data/spec/helpers.rb +15 -6
- data/spec/strelka/app/auth_spec.rb +5 -3
- data/spec/strelka/app/errors_spec.rb +2 -2
- data/spec/strelka/app/filters_spec.rb +2 -2
- data/spec/strelka/app/negotiation_spec.rb +2 -2
- data/spec/strelka/app/parameters_spec.rb +5 -5
- data/spec/strelka/app/restresources_spec.rb +8 -6
- data/spec/strelka/app/routing_spec.rb +3 -3
- data/spec/strelka/app/sessions_spec.rb +4 -2
- data/spec/strelka/app/templating_spec.rb +2 -2
- data/spec/strelka/app_spec.rb +5 -24
- data/spec/strelka/authprovider/basic_spec.rb +3 -2
- data/spec/strelka/authprovider/hostaccess_spec.rb +3 -2
- data/spec/strelka/authprovider_spec.rb +3 -2
- data/spec/strelka/cli_spec.rb +7 -4
- data/spec/strelka/cookie_spec.rb +2 -2
- data/spec/strelka/cookieset_spec.rb +2 -2
- data/spec/strelka/discovery_spec.rb +2 -2
- data/spec/strelka/exceptions_spec.rb +2 -2
- data/spec/strelka/httprequest/acceptparams_spec.rb +2 -2
- data/spec/strelka/httprequest/auth_spec.rb +3 -2
- data/spec/strelka/httprequest/negotiation_spec.rb +2 -2
- data/spec/strelka/httprequest/session_spec.rb +3 -2
- data/spec/strelka/httprequest_spec.rb +7 -2
- data/spec/strelka/httpresponse/negotiation_spec.rb +6 -5
- data/spec/strelka/httpresponse/session_spec.rb +3 -2
- data/spec/strelka/httpresponse_spec.rb +4 -3
- data/spec/strelka/mixins_spec.rb +85 -2
- data/spec/strelka/multipartparser_spec.rb +5 -4
- data/spec/strelka/paramvalidator_spec.rb +15 -10
- data/spec/strelka/plugins_spec.rb +24 -2
- data/spec/strelka/router/default_spec.rb +2 -2
- data/spec/strelka/router/exclusive_spec.rb +2 -2
- data/spec/strelka/router_spec.rb +2 -2
- data/spec/strelka/session/db_spec.rb +3 -2
- data/spec/strelka/session/default_spec.rb +3 -2
- data/spec/strelka/session_spec.rb +3 -2
- data/spec/strelka/testing_spec.rb +772 -0
- data/spec/strelka/websocketserver/heartbeat_spec.rb +19 -0
- data/spec/strelka/websocketserver/routing_spec.rb +31 -29
- data/spec/strelka/websocketserver_spec.rb +210 -75
- data/spec/strelka_spec.rb +172 -2
- metadata +43 -36
- metadata.gz.sig +0 -0
@@ -1,6 +1,8 @@
|
|
1
1
|
# -*- ruby -*-
|
2
2
|
# vim: set nosta noet ts=4 sw=4:
|
3
|
-
#
|
3
|
+
# frozen-string-literal: true
|
4
|
+
|
5
|
+
require 'set'
|
4
6
|
|
5
7
|
require 'mongrel2/handler'
|
6
8
|
require 'mongrel2/websocket'
|
@@ -27,15 +29,17 @@ require 'strelka/discovery'
|
|
27
29
|
# idle_timeout 15.0
|
28
30
|
#
|
29
31
|
# # When a websocket is set up, add a new user to the table, but without a nick.
|
30
|
-
#
|
31
|
-
# @users[
|
32
|
-
# return
|
32
|
+
# def handle_websocket_handshake( request )
|
33
|
+
# @users[ request.socket_id ] = nil
|
34
|
+
# return request.response # accept the connection
|
33
35
|
# end
|
34
36
|
#
|
37
|
+
# plugin :routing
|
38
|
+
#
|
35
39
|
# # Handle incoming commands, which should be text frames
|
36
|
-
# on_text do |
|
37
|
-
# senderid =
|
38
|
-
# data =
|
40
|
+
# on_text do |request|
|
41
|
+
# senderid = request.socket_id
|
42
|
+
# data = request.payload.read
|
39
43
|
#
|
40
44
|
# # If the input starts with '/', it's a command (e.g., /quit, /nick, etc.)
|
41
45
|
# output = nil
|
@@ -45,7 +49,7 @@ require 'strelka/discovery'
|
|
45
49
|
# output = self.say( senderid, data )
|
46
50
|
# end
|
47
51
|
#
|
48
|
-
# response =
|
52
|
+
# response = request.response
|
49
53
|
# response.puts( output )
|
50
54
|
# return response
|
51
55
|
# end
|
@@ -62,15 +66,92 @@ class Strelka::WebSocketServer < Mongrel2::Handler
|
|
62
66
|
log_to :strelka
|
63
67
|
|
64
68
|
|
69
|
+
### Overridden from Mongrel2::Handler -- use the value returned from .default_appid if
|
70
|
+
### one is not specified.
|
71
|
+
def self::run( appid=nil )
|
72
|
+
appid ||= self.default_appid
|
73
|
+
self.log.info "Starting up with appid %p." % [ appid ]
|
74
|
+
super( appid )
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
### Calculate a default application ID for the class based on either its ID
|
79
|
+
### constant or its name and return it.
|
80
|
+
def self::default_appid
|
81
|
+
self.log.info "Looking up appid for %p" % [ self.class ]
|
82
|
+
appid = nil
|
83
|
+
|
84
|
+
if self.const_defined?( :ID )
|
85
|
+
appid = self.const_get( :ID )
|
86
|
+
self.log.info " app has an ID: %p" % [ appid ]
|
87
|
+
else
|
88
|
+
appid = ( self.name || "anonymous#{self.object_id}" ).downcase
|
89
|
+
appid.gsub!( /[^[:alnum:]]+/, '-' )
|
90
|
+
self.log.info " deriving one from the class name: %p" % [ appid ]
|
91
|
+
end
|
92
|
+
|
93
|
+
return appid
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
### Return an instance of the App configured for the handler in the currently-loaded
|
98
|
+
### Mongrel2 config that corresponds to the #default_appid.
|
99
|
+
def self::default_app_instance
|
100
|
+
appid = self.default_appid
|
101
|
+
return self.app_instance_for( appid )
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
|
106
|
+
#################################################################
|
107
|
+
### I N S T A N C E M E T H O D S
|
108
|
+
#################################################################
|
109
|
+
|
110
|
+
### Dump the application stack when a new instance is created.
|
111
|
+
def initialize( * )
|
112
|
+
self.class.dump_application_stack
|
113
|
+
|
114
|
+
@connections = Hash.new {|h, k| h[k] = Set.new }
|
115
|
+
@connection_times = Hash.new {|h, k| h[k] = Hash.new }
|
116
|
+
|
117
|
+
super
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
######
|
122
|
+
public
|
123
|
+
######
|
124
|
+
|
125
|
+
##
|
126
|
+
# A Hash of sender ID => Set of connection IDs.
|
127
|
+
attr_reader :connections
|
128
|
+
|
129
|
+
##
|
130
|
+
# A Hash of [sender ID, connection ID] keys => connection Times
|
131
|
+
attr_reader :connection_times
|
132
|
+
|
133
|
+
|
134
|
+
|
135
|
+
### Run the app -- overriden to set the process name to something interesting.
|
136
|
+
def run
|
137
|
+
procname = "%s %s: %p %s" % [ RUBY_ENGINE, RUBY_VERSION, self.class, self.conn ]
|
138
|
+
$0 = procname
|
139
|
+
|
140
|
+
super
|
141
|
+
end
|
142
|
+
|
143
|
+
|
65
144
|
### Handle a WebSocket frame in +request+. If not overridden, WebSocket connections are
|
66
145
|
### closed with a policy error status.
|
67
|
-
def handle_websocket(
|
146
|
+
def handle_websocket( request )
|
68
147
|
response = nil
|
69
148
|
|
70
|
-
|
149
|
+
self.connection_times[ request.sender_id ][ request.conn_id ] = Time.now
|
150
|
+
|
151
|
+
# Dispatch the request
|
71
152
|
response = catch( :close_websocket ) do
|
72
|
-
self.log.debug "Incoming WEBSOCKET
|
73
|
-
self.
|
153
|
+
self.log.debug "Incoming WEBSOCKET request (%p):%s" % [ request, request.headers.path ]
|
154
|
+
self.handle_websocket_request( request )
|
74
155
|
end
|
75
156
|
|
76
157
|
return response
|
@@ -78,8 +159,13 @@ class Strelka::WebSocketServer < Mongrel2::Handler
|
|
78
159
|
|
79
160
|
|
80
161
|
### Handle a WebSocket handshake HTTP +request+.
|
162
|
+
### :TODO: Register/check for supported Sec-WebSocket-Protocol.
|
81
163
|
def handle_websocket_handshake( handshake )
|
82
|
-
self.log.
|
164
|
+
self.log.info "Incoming WEBSOCKET_HANDSHAKE request (%p)" % [ handshake.headers.path ]
|
165
|
+
self.connections[ handshake.sender_id ].add( handshake.conn_id )
|
166
|
+
self.connection_times[ handshake.sender_id ][ handshake.conn_id ] = Time.now
|
167
|
+
self.log.debug " connections: %p" % [ self.connections ]
|
168
|
+
|
83
169
|
return handshake.response( handshake.protocols.first )
|
84
170
|
end
|
85
171
|
|
@@ -87,59 +173,87 @@ class Strelka::WebSocketServer < Mongrel2::Handler
|
|
87
173
|
### Handle a disconnect notice from Mongrel2 via the given +request+. Its return value
|
88
174
|
### is ignored.
|
89
175
|
def handle_disconnect( request )
|
90
|
-
self.log.info "
|
176
|
+
self.log.info "Connection %d closed." % [ request.conn_id ]
|
177
|
+
self.connection_times[ request.sender_id ].delete( request.conn_id )
|
178
|
+
self.connections.delete( request.sender_id )
|
179
|
+
self.log.debug " connections remaining: %p" % [ self.connections ]
|
180
|
+
|
91
181
|
return nil
|
92
182
|
end
|
93
183
|
|
94
184
|
|
185
|
+
### Return the Time of the last frame from the client associated with the given
|
186
|
+
### +request+.
|
187
|
+
def last_connection_time( request )
|
188
|
+
table = self.connection_times[ request.sender_id ] or return nil
|
189
|
+
return table[ request.conn_id ]
|
190
|
+
end
|
191
|
+
|
192
|
+
|
193
|
+
### Send the specified +frame+ to all current connections, except those listed in
|
194
|
+
### +except+. The +except+ argument is a single [sender_id, conn_id] tuple.
|
195
|
+
def broadcast( frame, except: nil )
|
196
|
+
self.connections.each do |sender_id, conn_ids|
|
197
|
+
id_list = conn_ids.to_a.
|
198
|
+
reject {|cid| except&.first == sender_id && except&.last == cid }
|
199
|
+
|
200
|
+
self.log.debug "Broadcasting to %d connections for sender %s" %
|
201
|
+
[ conn_ids.length, sender_id ]
|
202
|
+
|
203
|
+
self.conn.broadcast( sender_id, id_list, frame.to_s )
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
|
95
208
|
#########
|
96
209
|
protected
|
97
210
|
#########
|
98
211
|
|
99
|
-
### Default
|
100
|
-
def
|
101
|
-
if
|
102
|
-
self.
|
212
|
+
### Default request handler.
|
213
|
+
def handle_websocket_request( request )
|
214
|
+
if request.control?
|
215
|
+
self.handle_control_request( request )
|
103
216
|
else
|
104
|
-
self.
|
217
|
+
self.handle_content_request( request )
|
105
218
|
end
|
106
219
|
end
|
107
220
|
|
108
221
|
|
109
|
-
### Throw a
|
110
|
-
|
222
|
+
### Throw a response with a 'close' frame that will close the current
|
223
|
+
### connection.
|
224
|
+
def close_with( request, reason )
|
111
225
|
self.log.debug "Closing the connection: %p" % [ reason ]
|
112
226
|
|
113
|
-
# Make a CLOSE
|
114
|
-
|
115
|
-
|
227
|
+
# Make a CLOSE response
|
228
|
+
response = request.response( :close )
|
229
|
+
response.set_status( reason )
|
116
230
|
|
117
|
-
throw :close_websocket,
|
231
|
+
throw :close_websocket, response
|
118
232
|
end
|
119
233
|
|
120
234
|
|
121
|
-
### Handle an incoming control frame.
|
122
|
-
def
|
123
|
-
self.log.debug "Handling control
|
235
|
+
### Handle an incoming request with a control frame.
|
236
|
+
def handle_control_request( request )
|
237
|
+
self.log.debug "Handling control request: %p" % [ request ]
|
124
238
|
|
125
|
-
case
|
239
|
+
case request.opcode
|
126
240
|
when :ping
|
127
|
-
return
|
241
|
+
return request.response
|
128
242
|
when :pong
|
129
243
|
return nil
|
130
244
|
when :close
|
131
|
-
self.conn.reply_close(
|
245
|
+
self.conn.reply_close( request )
|
132
246
|
return nil
|
133
247
|
else
|
134
|
-
self.close_with(
|
248
|
+
self.close_with( request, CLOSE_BAD_DATA_TYPE )
|
135
249
|
end
|
136
250
|
end
|
137
251
|
|
138
252
|
|
139
|
-
### Handle an incoming content frame.
|
140
|
-
def
|
141
|
-
self.log.warn "Unhandled
|
142
|
-
self.close_with(
|
253
|
+
### Handle an incoming request with a content frame.
|
254
|
+
def handle_content_request( request )
|
255
|
+
self.log.warn "Unhandled request type %p" % [ request.opcode ]
|
256
|
+
self.close_with( request, CLOSE_BAD_DATA_TYPE )
|
143
257
|
end
|
144
258
|
|
145
259
|
|
@@ -0,0 +1,163 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
# vim: set nosta noet ts=4 sw=4:
|
3
|
+
# frozen-string-literal: true
|
4
|
+
|
5
|
+
require 'strelka' unless defined?( Strelka )
|
6
|
+
require 'strelka/websocketserver' unless defined?( Strelka::WebSocketServer )
|
7
|
+
require 'strelka/plugin' unless defined?( Strelka::Plugin )
|
8
|
+
|
9
|
+
# Heartbeat logic for Strelka WebSocketServers.
|
10
|
+
#
|
11
|
+
# To ping connected clients, and disconnect them after 3 failed pings:
|
12
|
+
#
|
13
|
+
# class ChatServer < Strelka::WebSocketServer
|
14
|
+
# plugin :heartbeat
|
15
|
+
#
|
16
|
+
# heartbeat_rate 5.0
|
17
|
+
# idle_timeout 15.0
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
module Strelka::WebSocketServer::Heartbeat
|
21
|
+
extend Loggability,
|
22
|
+
Strelka::Plugin
|
23
|
+
include Strelka::Constants,
|
24
|
+
Mongrel2::WebSocket::Constants
|
25
|
+
|
26
|
+
|
27
|
+
# The default number of seconds between heartbeat events
|
28
|
+
DEFAULT_HEARTBEAT_RATE = 5.0
|
29
|
+
|
30
|
+
# The default number of seconds between events before a client is disconnected
|
31
|
+
DEFAULT_IDLE_TIMEOUT = 15.0
|
32
|
+
|
33
|
+
|
34
|
+
# Loggability API -- set up logging under the 'strelka' log host
|
35
|
+
log_to :strelka
|
36
|
+
|
37
|
+
# Plugins API -- set up load order
|
38
|
+
run_outside :routing
|
39
|
+
|
40
|
+
|
41
|
+
# Class methods to add to servers with a heartbeat.
|
42
|
+
module ClassMethods # :nodoc:
|
43
|
+
|
44
|
+
@heartbeat_rate = DEFAULT_HEARTBEAT_RATE
|
45
|
+
@idle_timeout = DEFAULT_IDLE_TIMEOUT
|
46
|
+
|
47
|
+
|
48
|
+
### Get/set the number of seconds between heartbeat events.
|
49
|
+
def heartbeat_rate( new_rate=nil )
|
50
|
+
@heartbeat_rate = new_rate.to_f if new_rate
|
51
|
+
return @heartbeat_rate
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
### Get/set the number of seconds between events before a client is disconnected
|
56
|
+
def idle_timeout( new_timeout=nil )
|
57
|
+
@idle_timeout = new_timeout if new_timeout
|
58
|
+
return @idle_timeout
|
59
|
+
end
|
60
|
+
|
61
|
+
|
62
|
+
### Inheritance hook -- inheriting classes inherit their parents' routes table.
|
63
|
+
def inherited( subclass )
|
64
|
+
super
|
65
|
+
subclass.instance_variable_set( :@idle_timeout, self.idle_timeout.dup )
|
66
|
+
subclass.instance_variable_set( :@heartbeat_rate, self.heartbeat_rate.dup )
|
67
|
+
end
|
68
|
+
|
69
|
+
end # module ClassMethods
|
70
|
+
|
71
|
+
|
72
|
+
######
|
73
|
+
public
|
74
|
+
######
|
75
|
+
|
76
|
+
### Called by Mongrel2::Handler when it starts accepting requests. Overridden
|
77
|
+
### to start up the heartbeat thread.
|
78
|
+
def start_accepting_requests
|
79
|
+
self.start_heartbeat
|
80
|
+
super
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
### Called by Mongrel2::Handler when the server is restarted. Overridden to
|
85
|
+
### restart the heartbeat thread.
|
86
|
+
def restart
|
87
|
+
self.stop_heartbeat
|
88
|
+
super
|
89
|
+
self.start_heartbeat
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
### Called by Mongrel2::Handler when the server is shut down. Overridden to
|
94
|
+
### stop the heartbeat thread.
|
95
|
+
def shutdown
|
96
|
+
self.stop_heartbeat
|
97
|
+
super
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
### Start a thread that will periodically ping connected sockets and remove any
|
102
|
+
### connections that don't reply
|
103
|
+
def start_heartbeat
|
104
|
+
self.log.info "Starting heartbeat timer."
|
105
|
+
@heartbeat_timer = self.reactor.add_periodic_timer( self.class.heartbeat_rate ) do
|
106
|
+
self.cull_idle_sockets
|
107
|
+
self.ping_all_sockets
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
|
112
|
+
### Tell the heartbeat thread to exit.
|
113
|
+
def stop_heartbeat
|
114
|
+
self.reactor.remove_timer( @heartbeat_timer )
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
### Disconnect any sockets that haven't sent any frames for at least
|
119
|
+
### SOCKET_IDLE_TIMEOUT seconds.
|
120
|
+
def cull_idle_sockets
|
121
|
+
self.log.debug "Culling idle sockets."
|
122
|
+
|
123
|
+
earliest = Time.now - self.class.idle_timeout
|
124
|
+
|
125
|
+
self.connection_times.each do |sender_id, times|
|
126
|
+
times.each do |conn_id, lastframe|
|
127
|
+
next unless earliest > lastframe
|
128
|
+
|
129
|
+
self.log.warn "Timing out connection %s:%d: last seen %0.3fs ago." %
|
130
|
+
[ sender_id, conn_id, Time.now - lastframe ]
|
131
|
+
|
132
|
+
# Make a CLOSE frame
|
133
|
+
frame = Mongrel2::WebSocket::Frame.close
|
134
|
+
frame.set_status( CLOSE_EXCEPTION )
|
135
|
+
|
136
|
+
# Use the connection directly so we can send a frame and close the
|
137
|
+
# connection
|
138
|
+
self.conn.send( sender_id, conn_id, frame.to_s )
|
139
|
+
self.conn.send_close( sender_id, conn_id )
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
|
145
|
+
### Send a PING frame to all connected sockets.
|
146
|
+
def ping_all_sockets
|
147
|
+
return if self.connections.empty?
|
148
|
+
|
149
|
+
self.log.debug "Pinging %d connected sockets." % [ self.connections.length ]
|
150
|
+
self.connections.each do |sender_id, conn_ids|
|
151
|
+
frame = Mongrel2::WebSocket::Frame.new( sender_id, conn_id, '', {}, 'heartbeat' )
|
152
|
+
frame.opcode = :ping
|
153
|
+
frame.fin = true
|
154
|
+
|
155
|
+
self.log.debug " %s/%d: PING" % [ sender_id, conn_id ]
|
156
|
+
self.conn.reply( frame )
|
157
|
+
end
|
158
|
+
|
159
|
+
self.log.debug " done with pings."
|
160
|
+
end
|
161
|
+
|
162
|
+
end # module Strelka::WebSocketServer::Heartbeat
|
163
|
+
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# -*- ruby -*-
|
2
2
|
# vim: set nosta noet ts=4 sw=4:
|
3
|
-
#
|
3
|
+
# frozen-string-literal: true
|
4
4
|
|
5
5
|
require 'strelka' unless defined?( Strelka )
|
6
6
|
require 'strelka/websocketserver' unless defined?( Strelka::WebSocketServer )
|
@@ -10,20 +10,36 @@ require 'strelka/plugin' unless defined?( Strelka::Plugin )
|
|
10
10
|
#
|
11
11
|
# For a protocol that defines its own opcodes:
|
12
12
|
#
|
13
|
-
# class ChatServer
|
13
|
+
# class ChatServer < Strelka::WebSocketServer
|
14
14
|
# plugin :routing
|
15
15
|
#
|
16
|
+
# on_handshake do |request|
|
17
|
+
# # ...
|
18
|
+
# end
|
19
|
+
# on_text do |request|
|
20
|
+
# # ...
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# class CustomChatServer < Strelka::WebSocketServer
|
25
|
+
# plugin :routing
|
16
26
|
# opcodes :nick => 7,
|
17
27
|
# :emote => 8
|
18
28
|
#
|
19
|
-
#
|
20
|
-
#
|
29
|
+
# on_nick do |request|
|
30
|
+
# nick = request.payload.read
|
31
|
+
# self.set_nick( request.socket_id, nick )
|
32
|
+
# return request.response( "Okay, nick set to #{nick}.", :text )
|
21
33
|
# end
|
22
34
|
#
|
23
|
-
#
|
24
|
-
#
|
35
|
+
# on_emote do |request|
|
36
|
+
# emote = request.payload.read
|
37
|
+
# nick = self.nick_for( request.socket_id )
|
38
|
+
# msg = "%s %s" % [ nick, emote ]
|
39
|
+
# self.broadcast( msg )
|
40
|
+
# return nil
|
25
41
|
# end
|
26
|
-
#
|
42
|
+
# end
|
27
43
|
#
|
28
44
|
module Strelka::WebSocketServer::Routing
|
29
45
|
extend Loggability,
|
@@ -42,40 +58,51 @@ module Strelka::WebSocketServer::Routing
|
|
42
58
|
# Class methods to add to classes with routing.
|
43
59
|
module ClassMethods # :nodoc:
|
44
60
|
|
61
|
+
##
|
45
62
|
# The list of routes to pass to the Router when the application is created
|
46
63
|
attr_reader :op_callbacks
|
47
64
|
@op_callbacks = {}
|
48
65
|
|
49
|
-
|
66
|
+
##
|
67
|
+
# The Hash of opcode names to numeric opcodes
|
50
68
|
attr_reader :opcode_map
|
51
69
|
@opcode_map = {}
|
52
70
|
|
71
|
+
##
|
72
|
+
# The Hash of numeric opcodes to opcode names
|
73
|
+
attr_reader :opcode_names
|
74
|
+
@opcode_names = {}
|
75
|
+
|
53
76
|
|
54
77
|
### Declare one or more opcodes in the form:
|
55
78
|
###
|
56
79
|
### {
|
57
|
-
### <
|
80
|
+
### <label> => <bit>,
|
58
81
|
### }
|
59
82
|
def opcodes( hash )
|
60
83
|
@opcode_map ||= {}
|
61
84
|
@opcode_map.merge!( hash )
|
62
|
-
|
85
|
+
|
86
|
+
@opcode_names = @opcode_map.invert
|
87
|
+
|
88
|
+
@opcode_map.each do |label, bit|
|
63
89
|
self.log.debug "Set opcode %p to %#0x" % [ label, bit ]
|
64
90
|
declarative = "on_#{label}"
|
65
91
|
block = self.make_declarative( label )
|
66
92
|
self.log.debug " declaring method %p on %p" % [ declarative, self ]
|
67
|
-
self.class.
|
93
|
+
self.class.remove_method( declarative ) if self.class.method_defined?( declarative )
|
94
|
+
self.class.define_method( declarative, &block )
|
68
95
|
end
|
69
96
|
end
|
70
97
|
|
71
98
|
|
72
|
-
### Make a declarative method for setting the callback for
|
73
|
-
### +opcode+ (Symbol).
|
99
|
+
### Make a declarative method for setting the callback for requests with frames
|
100
|
+
### with the specified +opcode+ (Symbol).
|
74
101
|
def make_declarative( opcode )
|
75
102
|
self.log.debug "Making a declarative for %p" % [ opcode ]
|
76
103
|
return lambda do |&block|
|
77
104
|
self.log.debug "Setting handler for %p frames to %p" % [ opcode, block ]
|
78
|
-
methodname = "on_#{opcode}
|
105
|
+
methodname = "on_#{opcode}_request"
|
79
106
|
define_method( methodname, &block )
|
80
107
|
self.op_callbacks[ opcode ] = self.instance_method( methodname )
|
81
108
|
end
|
@@ -94,7 +121,7 @@ module Strelka::WebSocketServer::Routing
|
|
94
121
|
### is registered.
|
95
122
|
def self::extended( mod )
|
96
123
|
super
|
97
|
-
mod.opcodes( Mongrel2::WebSocket::Constants::
|
124
|
+
mod.opcodes( Mongrel2::WebSocket::Constants::OPCODE )
|
98
125
|
end
|
99
126
|
|
100
127
|
end # module ClassMethods
|
@@ -102,14 +129,14 @@ module Strelka::WebSocketServer::Routing
|
|
102
129
|
|
103
130
|
|
104
131
|
### Dispatch the incoming frame to its handler based on its opcode
|
105
|
-
def
|
132
|
+
def handle_websocket_request( request )
|
106
133
|
self.log.debug "[:routing] Opcode map is: %p" % [ self.class.opcode_map ]
|
107
|
-
opname = self.class.
|
108
|
-
self.log.debug "[:routing] Routing
|
134
|
+
opname = self.class.opcode_names[ request.numeric_opcode ]
|
135
|
+
self.log.debug "[:routing] Routing request: %p" % [ opname ]
|
109
136
|
|
110
137
|
handler = self.class.op_callbacks[ opname ] or return super
|
111
138
|
|
112
|
-
return handler.bind( self ).call(
|
139
|
+
return handler.bind( self ).call( request )
|
113
140
|
end
|
114
141
|
|
115
142
|
end # module Strelka::WebSocketServer::Routing
|