celluloid_pubsub 0.3.3 → 0.4.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/celluloid_pubsub/client.rb +29 -23
- data/lib/celluloid_pubsub/helper.rb +47 -11
- data/lib/celluloid_pubsub/reactor.rb +48 -4
- data/lib/celluloid_pubsub/redis_reactor.rb +274 -0
- data/lib/celluloid_pubsub/version.rb +2 -2
- data/lib/celluloid_pubsub/web_server.rb +67 -49
- data/spec/lib/celluloid_pubsub/reactor_spec.rb +2 -2
- metadata +2 -3
- data/lib/celluloid_pubsub/redis/redis.rb +0 -38
- data/lib/celluloid_pubsub/redis/redis_reactor.rb +0 -96
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ef1607ef65dd326efbf983675c21f745babab415
|
4
|
+
data.tar.gz: fbcfed23005b8d5b22232396fb5106ef13b69877
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 081ee9751029919561347195180494667039584a58d71bb4b2d107582b9f26af0e9b8001bb7bcdae6e86ab2d150c1c4bd4b876879ad9797d8762e130c131fbd5
|
7
|
+
data.tar.gz: 64492820b1123719845b670a22d58314164604319a341af2d7f24c27e015f55f3a75b1da28f4a1d2c270449f4e008b76970cf8cee17752ff5154ecfbee89cf69
|
@@ -7,41 +7,29 @@ module CelluloidPubsub
|
|
7
7
|
# @!attribute actor
|
8
8
|
# @return [Celluloid::Actor] actor to which callbacks will be delegated to
|
9
9
|
#
|
10
|
-
# @!attribute connect_blk
|
11
|
-
# @return [Proc] Block that will execute after the connection is opened
|
12
|
-
#
|
13
|
-
# @!attribute connection
|
14
|
-
# @return [Celluloid::WebSocket::Client] A websocket connection that is used to chat witht the webserver
|
15
|
-
#
|
16
10
|
# @!attribute options
|
17
11
|
# @return [Hash] the options that can be used to connect to webser and send additional data
|
18
12
|
#
|
19
|
-
# @!attribute
|
20
|
-
# @return [String] The
|
21
|
-
#
|
22
|
-
# @!attribute port
|
23
|
-
# @return [String] The port on which the webserver runs on
|
24
|
-
#
|
25
|
-
# @!attribute path
|
26
|
-
# @return [String] The hostname on which the webserver runs on
|
13
|
+
# @!attribute channel
|
14
|
+
# @return [String] The channel to which the client will subscribe to
|
27
15
|
class Client
|
28
16
|
include Celluloid
|
29
17
|
include Celluloid::Logger
|
30
18
|
include CelluloidPubsub::Helper
|
31
19
|
|
32
|
-
|
20
|
+
attr_accessor :actor, :options, :channel
|
33
21
|
finalizer :shutdown
|
34
22
|
# receives a list of options that are used to connect to the webserver and an actor to which the callbacks are delegated to
|
35
23
|
# when receiving messages from a channel
|
36
24
|
#
|
37
25
|
# @param [Hash] options the options that can be used to connect to webser and send additional data
|
38
26
|
# @option options [String] :actor The actor that made the connection
|
27
|
+
# @option options [String] :channel The channel to which the client will subscribe to once the connection is open
|
28
|
+
# @option options [String] :log_file_path The path to the log file where debug messages will be printed, otherwise will use STDOUT
|
39
29
|
# @option options [String]:hostname The hostname on which the webserver runs on
|
40
30
|
# @option options [String] :port The port on which the webserver runs on
|
41
31
|
# @option options [String] :path The request path that the webserver accepts
|
42
32
|
#
|
43
|
-
# @param [Proc] connect_blk Block that will execute after the connection is opened
|
44
|
-
#
|
45
33
|
# @return [void]
|
46
34
|
#
|
47
35
|
# @api public
|
@@ -50,11 +38,15 @@ module CelluloidPubsub
|
|
50
38
|
@actor ||= @options.fetch('actor', nil)
|
51
39
|
@channel ||= @options.fetch('channel', nil)
|
52
40
|
raise "#{self}: Please provide an actor in the options list!!!" if @actor.blank?
|
53
|
-
raise "#{self}: Please provide an channel in the options list!!!" if @channel.blank?
|
54
41
|
supervise_actors
|
55
42
|
setup_celluloid_logger
|
56
43
|
end
|
57
44
|
|
45
|
+
# the method will return the path to the log file where debug messages will be printed
|
46
|
+
#
|
47
|
+
# @return [String, nil] return the path to the log file where debug messages will be printed
|
48
|
+
#
|
49
|
+
# @api public
|
58
50
|
def log_file_path
|
59
51
|
@log_file_path ||= @options.fetch('log_file_path', nil)
|
60
52
|
end
|
@@ -80,14 +72,32 @@ module CelluloidPubsub
|
|
80
72
|
@connection ||= Celluloid::WebSocket::Client.new("ws://#{hostname}:#{port}#{path}", Actor.current)
|
81
73
|
end
|
82
74
|
|
75
|
+
# the method will return the hostname of the server
|
76
|
+
#
|
77
|
+
#
|
78
|
+
# @return [String] the hostname where the server runs on
|
79
|
+
#
|
80
|
+
# @api public
|
83
81
|
def hostname
|
84
82
|
@hostname ||= @options.fetch('hostname', CelluloidPubsub::WebServer::HOST)
|
85
83
|
end
|
86
84
|
|
85
|
+
# the method will return the port on which the server accepts connections
|
86
|
+
#
|
87
|
+
#
|
88
|
+
# @return [String] the port on which the server accepts connections
|
89
|
+
#
|
90
|
+
# @api public
|
87
91
|
def port
|
88
92
|
@port ||= @options.fetch('port', CelluloidPubsub::WebServer::PORT)
|
89
93
|
end
|
90
94
|
|
95
|
+
# the method will return the path of the URL on which the servers acccepts the connection
|
96
|
+
#
|
97
|
+
#
|
98
|
+
# @return [String] the URL path that the server is mounted on
|
99
|
+
#
|
100
|
+
# @api public
|
91
101
|
def path
|
92
102
|
@path ||= @options.fetch('path', CelluloidPubsub::WebServer::PATH)
|
93
103
|
end
|
@@ -175,11 +185,7 @@ module CelluloidPubsub
|
|
175
185
|
# @api public
|
176
186
|
def on_open
|
177
187
|
log_debug("#{@actor.class} websocket connection opened")
|
178
|
-
async.subscribe(@channel)
|
179
|
-
end
|
180
|
-
|
181
|
-
def log_debug(message)
|
182
|
-
debug message if debug_enabled?
|
188
|
+
async.subscribe(@channel) if @channel.present?
|
183
189
|
end
|
184
190
|
|
185
191
|
# callback executes when actor receives a message from a subscribed channel
|
@@ -3,9 +3,9 @@ module CelluloidPubsub
|
|
3
3
|
module Helper
|
4
4
|
# checks if the message has the successfull subscription action
|
5
5
|
#
|
6
|
-
# @param [string] message
|
6
|
+
# @param [string] message The message that will be checked
|
7
7
|
#
|
8
|
-
# @return [
|
8
|
+
# @return [Boolean] return true if message contains key client_action with value 'succesfull_subscription'
|
9
9
|
#
|
10
10
|
# @api public
|
11
11
|
def succesfull_subscription?(message)
|
@@ -14,6 +14,21 @@ module CelluloidPubsub
|
|
14
14
|
|
15
15
|
module_function
|
16
16
|
|
17
|
+
# method used to determine if a action is a subsribe action
|
18
|
+
# @param [string] action The action that will be checked
|
19
|
+
#
|
20
|
+
# @return [Boolean] Returns true if the action equals to 'subscribe'
|
21
|
+
#
|
22
|
+
# @api public
|
23
|
+
def action_subscribe?(action)
|
24
|
+
action == 'subscribe'
|
25
|
+
end
|
26
|
+
|
27
|
+
# sets the celluloid logger and the celluloid exception handler
|
28
|
+
#
|
29
|
+
# @return [void]
|
30
|
+
#
|
31
|
+
# @api private
|
17
32
|
def setup_celluloid_logger
|
18
33
|
return if !debug_enabled? || (respond_to?(:log_file_path) && log_file_path.blank?)
|
19
34
|
setup_log_file
|
@@ -21,6 +36,11 @@ module CelluloidPubsub
|
|
21
36
|
setup_celluloid_exception_handler
|
22
37
|
end
|
23
38
|
|
39
|
+
# sets the celluloid exception handler
|
40
|
+
#
|
41
|
+
# @return [void]
|
42
|
+
#
|
43
|
+
# @api private
|
24
44
|
def setup_celluloid_exception_handler
|
25
45
|
Celluloid.task_class = Celluloid::TaskThread
|
26
46
|
Celluloid.exception_handler do |ex|
|
@@ -28,6 +48,11 @@ module CelluloidPubsub
|
|
28
48
|
end
|
29
49
|
end
|
30
50
|
|
51
|
+
# creates the log file where the debug messages will be printed
|
52
|
+
#
|
53
|
+
# @return [void]
|
54
|
+
#
|
55
|
+
# @api private
|
31
56
|
def setup_log_file
|
32
57
|
return if !debug_enabled? || (respond_to?(:log_file_path) && log_file_path.blank?)
|
33
58
|
FileUtils.mkdir_p(File.dirname(log_file_path)) unless File.directory?(log_file_path)
|
@@ -35,28 +60,39 @@ module CelluloidPubsub
|
|
35
60
|
log_file.sync = true
|
36
61
|
end
|
37
62
|
|
63
|
+
# checks if a given error needs to be filtered
|
64
|
+
#
|
65
|
+
# @param [Exception::Error] error
|
66
|
+
#
|
67
|
+
# @return [Boolean] returns true if the error should be filtered otherwise false
|
68
|
+
#
|
69
|
+
# @api private
|
38
70
|
def filtered_error?(error)
|
39
71
|
[Interrupt].any? { |class_name| error.is_a?(class_name) }
|
40
72
|
end
|
41
73
|
|
42
|
-
# receives a list of options that
|
74
|
+
# receives a list of options that need to be parsed
|
75
|
+
# if it is an Array will return the first element , otherwise if it is
|
76
|
+
# an Hash will return the hash with string keys, otherwise an empty hash
|
43
77
|
#
|
44
|
-
# @param [Hash] options the options that
|
45
|
-
# @option options [String]:hostname The hostname on which the webserver runs on
|
46
|
-
# @option options [Integer] :port The port on which the webserver runs on
|
47
|
-
# @option options [String] :path The request path that the webserver accepts
|
48
|
-
# @option options [Boolean] :spy Enable this only if you want to enable debugging for the webserver
|
49
|
-
# @option options [Integer]:backlog How many connections the server accepts
|
78
|
+
# @param [Hash, Array] options the options that need to be parsed
|
50
79
|
#
|
51
|
-
# @return [
|
80
|
+
# @return [Hash]
|
52
81
|
#
|
53
|
-
# @api
|
82
|
+
# @api private
|
54
83
|
def parse_options(options)
|
55
84
|
options = options.is_a?(Array) ? options.first : options
|
56
85
|
options = options.is_a?(Hash) ? options.stringify_keys : {}
|
57
86
|
options
|
58
87
|
end
|
59
88
|
|
89
|
+
# receives a message, and logs it to the log file if debug is enabled
|
90
|
+
#
|
91
|
+
# @param [Object] message
|
92
|
+
#
|
93
|
+
# @return [void]
|
94
|
+
#
|
95
|
+
# @api private
|
60
96
|
def log_debug(message)
|
61
97
|
debug message if respond_to?(:debug_enabled?) && debug_enabled?
|
62
98
|
end
|
@@ -36,6 +36,12 @@ module CelluloidPubsub
|
|
36
36
|
async.run
|
37
37
|
end
|
38
38
|
|
39
|
+
# the method will return true if debug is enabled
|
40
|
+
#
|
41
|
+
#
|
42
|
+
# @return [Boolean] returns true if debug is enabled otherwise false
|
43
|
+
#
|
44
|
+
# @api public
|
39
45
|
def debug_enabled?
|
40
46
|
@server.debug_enabled?
|
41
47
|
end
|
@@ -71,12 +77,16 @@ module CelluloidPubsub
|
|
71
77
|
nil
|
72
78
|
end
|
73
79
|
|
80
|
+
# the method will return the reactor's class name used in debug messages
|
81
|
+
#
|
82
|
+
#
|
83
|
+
# @return [Class] returns the reactor's class name used in debug messages
|
84
|
+
#
|
85
|
+
# @api public
|
74
86
|
def reactor_class
|
75
87
|
self.class
|
76
88
|
end
|
77
89
|
|
78
|
-
# :nocov:
|
79
|
-
|
80
90
|
# method used to parse a JSON object into a Hash object
|
81
91
|
#
|
82
92
|
# @param [JSON] message
|
@@ -145,7 +155,8 @@ module CelluloidPubsub
|
|
145
155
|
# @option json_data [String] :client_action The action based on which the reactor will decide what action should make
|
146
156
|
#
|
147
157
|
# Possible values are:
|
148
|
-
#
|
158
|
+
# unsubscribe_all
|
159
|
+
# unsubscribe_clients
|
149
160
|
# unsubscribe
|
150
161
|
# subscribe
|
151
162
|
# publish
|
@@ -166,7 +177,7 @@ module CelluloidPubsub
|
|
166
177
|
when 'subscribe'
|
167
178
|
async.start_subscriber(channel, json_data)
|
168
179
|
when 'publish'
|
169
|
-
|
180
|
+
async.publish_event(channel, json_data['data'].to_json)
|
170
181
|
else
|
171
182
|
handle_unknown_action(json_data)
|
172
183
|
end
|
@@ -185,6 +196,14 @@ module CelluloidPubsub
|
|
185
196
|
@server.async.handle_dispatched_message(Actor.current, json_data)
|
186
197
|
end
|
187
198
|
|
199
|
+
# if the reactor has unsubscribed from all his channels will close the websocket connection,
|
200
|
+
# otherwise will delete the channel from his channel list
|
201
|
+
#
|
202
|
+
# @param [String] channel The channel that needs to be deleted from the reactor's list of subscribed channels
|
203
|
+
#
|
204
|
+
# @return [void]
|
205
|
+
#
|
206
|
+
# @api public
|
188
207
|
def forget_channel(channel)
|
189
208
|
if @channels.blank?
|
190
209
|
@websocket.close
|
@@ -254,6 +273,14 @@ module CelluloidPubsub
|
|
254
273
|
@websocket << message.merge('client_action' => 'successful_subscription', 'channel' => channel).to_json unless @server.redis_enabled?
|
255
274
|
end
|
256
275
|
|
276
|
+
# this method will return a list of all subscribers to a particular channel or a empty array
|
277
|
+
#
|
278
|
+
#
|
279
|
+
# @param [String] channel The channel that will be used to fetch all subscribers from this channel
|
280
|
+
#
|
281
|
+
# @return [Array] returns a list of all subscribers to a particular channel or a empty array
|
282
|
+
#
|
283
|
+
# @api public
|
257
284
|
def channel_subscribers(channel)
|
258
285
|
@server.subscribers[channel] || []
|
259
286
|
end
|
@@ -274,6 +301,23 @@ module CelluloidPubsub
|
|
274
301
|
@server.subscribers[channel] = channel_subscribers(channel).push(reactor: Actor.current, message: message)
|
275
302
|
end
|
276
303
|
|
304
|
+
# method for publishing data to a channel
|
305
|
+
#
|
306
|
+
# @param [String] current_topic The Channel to which the reactor instance {CelluloidPubsub::Reactor} will publish the message to
|
307
|
+
# @param [Object] message
|
308
|
+
#
|
309
|
+
# @return [void]
|
310
|
+
#
|
311
|
+
# @api public
|
312
|
+
def publish_event(current_topic, message)
|
313
|
+
return if current_topic.blank? || message.blank?
|
314
|
+
(@server.subscribers[current_topic] || []).each do |hash|
|
315
|
+
hash[:reactor].websocket << message
|
316
|
+
end
|
317
|
+
rescue => exception
|
318
|
+
log_debug("could not publish message #{message} into topic #{current_topic} because of #{exception.inspect}")
|
319
|
+
end
|
320
|
+
|
277
321
|
# unsubscribes all actors from all channels and terminates the curent actor
|
278
322
|
#
|
279
323
|
# @return [void]
|
@@ -0,0 +1,274 @@
|
|
1
|
+
require_relative './reactor'
|
2
|
+
require_relative './helper'
|
3
|
+
module CelluloidPubsub
|
4
|
+
# reactor used for redis pubsub
|
5
|
+
# @!attribute connected
|
6
|
+
# @return [Boolean] returns true if already connected to redis
|
7
|
+
# @!attribute connection
|
8
|
+
# @return [EM::Hiredis] The connection used for redis
|
9
|
+
class RedisReactor < CelluloidPubsub::Reactor
|
10
|
+
include Celluloid
|
11
|
+
include Celluloid::IO
|
12
|
+
include Celluloid::Logger
|
13
|
+
include CelluloidPubsub::Helper
|
14
|
+
|
15
|
+
attr_accessor :connected, :connection
|
16
|
+
|
17
|
+
alias_method :connected?, :connected
|
18
|
+
|
19
|
+
# returns true if already connected to redis otherwise false
|
20
|
+
#
|
21
|
+
# @return [Boolean] returns true if already connected to redis otherwise false
|
22
|
+
#
|
23
|
+
# @api public
|
24
|
+
def connected
|
25
|
+
@connected ||= false
|
26
|
+
end
|
27
|
+
|
28
|
+
# method used to unsubscribe from a channel
|
29
|
+
# @see #redis_action
|
30
|
+
#
|
31
|
+
# @return [void]
|
32
|
+
#
|
33
|
+
# @api public
|
34
|
+
def unsubscribe(channel)
|
35
|
+
super
|
36
|
+
async.redis_action('unsubscribe', channel)
|
37
|
+
end
|
38
|
+
|
39
|
+
# method used to subscribe to a channel
|
40
|
+
# @see #redis_action
|
41
|
+
#
|
42
|
+
# @return [void]
|
43
|
+
#
|
44
|
+
# @api public
|
45
|
+
def add_subscriber_to_channel(channel, message)
|
46
|
+
super
|
47
|
+
async.redis_action('subscribe', channel, message)
|
48
|
+
end
|
49
|
+
|
50
|
+
# method used to unsubscribe from a channel
|
51
|
+
# @see #redis_action
|
52
|
+
#
|
53
|
+
# @return [void]
|
54
|
+
#
|
55
|
+
# @api public
|
56
|
+
def unsubscribe_from_channel(channel)
|
57
|
+
super
|
58
|
+
async.redis_action('unsubscribe', channel)
|
59
|
+
end
|
60
|
+
|
61
|
+
# method used to unsubscribe from all channels
|
62
|
+
# @see #redis_action
|
63
|
+
#
|
64
|
+
# @return [void]
|
65
|
+
#
|
66
|
+
# @api public
|
67
|
+
def unsubscribe_all
|
68
|
+
@channels.each do |channel|
|
69
|
+
async.redis_action('unsubscribe', channel)
|
70
|
+
end
|
71
|
+
info 'clearing connections'
|
72
|
+
shutdown
|
73
|
+
end
|
74
|
+
|
75
|
+
# method used to shutdown the reactor and unsubscribe from all channels
|
76
|
+
# @see #redis_action
|
77
|
+
#
|
78
|
+
# @return [void]
|
79
|
+
#
|
80
|
+
# @api public
|
81
|
+
def shutdown
|
82
|
+
@channels.each do |channel|
|
83
|
+
redis_action('unsubscribe', channel)
|
84
|
+
end if @channels.present?
|
85
|
+
super
|
86
|
+
end
|
87
|
+
|
88
|
+
# method used to publish event using redis
|
89
|
+
#
|
90
|
+
# @return [void]
|
91
|
+
#
|
92
|
+
# @api public
|
93
|
+
def publish_event(topic, data)
|
94
|
+
return if topic.blank? || data.blank?
|
95
|
+
connect_to_redis do |connection|
|
96
|
+
connection.publish(topic, data)
|
97
|
+
end
|
98
|
+
rescue => exception
|
99
|
+
log_debug("could not publish message #{message} into topic #{current_topic} because of #{exception.inspect}")
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
# method used to run the enventmachine and setup the exception handler
|
105
|
+
# @see #run_the_eventmachine
|
106
|
+
# @see #setup_em_exception_handler
|
107
|
+
#
|
108
|
+
# @param [Proc] block the block that will use the connection
|
109
|
+
#
|
110
|
+
# @return [void]
|
111
|
+
#
|
112
|
+
# @api private
|
113
|
+
def connect_to_redis(&block)
|
114
|
+
require 'eventmachine'
|
115
|
+
require 'em-hiredis'
|
116
|
+
run_the_eventmachine(&block)
|
117
|
+
setup_em_exception_handler
|
118
|
+
end
|
119
|
+
|
120
|
+
# method used to connect to redis and yield the connection
|
121
|
+
#
|
122
|
+
# @param [Proc] block the block that will use the connection
|
123
|
+
#
|
124
|
+
# @return [void]
|
125
|
+
#
|
126
|
+
# @api private
|
127
|
+
def run_the_eventmachine(&block)
|
128
|
+
EM.run do
|
129
|
+
@connection ||= EM::Hiredis.connect
|
130
|
+
@connected = true
|
131
|
+
block.call @connection
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# method used to setup the eventmachine exception handler
|
136
|
+
#
|
137
|
+
# @return [void]
|
138
|
+
#
|
139
|
+
# @api private
|
140
|
+
def setup_em_exception_handler
|
141
|
+
EM.error_handler do |error|
|
142
|
+
debug error unless filtered_error?(error)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
# method used to fetch the pubsub client from the connection and yield it
|
147
|
+
#
|
148
|
+
# @return [void]
|
149
|
+
#
|
150
|
+
# @api private
|
151
|
+
def fetch_pubsub
|
152
|
+
connect_to_redis do |connection|
|
153
|
+
@pubsub ||= connection.pubsub
|
154
|
+
yield @pubsub if block_given?
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# method used to fetch the pubsub client from the connection and yield it
|
159
|
+
# @see #action_subscribe
|
160
|
+
#
|
161
|
+
# @param [string] action The action that will be checked
|
162
|
+
# @param [string] channel The channel that reactor has subscribed to
|
163
|
+
# @param [string] message The initial message used to subscribe
|
164
|
+
#
|
165
|
+
# @return [void]
|
166
|
+
#
|
167
|
+
# @api private
|
168
|
+
def action_success(action, channel, message)
|
169
|
+
action_subscribe?(action) ? message.merge('client_action' => 'successful_subscription', 'channel' => channel) : nil
|
170
|
+
end
|
171
|
+
|
172
|
+
# method used execute an action (subscribe or unsubscribe ) to redis
|
173
|
+
# @see #prepare_redis_action
|
174
|
+
# @see #action_success
|
175
|
+
# @see #register_subscription_callbacks
|
176
|
+
#
|
177
|
+
# @param [string] action The action that will be checked
|
178
|
+
# @param [string] channel The channel that reactor has subscribed to
|
179
|
+
# @param [string] message The initial message used to subscribe
|
180
|
+
#
|
181
|
+
# @return [void]
|
182
|
+
#
|
183
|
+
# @api private
|
184
|
+
def redis_action(action, channel = nil, message = {})
|
185
|
+
fetch_pubsub do |pubsub|
|
186
|
+
callback = prepare_redis_action(action)
|
187
|
+
success_message = action_success(action, channel, message)
|
188
|
+
subscription = pubsub.send(action, channel, callback)
|
189
|
+
register_subscription_callbacks(subscription, action, success_message)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# method used check if the action is subscribe and write the incoming message to be websocket or log the message otherwise
|
194
|
+
# @see #log_unsubscriptions
|
195
|
+
# @see #action_subscribe
|
196
|
+
#
|
197
|
+
# @param [String] action The action that will be checked if it is subscribed
|
198
|
+
#
|
199
|
+
# @return [void]
|
200
|
+
#
|
201
|
+
# @api private
|
202
|
+
def prepare_redis_action(action)
|
203
|
+
log_unsubscriptions(pubsub)
|
204
|
+
proc do |subscribed_message|
|
205
|
+
action_subscribe?(action) ? (@websocket << subscribed_message) : log_debug(message)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
# method used to listen to unsubscriptions and log them to log file
|
210
|
+
# @see #register_redis_callback
|
211
|
+
# @see #register_redis_error_callback
|
212
|
+
#
|
213
|
+
# @param [EM::Hiredis::PubsubClient] pubsub The pubsub client that will be used to listen to unsubscriptions
|
214
|
+
#
|
215
|
+
# @return [void]
|
216
|
+
#
|
217
|
+
# @api private
|
218
|
+
def log_unsubscriptions(pubsub)
|
219
|
+
pubsub.on(:unsubscribe) do |subscribed_channel, remaining_subscriptions|
|
220
|
+
log_debug [:unsubscribe_happened, subscribed_channel, remaining_subscriptions].inspect
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
# method used registers the sucess and error callabacks
|
225
|
+
# @see #register_redis_callback
|
226
|
+
# @see #register_redis_error_callback
|
227
|
+
#
|
228
|
+
# @param [EM::DefaultDeferrable] subscription The subscription object
|
229
|
+
# @param [string] action The action that will be checked
|
230
|
+
# @param [string] sucess_message The initial message used to subscribe
|
231
|
+
#
|
232
|
+
# @return [void]
|
233
|
+
#
|
234
|
+
# @api private
|
235
|
+
def register_subscription_callbacks(subscription, action, sucess_message = nil)
|
236
|
+
register_redis_callback(subscription, sucess_message)
|
237
|
+
register_redis_error_callback(subscription, action)
|
238
|
+
end
|
239
|
+
|
240
|
+
# method used to register a success callback and if action is subscribe will write
|
241
|
+
# back to the websocket a message that will say it is a successful_subscription
|
242
|
+
# If action is something else, will log the incoming message
|
243
|
+
# @see #log_debug
|
244
|
+
#
|
245
|
+
# @param [EM::DefaultDeferrable] subscription The subscription object
|
246
|
+
# @param [string] sucess_message The initial message used to subscribe
|
247
|
+
#
|
248
|
+
# @return [void]
|
249
|
+
#
|
250
|
+
# @api private
|
251
|
+
def register_redis_callback(subscription, sucess_message = nil)
|
252
|
+
subscription.callback do |subscriptions_ids|
|
253
|
+
if sucess_message.present?
|
254
|
+
@websocket << sucess_message.merge('subscriptions' => subscriptions_ids).to_json
|
255
|
+
else
|
256
|
+
log_debug "#{action} success #{success_response.inspect}"
|
257
|
+
end
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
# Register an error callback on the deferrable object and logs to file the incoming message
|
262
|
+
# @see #log_debug
|
263
|
+
#
|
264
|
+
# @param [EM::DefaultDeferrable] subscription The subscription object
|
265
|
+
# @param [string] action The action that will be checked
|
266
|
+
#
|
267
|
+
# @return [void]
|
268
|
+
#
|
269
|
+
# @api private
|
270
|
+
def register_redis_error_callback(subscription, action)
|
271
|
+
subscription.errback { |reply| log_debug "#{action} error #{reply.inspect}" }
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
@@ -13,22 +13,6 @@ module CelluloidPubsub
|
|
13
13
|
#
|
14
14
|
# @!attribute subscribers
|
15
15
|
# @return [Hash] The hostname on which the webserver runs on
|
16
|
-
#
|
17
|
-
# @!attribute backlog
|
18
|
-
# @return [Integer] Determines how many connections can be used
|
19
|
-
# Defaults to 1024
|
20
|
-
#
|
21
|
-
# @!attribute hostname
|
22
|
-
# @return [String] The hostname on which the webserver runs on
|
23
|
-
#
|
24
|
-
# @!attribute port
|
25
|
-
# @return [String] The port on which the webserver runs on
|
26
|
-
#
|
27
|
-
# @!attribute path
|
28
|
-
# @return [String] The hostname on which the webserver runs on
|
29
|
-
#
|
30
|
-
# @!attribute spy
|
31
|
-
# @return [Boolean] Enable this only if you want to enable debugging for the webserver
|
32
16
|
class WebServer < Reel::Server::HTTP
|
33
17
|
include Celluloid::Logger
|
34
18
|
include CelluloidPubsub::Helper
|
@@ -40,7 +24,7 @@ module CelluloidPubsub
|
|
40
24
|
# The request path that the webserver accepts by default
|
41
25
|
PATH = '/ws'
|
42
26
|
|
43
|
-
attr_accessor :options, :subscribers
|
27
|
+
attr_accessor :options, :subscribers
|
44
28
|
finalizer :shutdown
|
45
29
|
# receives a list of options that are used to configure the webserver
|
46
30
|
#
|
@@ -64,10 +48,22 @@ module CelluloidPubsub
|
|
64
48
|
super(hostname, port, { spy: spy, backlog: backlog }, &method(:on_connection))
|
65
49
|
end
|
66
50
|
|
51
|
+
# the method will return true if redis can be used otherwise false
|
52
|
+
#
|
53
|
+
#
|
54
|
+
# @return [Boolean] return true if redis can be used otherwise false
|
55
|
+
#
|
56
|
+
# @api public
|
67
57
|
def use_redis
|
68
58
|
@use_redis ||= @options.fetch('use_redis', false)
|
69
59
|
end
|
70
60
|
|
61
|
+
# the method will return true if debug is enabled otherwise false
|
62
|
+
#
|
63
|
+
#
|
64
|
+
# @return [Boolean] returns true if debug is enabled otherwise false
|
65
|
+
#
|
66
|
+
# @api public
|
71
67
|
def debug_enabled?
|
72
68
|
@debug_enabled = @options.fetch('enable_debug', false)
|
73
69
|
@debug_enabled == true
|
@@ -84,66 +80,74 @@ module CelluloidPubsub
|
|
84
80
|
terminate
|
85
81
|
end
|
86
82
|
|
83
|
+
# the method will return the file path of the log file where debug messages will be printed
|
84
|
+
#
|
85
|
+
#
|
86
|
+
# @return [String] returns the file path of the log file where debug messages will be printed
|
87
|
+
#
|
88
|
+
# @api public
|
87
89
|
def log_file_path
|
88
90
|
@log_file_path ||= @options.fetch('log_file_path', nil)
|
89
91
|
end
|
90
92
|
|
93
|
+
# the method will return the hostname on which the server is running on
|
94
|
+
#
|
95
|
+
#
|
96
|
+
# @return [String] returns the hostname on which the server is running on
|
97
|
+
#
|
98
|
+
# @api public
|
91
99
|
def hostname
|
92
100
|
@hostname ||= @options.fetch('hostname', CelluloidPubsub::WebServer::HOST)
|
93
101
|
end
|
94
102
|
|
103
|
+
# the method will return the port on which will accept connections
|
104
|
+
#
|
105
|
+
#
|
106
|
+
# @return [String] returns the port on which will accept connections
|
107
|
+
#
|
108
|
+
# @api public
|
95
109
|
def port
|
96
110
|
@port ||= @options.fetch('port', CelluloidPubsub::WebServer::PORT)
|
97
111
|
end
|
98
112
|
|
113
|
+
# the method will return the URL path on which will acceept connections
|
114
|
+
#
|
115
|
+
#
|
116
|
+
# @return [String] returns the URL path on which will acceept connections
|
117
|
+
#
|
118
|
+
# @api public
|
99
119
|
def path
|
100
120
|
@path ||= @options.fetch('path', CelluloidPubsub::WebServer::PATH)
|
101
121
|
end
|
102
122
|
|
123
|
+
# the method will return true if connection to the server should be spied upon
|
124
|
+
#
|
125
|
+
#
|
126
|
+
# @return [Boolean] returns true if connection to the server should be spied upon, otherwise false
|
127
|
+
#
|
128
|
+
# @api public
|
103
129
|
def spy
|
104
130
|
@spy ||= @options.fetch('spy', false)
|
105
131
|
end
|
106
132
|
|
133
|
+
# the method will return the number of connections allowed to the server
|
134
|
+
#
|
135
|
+
#
|
136
|
+
# @return [Integer] returns the number of connections allowed to the server
|
137
|
+
#
|
138
|
+
# @api public
|
107
139
|
def backlog
|
108
140
|
@backlog = @options.fetch('backlog', 1024)
|
109
141
|
end
|
110
142
|
|
111
|
-
#
|
112
|
-
|
113
|
-
def redis_enabled?
|
114
|
-
use_redis.to_s.downcase == 'true'
|
115
|
-
end
|
116
|
-
|
117
|
-
# method for publishing data to a channel
|
143
|
+
# the method will return true if redis is enabled otherwise false
|
118
144
|
#
|
119
|
-
# @param [String] current_topic The Channel to which the reactor instance {CelluloidPubsub::Rector} will publish the message to
|
120
|
-
# @param [Object] message
|
121
145
|
#
|
122
|
-
# @return [
|
146
|
+
# @return [Boolean] returns true if redis is enabled otherwise false
|
123
147
|
#
|
124
148
|
# @api public
|
125
|
-
def
|
126
|
-
|
127
|
-
publish_redis_event(current_topic, message)
|
128
|
-
else
|
129
|
-
publish_clasic_event(current_topic, message)
|
130
|
-
end
|
131
|
-
rescue => exception
|
132
|
-
log_debug("could not publish message #{message} into topic #{current_topic} because of #{exception.inspect}")
|
133
|
-
end
|
134
|
-
|
135
|
-
def publish_redis_event(topic, data)
|
136
|
-
return if !redis_enabled? || topic.blank? || data.blank?
|
137
|
-
CelluloidPubsub::Redis.connect do |connection|
|
138
|
-
connection.publish(topic, data)
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
|
-
def publish_clasic_event(channel, data)
|
143
|
-
return if channel.blank? || data.blank?
|
144
|
-
(@subscribers[channel] || []).each do |hash|
|
145
|
-
hash[:reactor].websocket << data
|
146
|
-
end
|
149
|
+
def redis_enabled?
|
150
|
+
use_redis.to_s.downcase == 'true'
|
147
151
|
end
|
148
152
|
|
149
153
|
# callback that will execute when receiving new conections
|
@@ -180,10 +184,24 @@ module CelluloidPubsub
|
|
180
184
|
end
|
181
185
|
end
|
182
186
|
|
187
|
+
# returns the reactor class that will handle the connection depending if redis is enabled or not
|
188
|
+
# @see #redis_enabled?
|
189
|
+
#
|
190
|
+
# @return [Class] returns the reactor class that will handle the connection depending if redis is enabled or not
|
191
|
+
#
|
192
|
+
# @api public
|
183
193
|
def reactor_class
|
184
194
|
redis_enabled? ? CelluloidPubsub::RedisReactor : CelluloidPubsub::Reactor
|
185
195
|
end
|
186
196
|
|
197
|
+
# method will instantiate a new reactor object, will link the reactor to the current actor and will dispatch the request to the reactor
|
198
|
+
# @see #route_websocket
|
199
|
+
#
|
200
|
+
# @param [Reel::WebSocket] request The request that was made to the webserver
|
201
|
+
#
|
202
|
+
# @return [void]
|
203
|
+
#
|
204
|
+
# @api public
|
187
205
|
def dispatch_websocket_request(request)
|
188
206
|
reactor = reactor_class.new
|
189
207
|
Actor.current.link reactor
|
@@ -110,7 +110,7 @@ describe CelluloidPubsub::Reactor do
|
|
110
110
|
|
111
111
|
it 'publish' do
|
112
112
|
data = { 'client_action' => 'publish', 'channel' => 'some channel', 'data' => 'some data' }
|
113
|
-
|
113
|
+
subject.expects(:publish_event).with(data['channel'], data['data'].to_json)
|
114
114
|
subject.delegate_action(data)
|
115
115
|
end
|
116
116
|
|
@@ -196,7 +196,7 @@ describe CelluloidPubsub::Reactor do
|
|
196
196
|
describe '#add_subscriber_to_channel' do
|
197
197
|
let(:channel) { 'some channel' }
|
198
198
|
let(:message) { { a: 'b' } }
|
199
|
-
let(:subscribers) {mock}
|
199
|
+
let(:subscribers) { mock }
|
200
200
|
|
201
201
|
it 'adds subscribed' do
|
202
202
|
CelluloidPubsub::Registry.channels.stubs(:include?).with(channel).returns(false)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: celluloid_pubsub
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- bogdanRada
|
@@ -419,8 +419,7 @@ files:
|
|
419
419
|
- lib/celluloid_pubsub/client.rb
|
420
420
|
- lib/celluloid_pubsub/helper.rb
|
421
421
|
- lib/celluloid_pubsub/reactor.rb
|
422
|
-
- lib/celluloid_pubsub/
|
423
|
-
- lib/celluloid_pubsub/redis/redis_reactor.rb
|
422
|
+
- lib/celluloid_pubsub/redis_reactor.rb
|
424
423
|
- lib/celluloid_pubsub/registry.rb
|
425
424
|
- lib/celluloid_pubsub/version.rb
|
426
425
|
- lib/celluloid_pubsub/web_server.rb
|
@@ -1,38 +0,0 @@
|
|
1
|
-
require_relative '../helper'
|
2
|
-
module CelluloidPubsub
|
3
|
-
# class that handles redis connection
|
4
|
-
class Redis
|
5
|
-
class << self
|
6
|
-
include Celluloid::Logger
|
7
|
-
include CelluloidPubsub::Helper
|
8
|
-
|
9
|
-
@connected ||= false
|
10
|
-
attr_accessor :connected, :connection
|
11
|
-
|
12
|
-
alias_method :connected?, :connected
|
13
|
-
|
14
|
-
def connect(&block)
|
15
|
-
require 'eventmachine'
|
16
|
-
require 'em-hiredis'
|
17
|
-
run_the_eventmachine(&block)
|
18
|
-
setup_em_exception_handler
|
19
|
-
end
|
20
|
-
|
21
|
-
private
|
22
|
-
|
23
|
-
def run_the_eventmachine(&block)
|
24
|
-
EM.run do
|
25
|
-
@connection ||= EM::Hiredis.connect
|
26
|
-
@connected = true
|
27
|
-
block.call @connection
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
|
-
def setup_em_exception_handler
|
32
|
-
EM.error_handler do |error|
|
33
|
-
debug error unless filtered_error?(error)
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
@@ -1,96 +0,0 @@
|
|
1
|
-
require_relative '../reactor'
|
2
|
-
module CelluloidPubsub
|
3
|
-
# reactor used for redis pubsub
|
4
|
-
class RedisReactor < CelluloidPubsub::Reactor
|
5
|
-
include Celluloid
|
6
|
-
include Celluloid::IO
|
7
|
-
include Celluloid::Logger
|
8
|
-
|
9
|
-
def unsubscribe(channel)
|
10
|
-
super
|
11
|
-
async.redis_action('unsubscribe', channel)
|
12
|
-
end
|
13
|
-
|
14
|
-
def add_subscriber_to_channel(channel, message)
|
15
|
-
super
|
16
|
-
async.redis_action('subscribe', channel, message)
|
17
|
-
end
|
18
|
-
|
19
|
-
def unsubscribe_from_channel(channel)
|
20
|
-
super
|
21
|
-
async.redis_action('unsubscribe', channel)
|
22
|
-
end
|
23
|
-
|
24
|
-
def unsubscribe_all
|
25
|
-
CelluloidPubsub::Registry.channels.map do |channel|
|
26
|
-
async.redis_action('unsubscribe', channel)
|
27
|
-
end
|
28
|
-
info 'clearing connections'
|
29
|
-
shutdown
|
30
|
-
end
|
31
|
-
|
32
|
-
def shutdown
|
33
|
-
@channels.each do |channel|
|
34
|
-
redis_action('unsubscribe', channel)
|
35
|
-
end if @channels.present?
|
36
|
-
super
|
37
|
-
end
|
38
|
-
|
39
|
-
private
|
40
|
-
|
41
|
-
def fetch_pubsub
|
42
|
-
CelluloidPubsub::Redis.connect do |connection|
|
43
|
-
yield connection.pubsub
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
def action_subscribe?(action)
|
48
|
-
action == 'subscribe'
|
49
|
-
end
|
50
|
-
|
51
|
-
def action_success(action, channel, message)
|
52
|
-
action_subscribe?(action) ? message.merge('client_action' => 'successful_subscription', 'channel' => channel) : nil
|
53
|
-
end
|
54
|
-
|
55
|
-
def redis_action(action, channel = nil, message = {})
|
56
|
-
fetch_pubsub do |pubsub|
|
57
|
-
callback = prepare_redis_action(action)
|
58
|
-
success_message = action_success(action, channel, message)
|
59
|
-
subscription = pubsub.send(action, channel, callback)
|
60
|
-
handle_redis_action(subscription, action, success_message)
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
def prepare_redis_action(action)
|
65
|
-
log_unsubscriptions(pubsub)
|
66
|
-
proc do |subscribed_message|
|
67
|
-
action_subscribe?(action) ? (@websocket << subscribed_message) : log_debug(message)
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
def log_unsubscriptions(pubsub)
|
72
|
-
pubsub.on(:unsubscribe) do |subscribed_channel, remaining_subscriptions|
|
73
|
-
log_debug [:unsubscribe_happened, subscribed_channel, remaining_subscriptions].inspect
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
def handle_redis_action(subscription, action, sucess_message = nil)
|
78
|
-
register_redis_callback(subscription, sucess_message)
|
79
|
-
register_redis_error_callback(subscription, action)
|
80
|
-
end
|
81
|
-
|
82
|
-
def register_redis_callback(subscription, sucess_message = nil)
|
83
|
-
subscription.callback do |subscriptions_ids|
|
84
|
-
if sucess_message.present?
|
85
|
-
@websocket << sucess_message.merge('subscriptions' => subscriptions_ids).to_json
|
86
|
-
else
|
87
|
-
log_debug "#{action} success #{success_response.inspect}"
|
88
|
-
end
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
def register_redis_error_callback(subscription, action)
|
93
|
-
subscription.errback { |reply| log_debug "#{action} error #{reply.inspect}" }
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|