websocket-rails 0.1.8 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/CHANGELOG.md CHANGED
@@ -1,15 +1,28 @@
1
1
  # WebsocketRails Change Log
2
2
 
3
+ ## Version 0.1.9
4
+
5
+ November 19 2012
6
+
7
+ * Fix bug that crashed the server when receiving badly formed messages
8
+ through an open websocket. Fixes issue #27.
9
+
10
+ * Add support for communication between multiple server instances and
11
+ background jobs. Solves scaling problems discussed in issue #21.
12
+
13
+ * Fixed client_disconnected event firing twice - Thanks to
14
+ @nickdesaulniers
15
+
3
16
  ## Version 0.1.8
4
17
 
5
- July 18
18
+ July 18 2012
6
19
 
7
20
  * Fix bug in Channel#trigger preventing the data from coming through
8
21
  properly.
9
22
 
10
23
  ## Version 0.1.7
11
24
 
12
- July 17
25
+ July 17 2012
13
26
 
14
27
  * Fixed botched release of 0.1.6
15
28
  * Reorganized directory structure
data/Gemfile CHANGED
@@ -3,11 +3,13 @@ source "http://rubygems.org"
3
3
  gemspec
4
4
 
5
5
  gem "rspec-rails"
6
+ gem "therubyrhino"
7
+ gem "therubyracer"
6
8
  gem "jasmine"
7
9
  gem "headless"
8
10
  gem "coffee-script"
9
11
  gem "thin"
10
- gem "eventmachine", ">= 1.0.0.beta.3"
12
+ gem "eventmachine"
11
13
  gem "faye-websocket"
12
14
  gem "simplecov"
13
15
  gem "ruby_gntp"
@@ -9,6 +9,7 @@ class WebSocketRails.WebSocketConnection
9
9
  @_conn = new WebSocket(@url)
10
10
  @_conn.onmessage = @on_message
11
11
  @_conn.onclose = @on_close
12
+ @_conn.onerror = @on_error
12
13
 
13
14
  trigger: (event) =>
14
15
  if @dispatcher.state != 'connected'
@@ -24,6 +25,10 @@ class WebSocketRails.WebSocketConnection
24
25
  close_event = new WebSocketRails.Event(['connection_closed',{}])
25
26
  @dispatcher.dispatch close_event
26
27
 
28
+ on_error: (event) =>
29
+ error_event = new WebSocketRails.Event(['connection_error',event?.data])
30
+ @dispatcher.dispatch error_event
31
+
27
32
  flush_queue: =>
28
33
  for event in @message_queue
29
34
  @_conn.send event.serialize()
@@ -3,24 +3,50 @@ require 'thin'
3
3
 
4
4
  module WebsocketRails
5
5
  mattr_accessor :app_root
6
-
6
+
7
7
  def self.setup
8
8
  yield self
9
9
  end
10
-
10
+
11
11
  def self.route_block=(routes)
12
12
  @event_routes = routes
13
13
  end
14
-
14
+
15
15
  def self.route_block
16
16
  @event_routes
17
17
  end
18
- end
19
18
 
20
- LOG_LEVEL = :warn
19
+ def self.log_level
20
+ @log_level ||= :warn
21
+ end
22
+
23
+ def self.log_level=(level)
24
+ @log_level = level
25
+ end
26
+
27
+ attr_accessor :synchronize
28
+ module_function :synchronize, :synchronize=
29
+
30
+ def self.synchronize?
31
+ @synchronize == true
32
+ end
33
+
34
+ def self.redis_options
35
+ @redis_options ||= redis_defaults
36
+ end
37
+
38
+ def self.redis_options=(options = {})
39
+ @redis_options = redis_defaults.merge(options)
40
+ end
41
+
42
+ def self.redis_defaults
43
+ {:host => '127.0.0.1', :port => 6379}
44
+ end
45
+ end
21
46
 
22
47
  require 'websocket_rails/engine'
23
48
  require 'websocket_rails/logging'
49
+ require 'websocket_rails/synchronization'
24
50
  require 'websocket_rails/connection_manager'
25
51
  require 'websocket_rails/dispatcher'
26
52
  require 'websocket_rails/event'
@@ -1,6 +1,8 @@
1
1
  module WebsocketRails
2
2
  class Channel
3
3
 
4
+ include Logging
5
+
4
6
  attr_reader :name, :subscribers
5
7
 
6
8
  def initialize(channel_name)
@@ -10,30 +12,38 @@ module WebsocketRails
10
12
  end
11
13
 
12
14
  def subscribe(connection)
15
+ log "#{connection} subscribed to channel #{name}"
13
16
  @subscribers << connection
14
17
  end
15
18
 
16
- def trigger(event_name,data={})
17
- event_data = {:data => data, :channel => name}
18
- channel_event = Event.new event_name, event_data
19
- send_data channel_event
19
+ def trigger(event_name,data={},options={})
20
+ options.merge! :channel => name
21
+ options[:data] = data
22
+
23
+ event = Event.new event_name, options
24
+
25
+ send_data event
20
26
  end
21
27
 
22
28
  def trigger_event(event)
23
29
  send_data event
24
30
  end
25
-
31
+
26
32
  def make_private
27
33
  @private = true
28
34
  end
29
-
35
+
30
36
  def is_private?
31
37
  @private
32
38
  end
33
-
39
+
34
40
  private
35
41
 
36
42
  def send_data(event)
43
+ if WebsocketRails.synchronize? && event.server_token.nil?
44
+ Synchronization.publish event
45
+ end
46
+
37
47
  subscribers.each do |subscriber|
38
48
  subscriber.trigger event
39
49
  end
@@ -17,7 +17,7 @@ module WebsocketRails
17
17
  class ChannelManager
18
18
 
19
19
  attr_reader :channels
20
-
20
+
21
21
  def initialize
22
22
  @channels = HashWithIndifferentAccess.new
23
23
  end
@@ -1,19 +1,19 @@
1
1
  module WebsocketRails
2
2
  module ConnectionAdapters
3
-
3
+
4
4
  attr_reader :adapters
5
5
  module_function :adapters
6
-
6
+
7
7
  def self.register(adapter)
8
8
  @adapters ||= []
9
9
  @adapters.unshift adapter
10
10
  end
11
-
11
+
12
12
  def self.establish_connection(request,dispatcher)
13
13
  adapter = adapters.detect { |a| a.accepts?( request.env ) } || (raise InvalidConnectionError)
14
14
  adapter.new request, dispatcher
15
15
  end
16
-
16
+
17
17
  class Base
18
18
 
19
19
  include Logging
@@ -21,11 +21,11 @@ module WebsocketRails
21
21
  def self.accepts?(env)
22
22
  false
23
23
  end
24
-
24
+
25
25
  def self.inherited(adapter)
26
26
  ConnectionAdapters.register adapter
27
27
  end
28
-
28
+
29
29
  attr_reader :dispatcher, :queue, :env, :request
30
30
 
31
31
  def initialize(request,dispatcher)
@@ -52,6 +52,7 @@ module WebsocketRails
52
52
  end
53
53
 
54
54
  def on_close(data=nil)
55
+ @ping_timer.cancel
55
56
  dispatch Event.new_on_close( self, data )
56
57
  close_connection
57
58
  end
@@ -93,11 +94,11 @@ module WebsocketRails
93
94
  def send(message)
94
95
  raise NotImplementedError, "Override this method in the connection specific adapter class"
95
96
  end
96
-
97
+
97
98
  def rack_response
98
99
  [ -1, {}, [] ]
99
100
  end
100
-
101
+
101
102
  def id
102
103
  object_id.to_i
103
104
  end
@@ -135,6 +136,6 @@ module WebsocketRails
135
136
  end
136
137
 
137
138
  end
138
-
139
+
139
140
  end
140
141
  end
@@ -7,18 +7,18 @@ module WebsocketRails
7
7
  'Content-Type' => 'text/json',
8
8
  'Transfer-Encoding' => 'chunked'
9
9
  }
10
-
10
+
11
11
  def self.accepts?(env)
12
12
  true
13
13
  end
14
-
14
+
15
15
  attr_accessor :headers
16
16
 
17
17
  def initialize(env,dispatcher)
18
18
  super
19
19
  @body = DeferrableBody.new
20
20
  @headers = HttpHeaders
21
-
21
+
22
22
  define_deferrable_callbacks
23
23
 
24
24
  EM.next_tick do
@@ -30,9 +30,9 @@ module WebsocketRails
30
30
  def send(message)
31
31
  @body.chunk encode_chunk( message )
32
32
  end
33
-
33
+
34
34
  private
35
-
35
+
36
36
  def define_deferrable_callbacks
37
37
  @body.callback do |event|
38
38
  on_close(event)
@@ -41,7 +41,7 @@ module WebsocketRails
41
41
  on_close(event)
42
42
  end
43
43
  end
44
-
44
+
45
45
  # From [Rack::Stream](https://github.com/intridea/rack-stream)
46
46
  def encode_chunk(c)
47
47
  return nil if c.nil?
@@ -53,7 +53,7 @@ module WebsocketRails
53
53
  c.dup.force_encoding(Encoding::BINARY) if c.respond_to?(:force_encoding)
54
54
  [size.to_s(16), TERM, c, TERM].join
55
55
  end
56
-
56
+
57
57
  # From [thin_async](https://github.com/macournoyer/thin_async)
58
58
  class DeferrableBody
59
59
  include EM::Deferrable
@@ -103,7 +103,7 @@ module WebsocketRails
103
103
  end
104
104
  end
105
105
  end
106
-
106
+
107
107
  end
108
108
  end
109
109
  end
@@ -1,11 +1,11 @@
1
1
  module WebsocketRails
2
2
  module ConnectionAdapters
3
3
  class WebSocket < Base
4
-
4
+
5
5
  def self.accepts?(env)
6
6
  Faye::WebSocket.websocket?( env )
7
7
  end
8
-
8
+
9
9
  def initialize(request,dispatcher)
10
10
  super
11
11
  @connection = Faye::WebSocket.new( request.env )
@@ -14,7 +14,7 @@ module WebsocketRails
14
14
  @connection.onclose = method(:on_close)
15
15
  on_open
16
16
  end
17
-
17
+
18
18
  def send(message)
19
19
  @connection.send message
20
20
  end
@@ -23,7 +23,7 @@ module WebsocketRails
23
23
  data = event.respond_to?(:data) ? event.data : event
24
24
  super data
25
25
  end
26
-
26
+
27
27
  end
28
28
  end
29
29
  end
@@ -12,35 +12,48 @@ module WebsocketRails
12
12
  SuccessfulResponse = [200,{'Content-Type' => 'text/plain'},['success']].freeze
13
13
  BadRequestResponse = [400,{'Content-Type' => 'text/plain'},['invalid']].freeze
14
14
  ExceptionResponse = [500,{'Content-Type' => 'text/plain'},['exception']].freeze
15
-
15
+
16
16
  # Contains an Array of currently open connections.
17
17
  # @return [Array]
18
18
  attr_reader :connections
19
-
19
+
20
20
  # Contains the {Dispatcher} instance for the active server.
21
21
  # @return [Dispatcher]
22
22
  attr_reader :dispatcher
23
-
23
+
24
+ # Contains the {Synchronization} instance for the active server.
25
+ # @return [Synchronization]
26
+ attr_reader :synchronization
27
+
24
28
  def initialize
25
29
  @connections = []
26
- @dispatcher = Dispatcher.new( self )
30
+ @dispatcher = Dispatcher.new(self)
31
+
32
+ if WebsocketRails.synchronize?
33
+ EM.next_tick do
34
+ Fiber.new {
35
+ Synchronization.synchronize!
36
+ EM.add_shutdown_hook { Synchronization.shutdown! }
37
+ }.resume
38
+ end
39
+ end
27
40
  end
28
-
41
+
29
42
  # Primary entry point for the Rack application
30
43
  def call(env)
31
- request = ActionDispatch::Request.new env
44
+ request = ActionDispatch::Request.new(env)
32
45
 
33
46
  if request.post?
34
- response = parse_incoming_event request.params
47
+ response = parse_incoming_event(request.params)
35
48
  else
36
- response = open_connection request
49
+ response = open_connection(request)
37
50
  end
38
-
51
+
39
52
  response
40
53
  rescue InvalidConnectionError
41
54
  BadRequestResponse
42
55
  end
43
-
56
+
44
57
  private
45
58
 
46
59
  def parse_incoming_event(params)
@@ -52,7 +65,7 @@ module WebsocketRails
52
65
  def find_connection_by_id(id)
53
66
  connections.detect { |connection| connection.id == id.to_i } || (raise InvalidConnectionError)
54
67
  end
55
-
68
+
56
69
  # Opens a persistent connection using the appropriate {ConnectionAdapter}. Stores
57
70
  # active connections in the {connections} array.
58
71
  def open_connection(request)
@@ -68,6 +81,6 @@ module WebsocketRails
68
81
  connection = nil
69
82
  end
70
83
  public :close_connection
71
-
84
+
72
85
  end
73
86
  end
@@ -4,16 +4,16 @@ module WebsocketRails
4
4
  # variables defined in actions will be shared between clients. The {DataStore} provides a Hash
5
5
  # that is private for each connected client. It is accessed through a WebsocketRails controller
6
6
  # using the {BaseController.data_store} instance method.
7
- #
7
+ #
8
8
  # = Example Usage
9
9
  # == Creating a user
10
10
  # # action on ChatController called by :client_connected event
11
11
  # def new_user
12
12
  # # This would be overwritten when the next user joins
13
13
  # @user = User.new( message[:user_name] )
14
- #
14
+ #
15
15
  # # This will remain private for each user
16
- # data_store[:user] = User.new( message[:user_name] )
16
+ # data_store[:user] = User.new( message[:user_name] )
17
17
  # end
18
18
  #
19
19
  # == Collecting all Users from the DataStore
@@ -29,11 +29,11 @@ module WebsocketRails
29
29
  # data_store.each_user
30
30
  # => [UserOne,UserTwo,UserThree]
31
31
  class DataStore
32
-
32
+
33
33
  extend Forwardable
34
-
34
+
35
35
  def_delegator :@base, :client_id, :cid
36
-
36
+
37
37
  def initialize(base_controller)
38
38
  @base = base_controller
39
39
  @data = Hash.new {|h,k| h[k] = Hash.new}
@@ -55,15 +55,15 @@ module WebsocketRails
55
55
  block.call(hash) if block
56
56
  end
57
57
  end
58
-
58
+
59
59
  def remove_client
60
60
  @data.delete(cid)
61
61
  end
62
-
62
+
63
63
  def delete(key)
64
64
  @data[cid].delete(key)
65
65
  end
66
-
66
+
67
67
  def method_missing(method, *args, &block)
68
68
  if /each_(?<hash_key>\w*)/ =~ method
69
69
  results = []
@@ -76,4 +76,4 @@ module WebsocketRails
76
76
  end
77
77
  end
78
78
  end
79
- end
79
+ end