websocket-rails 0.1.8 → 0.1.9

Sign up to get free protection for your applications and to get access to all the features.
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