volt-sockjs 0.3.4.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/LICENCE +19 -0
  3. data/README.textile +118 -0
  4. data/lib/meta-state.rb +151 -0
  5. data/lib/rack/sockjs.rb +173 -0
  6. data/lib/sockjs.rb +59 -0
  7. data/lib/sockjs/callbacks.rb +19 -0
  8. data/lib/sockjs/connection.rb +45 -0
  9. data/lib/sockjs/delayed-response-body.rb +99 -0
  10. data/lib/sockjs/duck-punch-rack-mount.rb +12 -0
  11. data/lib/sockjs/duck-punch-thin-response.rb +15 -0
  12. data/lib/sockjs/examples/protocol_conformance_test.rb +73 -0
  13. data/lib/sockjs/faye.rb +18 -0
  14. data/lib/sockjs/protocol.rb +97 -0
  15. data/lib/sockjs/servers/request.rb +137 -0
  16. data/lib/sockjs/servers/response.rb +170 -0
  17. data/lib/sockjs/session.rb +492 -0
  18. data/lib/sockjs/transport.rb +357 -0
  19. data/lib/sockjs/transports/eventsource.rb +30 -0
  20. data/lib/sockjs/transports/htmlfile.rb +73 -0
  21. data/lib/sockjs/transports/iframe.rb +68 -0
  22. data/lib/sockjs/transports/info.rb +48 -0
  23. data/lib/sockjs/transports/jsonp.rb +84 -0
  24. data/lib/sockjs/transports/websocket.rb +198 -0
  25. data/lib/sockjs/transports/welcome_screen.rb +17 -0
  26. data/lib/sockjs/transports/xhr.rb +75 -0
  27. data/lib/sockjs/version.rb +13 -0
  28. data/spec/sockjs/protocol_spec.rb +49 -0
  29. data/spec/sockjs/session_spec.rb +51 -0
  30. data/spec/sockjs/transport_spec.rb +73 -0
  31. data/spec/sockjs/transports/eventsource_spec.rb +56 -0
  32. data/spec/sockjs/transports/htmlfile_spec.rb +72 -0
  33. data/spec/sockjs/transports/iframe_spec.rb +66 -0
  34. data/spec/sockjs/transports/jsonp_spec.rb +252 -0
  35. data/spec/sockjs/transports/websocket_spec.rb +101 -0
  36. data/spec/sockjs/transports/welcome_screen_spec.rb +36 -0
  37. data/spec/sockjs/transports/xhr_spec.rb +314 -0
  38. data/spec/sockjs/version_spec.rb +18 -0
  39. data/spec/sockjs_spec.rb +8 -0
  40. data/spec/spec_helper.rb +121 -0
  41. data/spec/support/async-test.rb +42 -0
  42. metadata +154 -0
@@ -0,0 +1,137 @@
1
+ # encoding: utf-8
2
+
3
+ require "uri"
4
+
5
+ module SockJS
6
+ #This is the SockJS wrapper for a Rack env hash-like. Currently it requires
7
+ #that we're running under Thin - someday we may break this out such that can
8
+ #adapt to other webservers or compatiblity layers. For now: do your SockJS
9
+ #stuff in Thin.
10
+ #
11
+ class Request
12
+ attr_reader :env
13
+ def initialize(env)
14
+ @env = env
15
+ end
16
+
17
+ # request.path_info
18
+ # => /echo/abc
19
+ def path_info
20
+ env["PATH_INFO"]
21
+ end
22
+
23
+ # request.http_method
24
+ # => "GET"
25
+ def http_method
26
+ env["REQUEST_METHOD"]
27
+ end
28
+
29
+ def async_callback
30
+ env["async.callback"]
31
+ end
32
+
33
+ def async_close
34
+ env["async.close"]
35
+ end
36
+
37
+ def on_close(&block)
38
+ async_close.callback( &block)
39
+ async_close.errback( &block)
40
+ end
41
+
42
+ def succeed
43
+ async_close.succeed
44
+ end
45
+
46
+ def fail
47
+ async_close.fail
48
+ end
49
+
50
+ #Somehow, default inspect pulls in the whole app...
51
+ def inspect
52
+ position = data.pos
53
+ data.rewind
54
+ body = data.read
55
+ "<<#{self.class.name}: #{http_method}/#{path_info} #{body.inspect}>>"
56
+ ensure
57
+ data.pos = position
58
+ end
59
+
60
+ # request.headers["origin"]
61
+ # => http://foo.bar
62
+ def headers
63
+ @headers ||=
64
+ begin
65
+ permitted_keys = /^(CONTENT_(LENGTH|TYPE))$/
66
+
67
+ @env.reduce(Hash.new) do |headers, (key, value)|
68
+ if key.match(/^HTTP_(.+)$/) || key.match(permitted_keys)
69
+ headers[$1.downcase.tr("_", "-")] = value
70
+ end
71
+
72
+ headers
73
+ end
74
+ end
75
+ end
76
+
77
+ # request.query_string["callback"]
78
+ # => "myFn"
79
+ def query_string
80
+ @query_string ||=
81
+ begin
82
+ @env["QUERY_STRING"].split("=").each_slice(2).each_with_object({}) do |pair, buffer|
83
+ buffer[pair.first] = pair.last
84
+ end
85
+ end
86
+ end
87
+
88
+
89
+ # request.cookies["JSESSIONID"]
90
+ # => "123sd"
91
+ def cookies
92
+ @cookies ||=
93
+ begin
94
+ ::Rack::Request.new(@env).cookies
95
+ end
96
+ end
97
+
98
+
99
+ # request.data.read
100
+ # => "message"
101
+ def data
102
+ @env["rack.input"]
103
+ end
104
+ HTTP_1_0 ||= "HTTP/1.0"
105
+ HTTP_VERSION ||= "version"
106
+
107
+ def http_1_0?
108
+ self.headers[HTTP_VERSION] == HTTP_1_0
109
+ end
110
+
111
+ def origin
112
+ return "*" if self.headers["origin"] == "null"
113
+ self.headers["origin"] || "*"
114
+ end
115
+
116
+ def content_type
117
+ self.headers["content-type"]
118
+ end
119
+
120
+ def callback
121
+ callback = self.query_string["callback"] || self.query_string["c"]
122
+ URI.unescape(callback) if callback
123
+ end
124
+
125
+ def keep_alive?
126
+ headers["connection"].downcase == "keep-alive"
127
+ end
128
+
129
+ def session_id
130
+ self.cookies["JSESSIONID"] || "dummy"
131
+ end
132
+
133
+ def fresh?(etag)
134
+ self.headers["if-none-match"] == etag
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,170 @@
1
+ # encoding: utf-8
2
+
3
+ require 'sockjs/delayed-response-body'
4
+
5
+ module SockJS
6
+ #Adapter for Thin Rack responses. It's a TODO feature to support other
7
+ #webservers and compatibility layers
8
+ class Response
9
+ extend Forwardable
10
+ attr_reader :request, :status, :headers, :body
11
+ attr_writer :status
12
+
13
+ def initialize(request, status = nil, headers = nil, &block)
14
+ # request.env["async.close"]
15
+ # ["rack.input"].closed? # it's a stream
16
+ @request, @status, @headers = request, status, headers || {}
17
+
18
+ if request.http_1_0?
19
+ SockJS.debug "Request is in HTTP/1.0, responding with HTTP/1.0"
20
+ @body = DelayedResponseBody.new
21
+ else
22
+ @body = DelayedResponseChunkedBody.new
23
+ end
24
+
25
+ @body.callback do
26
+ @request.succeed
27
+ end
28
+
29
+ @body.errback do
30
+ @request.fail
31
+ end
32
+
33
+ block.call(self) if block
34
+
35
+ set_connection_keep_alive_if_requested
36
+ end
37
+
38
+ def session=(session)
39
+ @body.session = session
40
+ end
41
+
42
+ def turn_chunking_on(headers)
43
+ headers["Transfer-Encoding"] = "chunked"
44
+ end
45
+
46
+
47
+ def write_head(status = nil, headers = nil)
48
+ @status = status || @status || raise("Please set the status!")
49
+ @headers = headers || @headers
50
+
51
+ if @headers["Content-Length"]
52
+ raise "You can't use Content-Length with chunking!"
53
+ end
54
+
55
+ unless @request.http_1_0? || @status == 204
56
+ turn_chunking_on(@headers)
57
+ end
58
+
59
+ SockJS.debug "Writing headers: #{@status.inspect}/#{@headers.inspect}"
60
+ @request.async_callback.call([@status, @headers, @body])
61
+
62
+ @head_written = true
63
+ end
64
+
65
+ def head_written?
66
+ !! @head_written
67
+ end
68
+
69
+ def write(data)
70
+ self.write_head unless self.head_written?
71
+
72
+ @last_written_at = Time.now.to_i
73
+
74
+ @body.write(data)
75
+ end
76
+
77
+ def finish(data = nil, &block)
78
+ if data
79
+ self.write(data)
80
+ else
81
+ self.write_head unless self.head_written?
82
+ end
83
+
84
+ @body.finish
85
+ end
86
+
87
+ def async?
88
+ true
89
+ end
90
+
91
+ # Time.now.to_i shows time in seconds.
92
+ def due_for_alive_check
93
+ Time.now.to_i != @last_written_at
94
+ end
95
+
96
+ def set_status(status)
97
+ @status = status
98
+ end
99
+
100
+ def set_header(key, value)
101
+ @headers[key] = value
102
+ end
103
+
104
+ def set_session_id(session_id)
105
+ self.headers["Set-Cookie"] = "JSESSIONID=#{session_id}; path=/"
106
+ end
107
+
108
+ # === Helpers === #
109
+ def set_access_control(origin)
110
+ self.set_header("Access-Control-Allow-Origin", origin)
111
+ self.set_header("Access-Control-Allow-Credentials", "true")
112
+ self.set_header("Access-Control-Allow-Headers", "Content-Type Origin Accept X-Requested-With X-CSRF-Token If-Modified-Since If-None-Match Auth-User-Token Authorization Connection Cookie User-Agent")
113
+ end
114
+
115
+ def set_cache_control
116
+ year = 31536000
117
+ time = Time.now + year
118
+
119
+ self.set_header("Cache-Control", "public, max-age=#{year}")
120
+ self.set_header("Expires", time.gmtime.to_s)
121
+ self.set_header("Access-Control-Max-Age", "1000001")
122
+ end
123
+
124
+ def set_allow_options_post
125
+ self.set_header("Allow", "OPTIONS, POST")
126
+ self.set_header("Access-Control-Allow-Methods", "OPTIONS, POST")
127
+ end
128
+
129
+ def set_allow_options_get
130
+ self.set_header("Allow", "OPTIONS, GET")
131
+ self.set_header("Access-Control-Allow-Methods", "OPTIONS, GET")
132
+ end
133
+
134
+ def set_no_cache
135
+ self.set_header("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0")
136
+ end
137
+
138
+ CONTENT_TYPES ||= {
139
+ plain: "text/plain; charset=UTF-8",
140
+ html: "text/html; charset=UTF-8",
141
+ javascript: "application/javascript; charset=UTF-8",
142
+ json: "application/json; charset=UTF-8",
143
+ event_stream: "text/event-stream; charset=UTF-8"
144
+ }
145
+
146
+ def set_content_length(body)
147
+ if body && body.respond_to?(:bytesize)
148
+ self.headers["Content-Length"] = body.bytesize.to_s
149
+ end
150
+ end
151
+
152
+ def set_content_type(symbol)
153
+ if string = CONTENT_TYPES[symbol]
154
+ self.set_header("Content-Type", string)
155
+ else
156
+ raise "No such content type: #{symbol}"
157
+ end
158
+ end
159
+
160
+ def set_connection_keep_alive_if_requested
161
+ if @request.env["HTTP_CONNECTION"] && @request.env["HTTP_CONNECTION"].downcase == "keep-alive"
162
+ if @request.http_1_0?
163
+ self.set_header("Connection", "Close")
164
+ else
165
+ self.set_header("Connection", "Keep-Alive")
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,492 @@
1
+ # encoding: utf-8
2
+ #
3
+ require 'meta-state'
4
+ require 'sockjs/protocol'
5
+
6
+ module SockJS
7
+ class Session < MetaState::Machine
8
+ class Consumer
9
+ def initialize(response, transport)
10
+ @response = response
11
+ @transport = transport
12
+ @total_sent_length = 0
13
+ end
14
+ attr_reader :response, :transport, :total_sent_length
15
+
16
+ #Close the *response* not the *session*
17
+ def disconnect
18
+ #WEBSCOKET shouldn't have limit of data - faye will send closing frame after 1GB
19
+ if @transport.kind_of?(SockJS::Transports::WebSocket)
20
+ @total_sent_length = 0
21
+ return
22
+ end
23
+ @response.finish if @response.respond_to?(:finish)
24
+ end
25
+
26
+ def heartbeat
27
+ transport.heartbeat_frame(response)
28
+ end
29
+
30
+ def messages(items)
31
+ unless items.empty?
32
+ @total_sent_length += transport.messages_frame(response, items)
33
+ end
34
+ end
35
+
36
+ def closing(status, message)
37
+ transport.closing_frame(response, status, message)
38
+ end
39
+
40
+ #XXX Still not sure what this is *FOR*
41
+ def check_alive
42
+ if !@response.body.closed?
43
+ if @response.due_for_alive_check
44
+ SockJS.debug "Checking if still alive"
45
+ @response.write(@transport.empty_string)
46
+ else
47
+ puts "~ [TODO] Not checking if still alive, why?"
48
+ puts "Status: #{@status} (response.body.closed: #{@response.body.closed?})\nSession class: #{self.class}\nTransport class: #{@transport.class}\nResponse: #{@response.to_s}\n\n"
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ state :Detached do
55
+ def on_enter
56
+ @consumer = nil
57
+ clear_all_timers
58
+ set_disconnect_timer
59
+ end
60
+
61
+ def attach_consumer(response, transport)
62
+ @consumer = Consumer.new(response, transport)
63
+ activate
64
+ transition_to :attached
65
+ after_consumer_attached
66
+ end
67
+
68
+ def detach_consumer
69
+ #XXX Not sure if this is the right behavior
70
+ close(1002,"Connection interrupted")
71
+ end
72
+
73
+ def activate
74
+ end
75
+
76
+ def suspended
77
+ end
78
+
79
+ def send(*messages)
80
+ @outbox += messages
81
+ end
82
+
83
+ def close(status = nil, message = nil)
84
+ @close_status = status
85
+ @close_message = message
86
+ transition_to(:closed)
87
+ end
88
+ end
89
+
90
+ state :Attached do
91
+ def on_enter
92
+ @consumer.messages(@outbox)
93
+ @outbox.clear
94
+ clear_all_timers
95
+ check_content_length
96
+ set_heartbeat_timer
97
+ end
98
+
99
+ def attach_consumer(response, transport)
100
+ SockJS.debug "Session#attach_consumer: another connection still open"
101
+ transport.closing_frame(response, 2010, "Another connection still open")
102
+ close(1002, "Connection interrupted")
103
+ end
104
+
105
+ def detach_consumer
106
+ transition_to :detached
107
+ after_consumer_detached
108
+ end
109
+
110
+ def send(*messages)
111
+ @consumer.messages(messages)
112
+ check_content_length
113
+ end
114
+
115
+ def send_heartbeat
116
+ @consumer.heartbeat
117
+ end
118
+
119
+ def suspend
120
+ transition_to :suspended
121
+ end
122
+
123
+ def activate
124
+ end
125
+
126
+ def close(status = 1002, message = "Connection interrupted")
127
+ @close_status = status
128
+ @close_message = message
129
+ @consumer.closing(@close_status, @close_message)
130
+ @consumer = nil
131
+ transition_to(:closed)
132
+ end
133
+ end
134
+
135
+ state :Suspended do
136
+ def on_enter
137
+ SockJS.debug "Session suspended - it is on hold"
138
+ suspended
139
+ end
140
+
141
+ def attach_consumer(response, transport)
142
+ SockJS.debug "Session#attach_consumer: another connection still open"
143
+ transport.closing_frame(response, 2010, "Another connection still open")
144
+ close(1002, "Connection interrupted")
145
+ end
146
+
147
+ def detach_consumer
148
+ transition_to :detached
149
+ after_consumer_detached
150
+ end
151
+
152
+ def send(*messages)
153
+ @outbox += messages
154
+ end
155
+
156
+ def send_heartbeat
157
+ @consumer.heartbeat
158
+ end
159
+
160
+ def suspend
161
+ end
162
+
163
+ def activate
164
+ SockJS.debug "Session activated - is not on hold anymore!"
165
+ transition_to :attached
166
+ activated
167
+ end
168
+
169
+ def close(status = 1002, message = "Connection interrupted")
170
+ @close_status = status
171
+ @close_message = message
172
+ @consumer.closing(@close_status, @close_message)
173
+ @consumer = nil
174
+ transition_to(:closed)
175
+ end
176
+ end
177
+
178
+ state :Closed do
179
+ def on_enter
180
+ @close_status ||= 3000
181
+ @close_message ||= "Go away!"
182
+ clear_all_timers
183
+ set_close_timer
184
+ closed
185
+ end
186
+
187
+ def suspend
188
+ SockJS.debug "Session#suspend: connection closed!"
189
+ end
190
+
191
+ def activate
192
+ SockJS.debug "Session#activate: connection closed!"
193
+ end
194
+
195
+ def attach_consumer(response, transport)
196
+ transport.closing_frame(response, @close_status, @close_message)
197
+ end
198
+
199
+ def close(status=nil, message=nil)
200
+ #can be called from faye onclose hook
201
+ end
202
+ end
203
+
204
+
205
+ #### Client Code interface
206
+
207
+ # All incoming data is treated as incoming messages,
208
+ # either single json-encoded messages or an array
209
+ # of json-encoded messages, depending on transport.
210
+ def receive_message(data)
211
+ clear_timer(:disconnect)
212
+ activate
213
+
214
+ SockJS.debug "Session receiving message: #{data.inspect}"
215
+ messages = parse_json(data)
216
+ SockJS.debug "Message parsed as: #{messages.inspect}"
217
+ unless messages.empty?
218
+ @received_messages.push(*messages)
219
+ end
220
+
221
+ EM.next_tick do
222
+ run_user_app
223
+ end
224
+
225
+ set_disconnect_timer
226
+ end
227
+
228
+ def suspended?
229
+ current_state == SockJS::Session::Suspended
230
+ end
231
+
232
+ def check_content_length
233
+ if @consumer.total_sent_length >= max_permitted_content_length
234
+ SockJS.debug "Maximum content length exceeded, closing the connection."
235
+ #shouldn't be restarting connection?
236
+ @consumer.disconnect
237
+ else
238
+ SockJS.debug "Permitted content length: #{@consumer.total_sent_length} of #{max_permitted_content_length}"
239
+ end
240
+ end
241
+
242
+ def run_user_app
243
+ unless @received_messages.empty?
244
+ reset_heartbeat_timer #XXX Only one point which can set hearbeat while state is closed
245
+
246
+ SockJS.debug "Executing user's SockJS app"
247
+
248
+ raise @error if @error
249
+
250
+ @received_messages.each do |message|
251
+ SockJS.debug "Executing app with message #{message.inspect}"
252
+ process_message(message)
253
+ end
254
+ @received_messages.clear
255
+
256
+ after_app_run
257
+
258
+ SockJS.debug "User's SockJS app finished"
259
+ end
260
+ rescue SockJS::CloseError => error
261
+ Protocol::ClosingFrame.new(error.status, error.message)
262
+ end
263
+
264
+ def process_message(message)
265
+ end
266
+
267
+ def opened
268
+ end
269
+
270
+ def after_app_run
271
+ end
272
+
273
+ def closed
274
+ end
275
+
276
+ def activated
277
+ end
278
+
279
+ def suspended
280
+ end
281
+
282
+ def after_consumer_attached
283
+ end
284
+
285
+ def after_consumer_detached
286
+ end
287
+
288
+ attr_accessor :disconnect_delay, :interval
289
+ attr_reader :transport, :response, :outbox, :closing_frame, :data
290
+
291
+ def initialize(connection)
292
+ super()
293
+
294
+ debug_with do |msg|
295
+ SockJS::debug(msg)
296
+ end
297
+
298
+ @connection = connection
299
+ @disconnect_delay = 5 # TODO: make this configurable.
300
+ @received_messages = []
301
+ @outbox = []
302
+ @total_sent_content_length = 0
303
+ @interval = 0.1
304
+ @closing_frame = nil
305
+ @data = {}
306
+ @alive = true
307
+ @timers = {}
308
+ end
309
+
310
+ def alive?
311
+ !!@alive
312
+ end
313
+
314
+ #XXX This is probably important - need to examine this case
315
+ def on_close
316
+ SockJS.debug "The connection has been closed on the client side (current status: #{@status})."
317
+ close_session(1002, "Connection interrupted")
318
+ end
319
+
320
+ def max_permitted_content_length
321
+ @max_permitted_content_length ||= ($DEBUG ? 4096 : 128_000)
322
+ end
323
+
324
+ def parse_json(data)
325
+ if data.empty?
326
+ return []
327
+ end
328
+
329
+ JSON.parse("[#{data}]")[0]
330
+ rescue JSON::ParserError => error
331
+ raise SockJS::InvalidJSON.new(500, "Broken JSON encoding.")
332
+ end
333
+
334
+ #Timers:
335
+ #"alive_checker" - need to check spec. Appears to check that response is
336
+ #live. Premature?
337
+ #
338
+ #"disconnect" - expires and closes the session - time without a consumer
339
+ #
340
+ #"close" - duration between closed and removed from management
341
+ #
342
+ #"heartbeat" - periodic for hb frame
343
+
344
+ #Timer actions:
345
+
346
+ def disconnect_expired
347
+ SockJS.debug "#{@disconnect_delay} has passed, firing @disconnect_timer"
348
+ close
349
+ #XXX Shouldn't destroy the session?
350
+ end
351
+
352
+ def check_response_alive
353
+ if @consumer
354
+ begin
355
+ @consumer.check_alive
356
+ rescue Exception => error
357
+ puts "==> #{error.message}"
358
+ SockJS.debug error
359
+ puts "==> #{error.message}"
360
+ on_close
361
+ @alive_checker.cancel
362
+ end
363
+ else
364
+ puts "~ [TODO] Not checking if still alive, why?"
365
+ end
366
+ end
367
+
368
+ def heartbeat_triggered
369
+ # It's better as we know for sure that
370
+ # clearing the buffer won't change it.
371
+ SockJS.debug "Sending heartbeat frame."
372
+ begin
373
+ send_heartbeat
374
+ rescue Exception => error
375
+ # Nah these exceptions are OK ... let's figure out when they occur
376
+ # and let's just not set the timer for such cases in the first place.
377
+ SockJS.debug "Exception when sending heartbeat frame: #{error.inspect}"
378
+ end
379
+ end
380
+
381
+ #Timer machinery
382
+
383
+ def set_timer(name, type, delay, &action)
384
+ @timers[name] ||=
385
+ begin
386
+ SockJS.debug "Setting timer: #{name} to expire after #{delay}"
387
+ type.new(delay, &action)
388
+ end
389
+ end
390
+
391
+ def clear_timer(name)
392
+ @timers[name].cancel unless @timers[name].nil?
393
+ @timers.delete(name)
394
+ end
395
+
396
+ def clear_all_timers
397
+ @timers.values.each do |timer|
398
+ timer.cancel
399
+ end
400
+ @timers.clear
401
+ end
402
+
403
+
404
+ def set_alive_timer
405
+ set_timer(:alive_check, EM::PeriodicTimer, 1) do
406
+ check_response_alive
407
+ end
408
+ end
409
+
410
+ def reset_alive_timer
411
+ clear_timer(:alive_check)
412
+ set_alive_timer
413
+ end
414
+
415
+ def set_heartbeat_timer
416
+ if current_state == SockJS::Session::Closed
417
+ SockJS.debug "trying to setup heartbeat on closed session!"
418
+ return
419
+ end
420
+ clear_timer(:disconnect)
421
+ clear_timer(:alive)
422
+ set_timer(:heartbeat, EM::PeriodicTimer, 25) do
423
+ heartbeat_triggered
424
+ end
425
+ end
426
+
427
+ def reset_heartbeat_timer
428
+ clear_timer(:heartbeat)
429
+ if current_state == SockJS::Session::Closed
430
+ SockJS.debug "trying to setup heartbeat on closed session!"
431
+ else
432
+ set_heartbeat_timer
433
+ end
434
+ end
435
+
436
+ def set_disconnect_timer
437
+ set_timer(:disconnect, EM::Timer, @disconnect_delay) do
438
+ disconnect_expired
439
+ end
440
+ end
441
+
442
+ def reset_disconnect_timer
443
+ clear_timer(:disconnect)
444
+ set_disconnect_timer
445
+ end
446
+
447
+ def set_close_timer
448
+ set_timer(:close, EM::Timer, @disconnect_delay) do
449
+ @alive = false
450
+ end
451
+ end
452
+
453
+ def reset_close_timer
454
+ clear_timer(:close)
455
+ set_close_timer
456
+ end
457
+ end
458
+
459
+ class WebSocketSession < Session
460
+ attr_accessor :ws
461
+ undef :response
462
+
463
+ def send_data(frame)
464
+ if frame.nil?
465
+ raise TypeError.new("Frame must not be nil!")
466
+ end
467
+
468
+ unless frame.empty?
469
+ SockJS.debug "@ws.send(#{frame.inspect})"
470
+ @ws.send(frame)
471
+ end
472
+ end
473
+
474
+ def after_app_run
475
+ return super unless self.closing?
476
+
477
+ after_close
478
+ end
479
+
480
+ def after_close
481
+ SockJS.debug "after_close: calling #finish"
482
+ finish
483
+
484
+ SockJS.debug "after_close: closing @ws and clearing @transport."
485
+ @ws.close
486
+ @transport = nil
487
+ end
488
+
489
+ def set_alive_checker
490
+ end
491
+ end
492
+ end