hkroger-websocket-rails 0.7.1
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +328 -0
- data/Gemfile +27 -0
- data/MIT-LICENSE +20 -0
- data/README.md +237 -0
- data/Rakefile +72 -0
- data/bin/thin-socketrails +45 -0
- data/lib/assets/javascripts/websocket_rails/abstract_connection.js.coffee +45 -0
- data/lib/assets/javascripts/websocket_rails/channel.js.coffee +70 -0
- data/lib/assets/javascripts/websocket_rails/event.js.coffee +42 -0
- data/lib/assets/javascripts/websocket_rails/http_connection.js.coffee +66 -0
- data/lib/assets/javascripts/websocket_rails/main.js +6 -0
- data/lib/assets/javascripts/websocket_rails/websocket_connection.js.coffee +29 -0
- data/lib/assets/javascripts/websocket_rails/websocket_rails.js.coffee +158 -0
- data/lib/config.ru +3 -0
- data/lib/generators/websocket_rails/install/install_generator.rb +33 -0
- data/lib/generators/websocket_rails/install/templates/events.rb +14 -0
- data/lib/generators/websocket_rails/install/templates/websocket_rails.rb +63 -0
- data/lib/hkroger-websocket-rails.rb +1 -0
- data/lib/rails/app/controllers/websocket_rails/delegation_controller.rb +13 -0
- data/lib/rails/config/routes.rb +7 -0
- data/lib/rails/tasks/websocket_rails.tasks +42 -0
- data/lib/spec_helpers/matchers/route_matchers.rb +65 -0
- data/lib/spec_helpers/matchers/trigger_matchers.rb +113 -0
- data/lib/spec_helpers/spec_helper_event.rb +34 -0
- data/lib/websocket-rails.rb +108 -0
- data/lib/websocket_rails/base_controller.rb +197 -0
- data/lib/websocket_rails/channel.rb +97 -0
- data/lib/websocket_rails/channel_manager.rb +55 -0
- data/lib/websocket_rails/configuration.rb +169 -0
- data/lib/websocket_rails/connection_adapters.rb +195 -0
- data/lib/websocket_rails/connection_adapters/http.rb +120 -0
- data/lib/websocket_rails/connection_adapters/web_socket.rb +36 -0
- data/lib/websocket_rails/connection_manager.rb +119 -0
- data/lib/websocket_rails/controller_factory.rb +80 -0
- data/lib/websocket_rails/data_store.rb +145 -0
- data/lib/websocket_rails/dispatcher.rb +129 -0
- data/lib/websocket_rails/engine.rb +26 -0
- data/lib/websocket_rails/event.rb +189 -0
- data/lib/websocket_rails/event_map.rb +184 -0
- data/lib/websocket_rails/event_queue.rb +33 -0
- data/lib/websocket_rails/internal_events.rb +37 -0
- data/lib/websocket_rails/logging.rb +133 -0
- data/lib/websocket_rails/spec_helpers.rb +3 -0
- data/lib/websocket_rails/synchronization.rb +182 -0
- data/lib/websocket_rails/user_manager.rb +276 -0
- data/lib/websocket_rails/version.rb +3 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/controllers/chat_controller.rb +53 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/models/user.rb +2 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +45 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/database.yml +22 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +26 -0
- data/spec/dummy/config/environments/production.rb +49 -0
- data/spec/dummy/config/environments/test.rb +34 -0
- data/spec/dummy/config/events.rb +7 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +10 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +58 -0
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/migrate/20130902222552_create_users.rb +10 -0
- data/spec/dummy/db/schema.rb +23 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/development.log +17 -0
- data/spec/dummy/log/production.log +0 -0
- data/spec/dummy/log/server.log +0 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +26 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/public/javascripts/application.js +2 -0
- data/spec/dummy/public/javascripts/controls.js +965 -0
- data/spec/dummy/public/javascripts/dragdrop.js +974 -0
- data/spec/dummy/public/javascripts/effects.js +1123 -0
- data/spec/dummy/public/javascripts/prototype.js +6001 -0
- data/spec/dummy/public/javascripts/rails.js +202 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/integration/connection_manager_spec.rb +135 -0
- data/spec/javascripts/support/jasmine.yml +52 -0
- data/spec/javascripts/support/jasmine_helper.rb +38 -0
- data/spec/javascripts/support/vendor/sinon-1.7.1.js +4343 -0
- data/spec/javascripts/websocket_rails/channel_spec.coffee +112 -0
- data/spec/javascripts/websocket_rails/event_spec.coffee +69 -0
- data/spec/javascripts/websocket_rails/helpers.coffee +6 -0
- data/spec/javascripts/websocket_rails/websocket_connection_spec.coffee +158 -0
- data/spec/javascripts/websocket_rails/websocket_rails_spec.coffee +274 -0
- data/spec/spec_helper.rb +41 -0
- data/spec/spec_helpers/matchers/route_matchers_spec.rb +109 -0
- data/spec/spec_helpers/matchers/trigger_matchers_spec.rb +247 -0
- data/spec/spec_helpers/spec_helper_event_spec.rb +66 -0
- data/spec/support/helper_methods.rb +42 -0
- data/spec/support/mock_web_socket.rb +41 -0
- data/spec/unit/base_controller_spec.rb +74 -0
- data/spec/unit/channel_manager_spec.rb +58 -0
- data/spec/unit/channel_spec.rb +169 -0
- data/spec/unit/connection_adapters/http_spec.rb +88 -0
- data/spec/unit/connection_adapters/web_socket_spec.rb +30 -0
- data/spec/unit/connection_adapters_spec.rb +259 -0
- data/spec/unit/connection_manager_spec.rb +148 -0
- data/spec/unit/controller_factory_spec.rb +76 -0
- data/spec/unit/data_store_spec.rb +106 -0
- data/spec/unit/dispatcher_spec.rb +203 -0
- data/spec/unit/event_map_spec.rb +120 -0
- data/spec/unit/event_queue_spec.rb +36 -0
- data/spec/unit/event_spec.rb +181 -0
- data/spec/unit/logging_spec.rb +162 -0
- data/spec/unit/synchronization_spec.rb +150 -0
- data/spec/unit/target_validator_spec.rb +88 -0
- data/spec/unit/user_manager_spec.rb +165 -0
- metadata +320 -0
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
module WebsocketRails
|
|
2
|
+
# Provides a DSL for mapping client events to controller actions.
|
|
3
|
+
#
|
|
4
|
+
# == Example events.rb file
|
|
5
|
+
# # located in config/initializers/events.rb
|
|
6
|
+
# WebsocketRails::EventMap.describe do
|
|
7
|
+
# subscribe :client_connected, to: ChatController, with_method: :client_connected
|
|
8
|
+
# end
|
|
9
|
+
#
|
|
10
|
+
# A single event can be mapped to any number of controller actions.
|
|
11
|
+
#
|
|
12
|
+
# subscribe :new_message, :to => ChatController, :with_method => :rebroadcast_message
|
|
13
|
+
# subscribe :new_message, :to => LogController, :with_method => :log_message
|
|
14
|
+
#
|
|
15
|
+
# Events can be nested underneath namesapces.
|
|
16
|
+
#
|
|
17
|
+
# namespace :product do
|
|
18
|
+
# subscribe :new, :to => ProductController, :with_method => :new
|
|
19
|
+
# end
|
|
20
|
+
class EventMap
|
|
21
|
+
|
|
22
|
+
def self.describe(&block)
|
|
23
|
+
WebsocketRails.config.route_block = block
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
attr_reader :namespace
|
|
27
|
+
|
|
28
|
+
def initialize(dispatcher)
|
|
29
|
+
@dispatcher = dispatcher
|
|
30
|
+
@namespace = DSL.new(dispatcher).evaluate WebsocketRails.config.route_block
|
|
31
|
+
@namespace = DSL.new(dispatcher,@namespace).evaluate InternalEvents.events
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def routes_for(event, &block)
|
|
35
|
+
@namespace.routes_for event, &block
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Proxy the reload_controllers! method to the global namespace.
|
|
39
|
+
def reload_controllers!
|
|
40
|
+
@namespace.reload_controllers!
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Provides the DSL methods available to the Event routes file
|
|
44
|
+
class DSL
|
|
45
|
+
|
|
46
|
+
def initialize(dispatcher,namespace=nil)
|
|
47
|
+
if namespace
|
|
48
|
+
@namespace = namespace
|
|
49
|
+
else
|
|
50
|
+
@namespace = Namespace.new :global, dispatcher
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def evaluate(route_block)
|
|
55
|
+
instance_eval &route_block unless route_block.nil?
|
|
56
|
+
@namespace
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def subscribe(event_name,options)
|
|
60
|
+
@namespace.store event_name, options
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def namespace(new_namespace,&block)
|
|
64
|
+
@namespace = @namespace.find_or_create new_namespace
|
|
65
|
+
instance_eval &block if block.present?
|
|
66
|
+
@namespace = @namespace.parent
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def private_channel(channel)
|
|
70
|
+
WebsocketRails[channel].make_private
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Stores route map for nested namespaces
|
|
76
|
+
class Namespace
|
|
77
|
+
|
|
78
|
+
include Logging
|
|
79
|
+
|
|
80
|
+
attr_reader :name, :controllers, :actions, :namespaces, :parent
|
|
81
|
+
|
|
82
|
+
def initialize(name,dispatcher,parent=nil)
|
|
83
|
+
@name = name
|
|
84
|
+
@parent = parent
|
|
85
|
+
@dispatcher = dispatcher
|
|
86
|
+
@actions = Hash.new {|h,k| h[k] = Array.new}
|
|
87
|
+
@controllers = Hash.new
|
|
88
|
+
@namespaces = Hash.new
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def find_or_create(namespace)
|
|
92
|
+
unless child = namespaces[namespace]
|
|
93
|
+
child = Namespace.new namespace, @dispatcher, self
|
|
94
|
+
namespaces[namespace] = child
|
|
95
|
+
end
|
|
96
|
+
child
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Stores controller/action pairs for events subscribed under
|
|
100
|
+
# this namespace.
|
|
101
|
+
def store(event_name,options)
|
|
102
|
+
klass, action = TargetValidator.validate_target options
|
|
103
|
+
actions[event_name] << [klass,action]
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Iterates through the namespace tree and yields all
|
|
107
|
+
# controller/action pairs stored for the target event.
|
|
108
|
+
def routes_for(event, event_namespace=nil, &block)
|
|
109
|
+
|
|
110
|
+
# Grab the first level namespace from the namespace array
|
|
111
|
+
# and remove it from the copy.
|
|
112
|
+
event_namespace = copy_event_namespace( event, event_namespace ) || return
|
|
113
|
+
namespace = event_namespace.shift
|
|
114
|
+
|
|
115
|
+
# If the namespace matches the current namespace and we are
|
|
116
|
+
# at the last namespace level, yield any controller/action
|
|
117
|
+
# pairs for this event.
|
|
118
|
+
#
|
|
119
|
+
# If the namespace does not match, search the list of child
|
|
120
|
+
# namespaces stored at this level for a match and delegate
|
|
121
|
+
# to it's #routes_for method, passing along the current
|
|
122
|
+
# copy of the event's namespace array.
|
|
123
|
+
if namespace == @name and event_namespace.empty?
|
|
124
|
+
actions[event.name].each do |klass,action|
|
|
125
|
+
block.call klass, action
|
|
126
|
+
end
|
|
127
|
+
else
|
|
128
|
+
child_namespace = event_namespace.first
|
|
129
|
+
child = namespaces[child_namespace]
|
|
130
|
+
child.routes_for event, event_namespace, &block unless child.nil?
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
private
|
|
135
|
+
|
|
136
|
+
def copy_event_namespace(event, namespace=nil)
|
|
137
|
+
namespace = event.namespace.dup if namespace.nil?
|
|
138
|
+
namespace
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
class TargetValidator
|
|
146
|
+
|
|
147
|
+
# Parses the target and extracts controller/action pair or raises an error if target is invalid
|
|
148
|
+
def self.validate_target(target)
|
|
149
|
+
case target
|
|
150
|
+
when Hash
|
|
151
|
+
validate_hash_target target
|
|
152
|
+
when String
|
|
153
|
+
validate_string_target target
|
|
154
|
+
else
|
|
155
|
+
raise('Must specify the event target either as a string product#new_product or as a Hash to: ProductController, with_method: :new_product')
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
private
|
|
160
|
+
|
|
161
|
+
# Parses the target as a Hash, expecting keys to: and with_method:
|
|
162
|
+
def self.validate_hash_target(target)
|
|
163
|
+
klass = target[:to] || raise("Must specify a class for to: option in event route")
|
|
164
|
+
action = target[:with_method] || raise("Must specify a method for with_method: option in event route")
|
|
165
|
+
[klass, action]
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# Parses the target as a String, expecting it to be in the format "product#new_product"
|
|
169
|
+
def self.validate_string_target(target)
|
|
170
|
+
strings = target.split('#')
|
|
171
|
+
raise('The string must be in a format like product#new_product') unless strings.count == 2
|
|
172
|
+
klass = constantize_controller strings[0]
|
|
173
|
+
action = strings[1].to_sym
|
|
174
|
+
[klass, action]
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def self.constantize_controller(controller_string)
|
|
178
|
+
strings = (controller_string << '_controller').split('/')
|
|
179
|
+
strings.map{|string| string.camelize}.join('::').constantize
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module WebsocketRails
|
|
2
|
+
class EventQueue
|
|
3
|
+
|
|
4
|
+
attr_reader :queue
|
|
5
|
+
|
|
6
|
+
def initialize
|
|
7
|
+
@queue = []
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def enqueue(event)
|
|
11
|
+
@queue << event
|
|
12
|
+
end
|
|
13
|
+
alias :<< :enqueue
|
|
14
|
+
|
|
15
|
+
def last
|
|
16
|
+
@queue.last
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def size
|
|
20
|
+
@queue.size
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def flush(&block)
|
|
24
|
+
unless block.nil?
|
|
25
|
+
@queue.each do |item|
|
|
26
|
+
block.call item
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
@queue = []
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module WebsocketRails
|
|
2
|
+
class InternalEvents
|
|
3
|
+
def self.events
|
|
4
|
+
Proc.new do
|
|
5
|
+
namespace :websocket_rails do
|
|
6
|
+
subscribe :pong, :to => InternalController, :with_method => :do_pong
|
|
7
|
+
subscribe :subscribe, :to => InternalController, :with_method => :subscribe_to_channel
|
|
8
|
+
subscribe :unsubscribe, :to => InternalController, :with_method => :unsubscribe_to_channel
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
class InternalController < BaseController
|
|
15
|
+
include Logging
|
|
16
|
+
|
|
17
|
+
def subscribe_to_channel
|
|
18
|
+
channel_name = event.data[:channel]
|
|
19
|
+
unless WebsocketRails[channel_name].is_private?
|
|
20
|
+
WebsocketRails[channel_name].subscribe connection
|
|
21
|
+
trigger_success
|
|
22
|
+
else
|
|
23
|
+
trigger_failure( { :reason => "channel is private", :hint => "use subscribe_private instead." } )
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def unsubscribe_to_channel
|
|
28
|
+
channel_name = event.data[:channel]
|
|
29
|
+
WebsocketRails[channel_name].unsubscribe connection
|
|
30
|
+
trigger_success
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def do_pong
|
|
34
|
+
connection.pong = true
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
require 'active_support/core_ext/module/delegation'
|
|
2
|
+
require 'active_support/core_ext/hash'
|
|
3
|
+
require 'bigdecimal'
|
|
4
|
+
require 'bigdecimal/util'
|
|
5
|
+
|
|
6
|
+
module WebsocketRails
|
|
7
|
+
module Logging
|
|
8
|
+
# Logging module heavily influenced by Travis-Support library
|
|
9
|
+
|
|
10
|
+
LOGGABLE_DATA = [
|
|
11
|
+
String,
|
|
12
|
+
Hash,
|
|
13
|
+
ActiveSupport::HashWithIndifferentAccess
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
ANSI = {
|
|
17
|
+
:red => 31,
|
|
18
|
+
:green => 32,
|
|
19
|
+
:yellow => 33,
|
|
20
|
+
:cyan => 36
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
class << self
|
|
24
|
+
def configure(logger)
|
|
25
|
+
logger.tap do
|
|
26
|
+
logger.formatter = proc { |*args| Format.format(*args) }
|
|
27
|
+
logger.level = Logger.const_get(log_level.to_s.upcase)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def log_level
|
|
32
|
+
WebsocketRails.config.log_level || :debug
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
delegate :logger, :to => WebsocketRails
|
|
37
|
+
|
|
38
|
+
[:fatal, :error, :warn, :info, :debug].each do |level|
|
|
39
|
+
define_method(level) do |*args|
|
|
40
|
+
message, options = *args
|
|
41
|
+
log(level, message, options)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def log(level, message, options = {})
|
|
46
|
+
message.chomp.split("\n").each do |line|
|
|
47
|
+
logger.send(level, wrap(level, self, line, options || {}))
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def log_event_start(event)
|
|
52
|
+
message = "Started Event: #{event.encoded_name}\n"
|
|
53
|
+
message << "#{colorize(:cyan, "Name:")} #{event.encoded_name}\n"
|
|
54
|
+
message << "#{colorize(:cyan, "Data:")} #{event.data.inspect}\n" if log_data?(event)
|
|
55
|
+
message << "#{colorize(:cyan, "Connection:")} #{event.connection}\n\n"
|
|
56
|
+
info message
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def log_event_end(event, time)
|
|
60
|
+
info "Event #{event.encoded_name} Finished in #{time.to_f.to_d.to_s} seconds\n\n"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def log_event(event, &block)
|
|
64
|
+
log_event_start(event) if log_event?(event)
|
|
65
|
+
start_time = Time.now
|
|
66
|
+
block.call
|
|
67
|
+
total_time = Time.now - start_time
|
|
68
|
+
log_event_end(event, total_time) if log_event?(event)
|
|
69
|
+
rescue Exception => ex
|
|
70
|
+
log_exception(ex)
|
|
71
|
+
raise
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def log_event?(event)
|
|
75
|
+
if event.is_internal?
|
|
76
|
+
WebsocketRails.config.log_internal_events?
|
|
77
|
+
else
|
|
78
|
+
true
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def log_data?(event)
|
|
83
|
+
LOGGABLE_DATA.include?(event.data.class)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def log_exception(exception)
|
|
87
|
+
logger.error(wrap(:error, self, "#{exception.class.name}: #{exception.message}"))
|
|
88
|
+
exception.backtrace.each { |line| logger.error(wrap(:error, self, line)) } if exception.backtrace
|
|
89
|
+
logger << "\n"
|
|
90
|
+
rescue Exception => ex
|
|
91
|
+
puts '--- FATAL ---'
|
|
92
|
+
puts 'an exception occured while logging an exception'
|
|
93
|
+
puts ex.message, ex.backtrace
|
|
94
|
+
puts exception.message, exception.backtrace
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def wrap(level, object, message, options = {})
|
|
98
|
+
header = options[:header] || object.log_header
|
|
99
|
+
color = color_for_level(level)
|
|
100
|
+
"[#{colorize(color, header)}] #{message.chomp}"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def colorize(color, text)
|
|
104
|
+
"\e[#{ANSI[color]}m#{text}\e[0m"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def color_for_level(level)
|
|
108
|
+
case level
|
|
109
|
+
when :info then :green
|
|
110
|
+
when :debug then :yellow
|
|
111
|
+
else
|
|
112
|
+
:red
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def log_header
|
|
117
|
+
self.class.name.split('::').last
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
module Format
|
|
121
|
+
class << self
|
|
122
|
+
def format(severity, time, progname, msg)
|
|
123
|
+
"#{severity[0, 1]} [#{format_time(time)}] #{msg}\n"
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def format_time(time)
|
|
127
|
+
time.strftime("%Y-%m-%d %H:%M:%S.") << time.usec.to_s[0, 3]
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
end
|
|
133
|
+
end
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
require "redis/connection/synchrony"
|
|
2
|
+
require "redis"
|
|
3
|
+
require "redis/connection/ruby"
|
|
4
|
+
|
|
5
|
+
module WebsocketRails
|
|
6
|
+
class Synchronization
|
|
7
|
+
|
|
8
|
+
def self.all_users
|
|
9
|
+
singleton.all_users
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def self.find_user(connection)
|
|
13
|
+
singleton.find_user connection
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.register_user(connection)
|
|
17
|
+
singleton.register_user connection
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.destroy_user(connection)
|
|
21
|
+
singleton.destroy_user connection
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def self.publish(event)
|
|
25
|
+
singleton.publish event
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.synchronize!
|
|
29
|
+
singleton.synchronize!
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.shutdown!
|
|
33
|
+
singleton.shutdown!
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def self.redis
|
|
37
|
+
singleton.redis
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.singleton
|
|
41
|
+
@singleton ||= new
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
include Logging
|
|
45
|
+
|
|
46
|
+
def redis
|
|
47
|
+
@redis ||= begin
|
|
48
|
+
redis_options = WebsocketRails.config.redis_options
|
|
49
|
+
debug "Reactor is not running - engaging ruby redis" unless EM.reactor_running?
|
|
50
|
+
debug "Reactor is running - engaging standard redis new" if EM.reactor_running?
|
|
51
|
+
EM.reactor_running? ? Redis.new(redis_options) : ruby_redis
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def ruby_redis
|
|
56
|
+
@ruby_redis ||= begin
|
|
57
|
+
WebsocketRails.config.redis_options.merge(:driver => :ruby) unless WebsocketRails.config.redis_options.has_key? :driver
|
|
58
|
+
redis_options = WebsocketRails.config.redis_options
|
|
59
|
+
Redis.new(redis_options)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def publish(event)
|
|
64
|
+
Fiber.new do
|
|
65
|
+
event.server_token = server_token
|
|
66
|
+
redis.publish "websocket_rails.events", event.serialize
|
|
67
|
+
end.resume
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def server_token
|
|
71
|
+
@server_token
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def synchronize!
|
|
75
|
+
unless @synchronizing
|
|
76
|
+
@server_token = generate_server_token
|
|
77
|
+
register_server(@server_token)
|
|
78
|
+
|
|
79
|
+
synchro = Fiber.new do
|
|
80
|
+
EM.defer do
|
|
81
|
+
fiber_redis = Redis.new(WebsocketRails.config.redis_options)
|
|
82
|
+
fiber_redis.subscribe "websocket_rails.events" do |on|
|
|
83
|
+
debug "Subscribed to websocket_rails events"
|
|
84
|
+
|
|
85
|
+
on.message do |_, encoded_event|
|
|
86
|
+
event = Event.new_from_json(encoded_event, nil)
|
|
87
|
+
|
|
88
|
+
# Do nothing if this is the server that sent this event.
|
|
89
|
+
next if event.server_token == server_token
|
|
90
|
+
|
|
91
|
+
# Ensure an event never gets triggered twice. Events added to the
|
|
92
|
+
# redis queue from other processes may not have a server token
|
|
93
|
+
# attached.
|
|
94
|
+
event.server_token = server_token if event.server_token.nil?
|
|
95
|
+
|
|
96
|
+
trigger_incoming event
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
info "Beginning Synchronization"
|
|
101
|
+
end
|
|
102
|
+
@synchronizing = true
|
|
103
|
+
|
|
104
|
+
EM.next_tick { synchro.resume }
|
|
105
|
+
|
|
106
|
+
trap('TERM') do
|
|
107
|
+
Thread.new { shutdown! }
|
|
108
|
+
end
|
|
109
|
+
trap('INT') do
|
|
110
|
+
Thread.new { shutdown! }
|
|
111
|
+
end
|
|
112
|
+
trap('QUIT') do
|
|
113
|
+
Thread.new { shutdown! }
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def trigger_incoming(event)
|
|
119
|
+
case
|
|
120
|
+
when event.is_channel?
|
|
121
|
+
WebsocketRails[event.channel].trigger_event(event)
|
|
122
|
+
when event.is_user?
|
|
123
|
+
connection = WebsocketRails.users[event.user_id.to_s]
|
|
124
|
+
return if connection.nil?
|
|
125
|
+
connection.trigger event
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def shutdown!
|
|
130
|
+
remove_server(server_token)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def generate_server_token
|
|
134
|
+
begin
|
|
135
|
+
token = SecureRandom.urlsafe_base64
|
|
136
|
+
end while redis.sismember("websocket_rails.active_servers", token)
|
|
137
|
+
|
|
138
|
+
token
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def register_server(token)
|
|
142
|
+
Fiber.new do
|
|
143
|
+
redis.sadd "websocket_rails.active_servers", token
|
|
144
|
+
info "Server Registered: #{token}"
|
|
145
|
+
end.resume
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def remove_server(token)
|
|
149
|
+
ruby_redis.srem "websocket_rails.active_servers", token
|
|
150
|
+
info "Server Removed: #{token}"
|
|
151
|
+
EM.stop
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def register_user(connection)
|
|
155
|
+
Fiber.new do
|
|
156
|
+
id = connection.user_identifier
|
|
157
|
+
user = connection.user
|
|
158
|
+
redis.hset 'websocket_rails.users', id, user.as_json(root: false).to_json
|
|
159
|
+
end.resume
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def destroy_user(identifier)
|
|
163
|
+
Fiber.new do
|
|
164
|
+
redis.hdel 'websocket_rails.users', identifier
|
|
165
|
+
end.resume
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def find_user(identifier)
|
|
169
|
+
Fiber.new do
|
|
170
|
+
raw_user = redis.hget('websocket_rails.users', identifier)
|
|
171
|
+
raw_user ? JSON.parse(raw_user) : nil
|
|
172
|
+
end.resume
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def all_users
|
|
176
|
+
Fiber.new do
|
|
177
|
+
redis.hgetall('websocket_rails.users')
|
|
178
|
+
end.resume
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
end
|
|
182
|
+
end
|