plezi 0.9.2 → 0.10.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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +30 -0
  3. data/README.md +44 -31
  4. data/bin/plezi +3 -3
  5. data/lib/plezi.rb +21 -43
  6. data/lib/plezi/common/defer.rb +21 -0
  7. data/lib/plezi/common/dsl.rb +115 -91
  8. data/lib/plezi/common/redis.rb +44 -0
  9. data/lib/plezi/common/settings.rb +58 -0
  10. data/lib/plezi/handlers/controller_core.rb +132 -0
  11. data/lib/plezi/handlers/controller_magic.rb +85 -259
  12. data/lib/plezi/handlers/http_router.rb +139 -60
  13. data/lib/plezi/handlers/route.rb +9 -178
  14. data/lib/plezi/handlers/stubs.rb +2 -2
  15. data/lib/plezi/helpers/http_sender.rb +72 -0
  16. data/lib/plezi/helpers/magic_helpers.rb +12 -0
  17. data/lib/plezi/{server → helpers}/mime_types.rb +0 -0
  18. data/lib/plezi/version.rb +1 -1
  19. data/plezi.gemspec +3 -11
  20. data/resources/Gemfile +20 -21
  21. data/resources/controller.rb +2 -2
  22. data/resources/oauth_config.rb +1 -1
  23. data/resources/redis_config.rb +2 -0
  24. data/test/plezi_tests.rb +39 -46
  25. metadata +24 -33
  26. data/lib/plezi/common/logging.rb +0 -60
  27. data/lib/plezi/eventmachine/connection.rb +0 -190
  28. data/lib/plezi/eventmachine/em.rb +0 -98
  29. data/lib/plezi/eventmachine/io.rb +0 -272
  30. data/lib/plezi/eventmachine/protocol.rb +0 -54
  31. data/lib/plezi/eventmachine/queue.rb +0 -51
  32. data/lib/plezi/eventmachine/ssl_connection.rb +0 -144
  33. data/lib/plezi/eventmachine/timers.rb +0 -117
  34. data/lib/plezi/eventmachine/workers.rb +0 -33
  35. data/lib/plezi/handlers/http_echo.rb +0 -27
  36. data/lib/plezi/handlers/http_host.rb +0 -214
  37. data/lib/plezi/handlers/magic_helpers.rb +0 -32
  38. data/lib/plezi/server/http.rb +0 -129
  39. data/lib/plezi/server/http_protocol.rb +0 -319
  40. data/lib/plezi/server/http_request.rb +0 -146
  41. data/lib/plezi/server/http_response.rb +0 -319
  42. data/lib/plezi/server/websocket.rb +0 -251
  43. data/lib/plezi/server/websocket_client.rb +0 -178
  44. data/lib/plezi/server/ws_response.rb +0 -161
@@ -0,0 +1,44 @@
1
+
2
+ module Plezi
3
+
4
+ module_function
5
+
6
+ # Reviews the Redis connection, sets it up if it's missing and returns the Redis connection.
7
+ #
8
+ # A Redis connection will be automatically created if the `ENV['PL_REDIS_URL']` is set.
9
+ # for example:
10
+ # ENV['PL_REDIS_URL'] = ENV['REDISCLOUD_URL']`
11
+ # or
12
+ # ENV['PL_REDIS_URL'] = "redis://username:password@my.host:6379"
13
+ def redis_connection
14
+ return @redis if (@redis_sub_thread && @redis_sub_thread.alive?) && @redis
15
+ return false unless defined?(Redis) && ENV['PL_REDIS_URL']
16
+ @redis_uri ||= URI.parse(ENV['PL_REDIS_URL'])
17
+ @redis ||= Redis.new(host: @redis_uri.host, port: @redis_uri.port, password: @redis_uri.password)
18
+ raise "Redis connction failed for: #{ENV['PL_REDIS_URL']}" unless @redis
19
+ @redis_sub_thread = Thread.new do
20
+ begin
21
+ Redis.new(host: @redis_uri.host, port: @redis_uri.port, password: @redis_uri.password).subscribe(Plezi::Settings.redis_channel_name) do |on|
22
+ on.message do |channel, msg|
23
+ begin
24
+ data = YAML.load(msg)
25
+ next if data[:server] == Plezi::Settings::UUID
26
+ if data[:target]
27
+ GRHttp::Base::WSHandler.unicast data[:target], data
28
+ else
29
+ GRHttp::Base::WSHandler.broadcast data
30
+ end
31
+ rescue => e
32
+ Plezi.error e
33
+ end
34
+ end
35
+ end
36
+ rescue => e
37
+ Plezi.error e
38
+ retry
39
+ end
40
+ end
41
+ @redis
42
+ end
43
+ end
44
+
@@ -0,0 +1,58 @@
1
+
2
+ module Plezi
3
+
4
+ # This module allows you to set some of the Plezi framework's settings.
5
+ module Settings
6
+
7
+ module_function
8
+
9
+ # The maximum number of threads that are used for concurrency.
10
+ def max_threads
11
+ @max_threads ||= 8
12
+ end
13
+ # Sets the maximum number of threads that are used for concurrency.
14
+ def max_threads=val
15
+ @max_threads = val
16
+ end
17
+
18
+ # The number of second between pings automatically sent by an open websocket.
19
+ def autoping
20
+ @autoping ||= 45
21
+ end
22
+ # Sets the number of second between pings automatically sent by an open websocket.
23
+ #
24
+ # Set to nil or false to disable auto-pinging.
25
+ def autoping=val
26
+ @autoping = 45
27
+ end
28
+
29
+ # Sets the Redis Channel Name.
30
+ def redis_channel_name=val
31
+ return false unless defined? Redis
32
+ raise "Can't change channel name after Redis subscription had been initiated." if @redis
33
+ @redis_channel_name = val
34
+ end
35
+ # @return [String] Returns the Redis Channel Name.
36
+ def redis_channel_name
37
+ @redis_channel_name ||= 'Plezi_Redis_Channel'
38
+ end
39
+
40
+ # Sets the message byte size limit for a Websocket message. Defaults to 0 (no limit)
41
+ #
42
+ # Although memory will be allocated for the latest TCP/IP frame,
43
+ # this allows the websocket to disconnect if the incoming expected message size exceeds the allowed maximum size.
44
+ #
45
+ # If the sessage size limit is exceeded, the disconnection will be immidiate as an attack will be assumed. The protocol's normal disconnect sequesnce will be discarded.
46
+ def ws_message_size_limit=val
47
+ GRHttp.ws_message_size_limit = val
48
+ end
49
+ # Gets the message byte size limit for a Websocket message. Defaults to 0 (no limit)
50
+ def ws_message_size_limit
51
+ GRHttp.ws_message_size_limit
52
+ end
53
+
54
+ # This Server's UUID
55
+ UUID = SecureRandom.uuid
56
+ end
57
+ end
58
+
@@ -0,0 +1,132 @@
1
+ module Plezi
2
+ module Base
3
+
4
+ # the methods defined in this module will be injected into the Controller's Core class (inherited from the controller).
5
+ module ControllerCore
6
+ def self.included base
7
+ base.send :include, InstanceMethods
8
+ base.extend ClassMethods
9
+ end
10
+
11
+ module InstanceMethods
12
+ public
13
+
14
+ def initialize request, response
15
+ @request = request
16
+ @params = request.params
17
+ @flash = response.flash
18
+ @host_params = request.io[:params]
19
+ @response = response
20
+ @cookies = request.cookies
21
+ # @response["content-type"] ||= ::Plezi.default_content_type
22
+ super()
23
+ end
24
+
25
+
26
+ # WebSockets.
27
+ #
28
+ # this method handles the protocol and handler transition between the HTTP connection
29
+ # (with a protocol instance of HTTPProtocol and a handler instance of HTTPRouter)
30
+ # and the WebSockets connection
31
+ # (with a protocol instance of WSProtocol and an instance of the Controller class set as a handler)
32
+ def pre_connect
33
+ # make sure this is a websocket controller
34
+ return false unless self.class.has_super_method?(:on_message)
35
+ # call the controller's original method, if exists, and check connection.
36
+ return false if (defined?(super) && !super)
37
+ # finish if the response was sent
38
+ return false if response.headers_sent?
39
+ # complete handshake
40
+ return self
41
+ end
42
+ # handles websocket opening.
43
+ def on_open ws
44
+ # set broadcasts and return true
45
+ @response = ws
46
+ ws.autopong Plezi::Settings.autoping unless Plezi::Settings.autoping
47
+ # create the redis connection (in case this in the first instance of this class)
48
+ Plezi.redis_connection
49
+ super() if defined?(super)
50
+ end
51
+ # handles websocket messages.
52
+ def on_message ws
53
+ super(ws.data) if defined?(super)
54
+ end
55
+ # handles websocket being closed.
56
+ def on_close ws
57
+ super() if defined? super
58
+ end
59
+ # handles websocket being closed.
60
+ def on_broadcast ws
61
+ data = ws.data
62
+ unless (data[:type] || data[:target]) && data[:method] && data[:data]
63
+ GReactor.warn "Broadcast message unknown... falling back on base broadcasting"
64
+ return super(data) if defined? super
65
+ return false
66
+ end
67
+ return false if data[:type] && !self.is_a?(data[:type])
68
+ return false unless self.class.has_method?(data[:method])
69
+ self.method(data[:method]).call *data[:data]
70
+ end
71
+
72
+ # Inner Routing
73
+ #
74
+ #
75
+ def _route_path_to_methods_and_set_the_response_
76
+ #run :before filter
77
+ return false if self.class.has_method?(:before) && self.before == false
78
+ #check request is valid and call requested method
79
+ ret = requested_method
80
+ return false unless ret
81
+ ret = self.method(ret).call
82
+ return false unless ret
83
+ #run :after filter
84
+ return false if self.class.has_method?(:after) && self.after == false
85
+ # review returned type for adding String to response
86
+ return ret
87
+ end
88
+
89
+ end
90
+
91
+ module ClassMethods
92
+ public
93
+
94
+ def reset_routing_cache
95
+ @methods_list = nil
96
+ @exposed_methods_list = nil
97
+ @super_methods_list = nil
98
+ has_method? nil
99
+ has_exposed_method? nil
100
+ has_super_method? nil
101
+ end
102
+ def has_method? method_name
103
+ @methods_list ||= self.instance_methods.to_set
104
+ @methods_list.include? method_name
105
+ end
106
+ def has_exposed_method? method_name
107
+ @exposed_methods_list ||= ( (self.public_instance_methods - Class.new.instance_methods - Plezi::ControllerMagic::InstanceMethods.instance_methods - [:before, :after, :save, :show, :update, :delete, :initialize, :on_message, :on_broadcast, :pre_connect, :on_open, :on_close]).delete_if {|m| m.to_s[0] == '_'} ).to_set
108
+ @exposed_methods_list.include? method_name
109
+ end
110
+ def has_super_method? method_name
111
+ @super_methods_list ||= self.superclass.instance_methods.to_set
112
+ @super_methods_list.include? method_name
113
+ end
114
+
115
+ protected
116
+
117
+ # a callback that resets the class router whenever a method (a potential route) is added
118
+ def method_added(id)
119
+ reset_routing_cache
120
+ end
121
+ # a callback that resets the class router whenever a method (a potential route) is removed
122
+ def method_removed(id)
123
+ reset_routing_cache
124
+ end
125
+ # a callback that resets the class router whenever a method (a potential route) is undefined (using #undef_method).
126
+ def method_undefined(id)
127
+ reset_routing_cache
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
@@ -44,31 +44,6 @@ module Plezi
44
44
  # the parameters used to create the host (the parameters passed to the `listen` / `add_service` call).
45
45
  attr_reader :host_params
46
46
 
47
- # Get's the websocket's unique identifier for unicast transmissions.
48
- #
49
- # This UUID is also used to make sure Radis broadcasts don't triger the
50
- # boadcasting object's event.
51
- def uuid
52
- @uuid ||= SecureRandom.uuid
53
- end
54
- alias :unicast_id :uuid
55
-
56
- # checks whether this instance accepts broadcasts (WebSocket instances).
57
- def accepts_broadcast?
58
- @_accepts_broadcast
59
- end
60
- # sets the controller to refuse "broadcasts".
61
- #
62
- # The controller will also refuse any messages directed at it using the `unicast` method.
63
- #
64
- # This allows some websocket connections to isolate themselves even before they are fully disconnected.
65
- #
66
- # call this method once it is clear the websocket connection should be terminated.
67
- def refuse_broadcasts
68
- @_accepts_broadcast = false
69
- self
70
- end
71
-
72
47
  # this method does two things.
73
48
  #
74
49
  # 1. sets redirection headers for the response.
@@ -140,29 +115,14 @@ module Plezi
140
115
  #
141
116
  def send_data data, options = {}
142
117
  raise 'Cannot use "send_data" after headers were sent' if response.headers_sent?
143
- # write data to response object
118
+ Plezi.warn 'HTTP response buffer is cleared by `#send_data`' if response.body && response.body.any? && response.body.clear
144
119
  response << data
145
120
 
146
121
  # set headers
147
- content_disposition = 'attachment'
122
+ content_disposition = options[:inline] ? 'inline' : 'attachment'
123
+ content_disposition << "; filename=#{options[:filename]}" if options[:filename]
148
124
 
149
- options[:type] ||= MimeTypeHelper::MIME_DICTIONARY[::File.extname(options[:filename])] if options[:filename]
150
-
151
- if options[:type]
152
- response['content-type'] = options[:type]
153
- options.delete :type
154
- end
155
- if options[:inline]
156
- content_disposition = 'inline'
157
- options.delete :inline
158
- end
159
- if options[:attachment]
160
- options.delete :attachment
161
- end
162
- if options[:filename]
163
- content_disposition << "; filename=#{options[:filename]}"
164
- options.delete :filename
165
- end
125
+ response['content-type'] = options[:type] ||= MimeTypeHelper::MIME_DICTIONARY[::File.extname(options[:filename])] if options[:filename]
166
126
  response['content-length'] = data.bytesize rescue true
167
127
  response['content-disposition'] = content_disposition
168
128
  response.finish
@@ -216,7 +176,7 @@ module Plezi
216
176
 
217
177
  # returns the initial method called (or about to be called) by the router for the HTTP request.
218
178
  #
219
- # this is can be very useful within the before / after filters:
179
+ # this can be very useful within the before / after filters:
220
180
  # def before
221
181
  # return false unless "check credentials" && [:save, :update, :delete].include?(requested_method)
222
182
  #
@@ -225,75 +185,56 @@ module Plezi
225
185
  # (which is the last method called before the protocol is switched from HTTP to WebSockets).
226
186
  def requested_method
227
187
  # respond to websocket special case
228
- return :pre_connect if request['upgrade'] && request['upgrade'].to_s.downcase == 'websocket' && request['connection'].to_s.downcase == 'upgrade'
188
+ return :pre_connect if request.upgrade?
229
189
  # respond to save 'new' special case
230
- return :save if request.request_method.match(/POST|PUT|PATCH/) && (params[:id].nil? || params[:id] == 'new')
190
+ return (self.class.has_method?(:save) ? :save : false) if request.request_method.match(/POST|PUT|PATCH/) && (params[:id].nil? || params[:id] == 'new')
231
191
  # set DELETE method if simulated
232
192
  request.request_method = 'DELETE' if params[:_method].to_s.downcase == 'delete'
233
193
  # respond to special :id routing
234
- return params[:id].to_s.to_sym if params[:id] && self.class.available_public_methods.include?(params[:id].to_s.to_sym)
194
+ return params[:id].to_s.to_sym if params[:id] && self.class.has_exposed_method?(params[:id].to_s.to_sym)
235
195
  #review general cases
236
196
  case request.request_method
237
197
  when 'GET', 'HEAD'
238
- return :index unless params[:id]
239
- return :show
198
+ return (self.class.has_method?(:index) ? :index : false) unless params[:id]
199
+ return (self.class.has_method?(:show) ? :show : false)
240
200
  when 'POST', 'PUT', 'PATCH'
241
- return :update
201
+ return (self.class.has_method?(:update) ? :update : false)
242
202
  when 'DELETE'
243
- return :delete
203
+ return (self.class.has_method?(:delete) ? :delete : false)
244
204
  end
245
205
  false
246
206
  end
247
207
 
248
208
  ## WebSockets Magic
249
209
 
210
+ # Get's the websocket's unique identifier for unicast transmissions.
211
+ #
212
+ # This UUID is also used to make sure Radis broadcasts don't triger the
213
+ # boadcasting object's event.
214
+ def uuid
215
+ return @response.uuid if @response.is_a?(GRHttp::WSEvent)
216
+ nil
217
+ end
218
+ alias :unicast_id :uuid
219
+
250
220
  # WebSockets.
251
221
  #
252
222
  # Use this to brodcast an event to all 'sibling' websockets (websockets that have been created using the same Controller class).
253
223
  #
254
- # accepts:
224
+ # Accepts:
255
225
  # method_name:: a Symbol with the method's name that should respond to the broadcast.
256
- # *args:: any arguments that should be passed to the method (IF REDIS IS USED, LIMITATIONS APPLY).
226
+ # args*:: The method's argumenst - It MUST be possible to stringify the arguments into a YAML string, or broadcasting and unicasting will fail when scaling beyond one process / one machine.
257
227
  #
258
- # the method will be called asynchrnously for each sibling instance of this Controller class.
228
+ # The method will be called asynchrnously for each sibling instance of this Controller class.
259
229
  #
260
- def broadcast method_name, *args, &block
261
- return false unless self.class.public_instance_methods.include?(method_name)
262
- @uuid ||= SecureRandom.uuid
263
- self.class.__inner_redis_broadcast(uuid, nil, method_name, args, &block) || self.class.__inner_process_broadcast(uuid, nil, method_name.to_sym, args, &block)
230
+ def broadcast method_name, *args
231
+ return false unless self.class.has_method? method_name
232
+ self.class._inner_broadcast({ method: method_name, data: args, type: self.class}, @response.io)
264
233
  end
265
234
 
266
235
  # {include:ControllerMagic::ClassMethods#unicast}
267
236
  def unicast target_uuid, method_name, *args
268
- self.class.unicast target_uuid, method_name, *args
269
- end
270
-
271
- # WebSockets.
272
- #
273
- # Use this to collect data from all 'sibling' websockets (websockets that have been created using the same Controller class).
274
- #
275
- # This method will call the requested method on all instance siblings and return an Array of the returned values (including nil values).
276
- #
277
- # This method will block the excecution unless a block is passed to the method - in which case
278
- # the block will used as a callback and recieve the Array as a parameter.
279
- #
280
- # i.e.
281
- #
282
- # this will block: `collect :_get_id`
283
- #
284
- # this will not block: `collect(:_get_id) {|a| puts "got #{a.length} responses."; a.each { |id| puts "#{id}"} }
285
- #
286
- # accepts:
287
- # method_name:: a Symbol with the method's name that should respond to the broadcast.
288
- # *args:: any arguments that should be passed to the method.
289
- # &block:: an optional block to be used as a callback.
290
- #
291
- # the method will be called asynchrnously for each sibling instance of this Controller class.
292
- def collect method_name, *args, &block
293
- return Plezi.callback(self, :collect, *args, &block) if block
294
- r = []
295
- ObjectSpace.each_object(self.class) { |controller| r << controller.method(method_name).call(*args) if controller.accepts_broadcast? && (controller.object_id != self.object_id) }
296
- return r
237
+ self.class._inner_broadcast method: method_name, data: args, target: target_uuid
297
238
  end
298
239
 
299
240
 
@@ -307,46 +248,62 @@ module Plezi
307
248
  end
308
249
 
309
250
  module ClassMethods
310
- protected
251
+ public
311
252
 
312
- # Sets the HTTP route that is the owner of this controller.
253
+ # WebSockets: fires an event on all of this controller's active websocket connections.
313
254
  #
314
- # This is used by the Plezi framework internally and is supplied only for advanced purposes. It is better to avoid using this method.
315
- def set_pl_route route
316
- @pl_http_route = route
255
+ # Class method.
256
+ #
257
+ # Use this to brodcast an event to all connections.
258
+ #
259
+ # accepts:
260
+ # method_name:: a Symbol with the method's name that should respond to the broadcast.
261
+ # *args:: any arguments that should be passed to the method (IF REDIS IS USED, LIMITATIONS APPLY).
262
+ #
263
+ # this method accepts and optional block (NON-REDIS ONLY) to be used as a callback for each sibling's event.
264
+ #
265
+ # the method will be called asynchrnously for each sibling instance of this Controller class.
266
+ def broadcast method_name, *args
267
+ return false unless has_method? method_name
268
+ _inner_broadcast method: method_name, data: args, type: self
317
269
  end
318
270
 
319
- # Gets the HTTP route that is the owner of this controller.
271
+ # WebSockets: fires an event on a specific websocket connection using it's UUID.
320
272
  #
321
- # This is used to utilize the `url_for` method.
322
- def get_pl_route
323
- @pl_http_route
273
+ # Use this to unidcast an event to specific websocket connection using it's UUID.
274
+ #
275
+ # accepts:
276
+ # target_uuid:: the target's unique UUID.
277
+ # method_name:: a Symbol with the method's name that should respond to the broadcast.
278
+ # *args:: any arguments that should be passed to the method (IF REDIS IS USED, LIMITATIONS APPLY).
279
+ def unicast target_uuid, method_name, *args
280
+ _inner_broadcast method: method_name, data: args, target: target_uuid
324
281
  end
325
282
 
326
- public
327
-
328
283
  # This class method behaves the same way as the instance method #url_for. See the instance method's documentation for more details.
329
284
  def url_for dest
330
285
  get_pl_route.url_for dest
331
286
  end
332
287
 
333
- # lists the available methods that will be exposed to HTTP requests
334
- def available_public_methods
335
- # set class global to improve performance while checking for supported methods
336
- @available_public_methods ||= (available_routing_methods - [:before, :after, :save, :show, :update, :delete, :initialize, :on_message, :pre_connect, :on_connect, :on_disconnect]).to_set
288
+ # resets the routing cache
289
+ def reset_routing_cache
290
+ @inheritance.each {|sub| sub.reset_routing_cache}
337
291
  end
338
292
 
339
- # lists the available methods that will be exposed to the HTTP router
340
- def available_routing_methods
341
- @available_routing_methods ||= ( ( (public_instance_methods - Object.public_instance_methods) - Plezi::ControllerMagic::InstanceMethods.instance_methods).delete_if {|m| m.to_s[0] == '_'}).to_set
293
+ protected
294
+
295
+ # Sets the HTTP route that is the owner of this controller.
296
+ #
297
+ # This is used by the Plezi framework internally and is supplied only for advanced purposes. It is better to avoid using this method.
298
+ def set_pl_route route
299
+ @pl_http_route = route
342
300
  end
343
301
 
344
- # resets this controller's router, to allow for dynamic changes
345
- def reset_routing_cache
346
- @available_routing_methods = false
347
- @available_public_methods = false
348
- available_routing_methods
349
- available_public_methods
302
+ # Gets the HTTP route that is the owner of this controller.
303
+ #
304
+ # This is used to utilize the `url_for` method.
305
+ def get_pl_route
306
+ @pl_http_route
350
307
  end
351
308
 
352
309
  # a callback that resets the class router whenever a method (a potential route) is added
@@ -361,163 +318,32 @@ module Plezi
361
318
  def method_undefined(id)
362
319
  reset_routing_cache
363
320
  end
364
- # # lists the available methods that will be exposed to HTTP requests
365
- # def available_public_methods
366
- # # set class global to improve performance while checking for supported methods
367
- # Plezi.cached?(self.superclass.name + '_p&rt') ? Plezi.get_cached(self.superclass.name + "_p&rt") : Plezi.cache_data(self.superclass.name + "_p&rt", (available_routing_methods - [:before, :after, :save, :show, :update, :delete, :initialize, :on_message, :pre_connect, :on_connect, :on_disconnect]).to_set )
368
- # end
369
321
 
370
- # # lists the available methods that will be exposed to the HTTP router
371
- # def available_routing_methods
372
- # # set class global to improve performance while checking for supported methods
373
- # Plezi.cached?(self.superclass.name + '_r&rt') ? Plezi.get_cached(self.superclass.name + "_r&rt") : Plezi.cache_data(self.superclass.name + "_r&rt", (((public_instance_methods - Object.public_instance_methods) - Plezi::ControllerMagic::InstanceMethods.instance_methods).delete_if {|m| m.to_s[0] == '_'}).to_set )
374
- # end
375
-
376
- # # resets this controller's router, to allow for dynamic changes
377
- # def reset_routing_cache
378
- # Plezi.clear_cached(self.superclass.name + '_p&rt')
379
- # Plezi.clear_cached(self.superclass.name + '_r&rt')
380
- # available_routing_methods
381
- # available_public_methods
382
- # end
383
-
384
-
385
- # Reviews the Redis connection, sets it up if it's missing and returns the Redis connection.
386
- #
387
- # A Redis connection will be automatically created if the `ENV['PL_REDIS_URL']` is set.
388
- # for example:
389
- # ENV['PL_REDIS_URL'] = ENV['REDISCLOUD_URL']`
390
- # or
391
- # ENV['PL_REDIS_URL'] = "redis://username:password@my.host:6379"
392
- def redis_connection
393
- # return false unless defined?(Redis) && ENV['PL_REDIS_URL']
394
- # return @redis if defined?(@redis_sub_thread) && @redis
395
- # @@redis_uri ||= URI.parse(ENV['PL_REDIS_URL'])
396
- # @redis ||= Redis.new(host: @@redis_uri.host, port: @@redis_uri.port, password: @@redis_uri.password)
397
- # @redis_sub_thread = Thread.new do
398
- # begin
399
- # Redis.new(host: @@redis_uri.host, port: @@redis_uri.port, password: @@redis_uri.password).subscribe(redis_channel_name) do |on|
400
- # on.message do |channel, msg|
401
- # args = JSON.parse(msg)
402
- # params = args.shift
403
- # __inner_process_broadcast params['_pl_ignore_object'], params['_pl_method_broadcasted'].to_sym, args
404
- # end
405
- # end
406
- # rescue Exception => e
407
- # Plezi.error e
408
- # retry
409
- # end
410
- # end
411
- # raise "Redis connction failed for: #{ENV['PL_REDIS_URL']}" unless @redis
412
- # @redis
413
- return false unless defined?(Redis) && ENV['PL_REDIS_URL']
414
- return Plezi.get_cached(self.superclass.name + '_b') if Plezi.cached?(self.superclass.name + '_b')
415
- @@redis_uri ||= URI.parse(ENV['PL_REDIS_URL'])
416
- Plezi.cache_data self.superclass.name + '_b', Redis.new(host: @@redis_uri.host, port: @@redis_uri.port, password: @@redis_uri.password)
417
- raise "Redis connction failed for: #{ENV['PL_REDIS_URL']}" unless Plezi.cached?(self.superclass.name + '_b')
418
- t = Thread.new do
419
- begin
420
- Redis.new(host: @@redis_uri.host, port: @@redis_uri.port, password: @@redis_uri.password).subscribe(redis_channel_name) do |on|
421
- on.message do |channel, msg|
422
- args = JSON.parse(msg)
423
- params = args.shift
424
- __inner_process_broadcast params['_pl_ignore_object'], params['_pl_target_object'], params['_pl_method_broadcasted'].to_sym, args
425
- end
426
- end
427
- rescue Exception => e
428
- Plezi.error e
429
- retry
430
- end
431
- end
432
- Plezi.cache_data self.superclass.name + '_t', t
433
- Plezi.get_cached(self.superclass.name + '_b')
322
+ def inherited sub
323
+ (@inheritance ||= [].to_set) << sub
434
324
  end
435
325
 
436
- # returns a Redis channel name for this controller.
437
- def redis_channel_name
438
- self.superclass.name.to_s
439
- end
440
326
 
441
- # broadcasts messages (methods) for this process
442
- def __inner_process_broadcast ignore, target, method_name, args, &block
443
- ObjectSpace.each_object(self) { |controller| Plezi.callback controller, method_name, *args, &block if controller.accepts_broadcast? && (!ignore || (controller.uuid != ignore)) && (!target || (controller.uuid == target)) }
444
- end
327
+ # WebSockets
445
328
 
446
- # broadcasts messages (methods) between all processes (using Redis).
447
- def __inner_redis_broadcast ignore, target, method_name, args, &block
448
- return false unless redis_connection
449
- raise "Radis broadcasts cannot accept blocks (no inter-process callbacks of memory sharing)!" if block
450
- # raise "Radis broadcasts accept only one paramater, which is an optional Hash (no inter-process memory sharing)" if args.length > 1 || (args[0] && !args[0].is_a?(Hash))
451
- args.unshift ({_pl_method_broadcasted: method_name, _pl_ignore_object: ignore, _pl_target_object: target})
452
- redis_connection.publish(redis_channel_name, args.to_json )
329
+ # sends the broadcast
330
+ def _inner_broadcast data, ignore_io = nil
331
+ if data[:target]
332
+ __inner_redis_broadcast data unless GRHttp::Base::WSHandler.unicast data[:target], data
333
+ else
334
+ GRHttp::Base::WSHandler.broadcast data, ignore_io
335
+ __inner_redis_broadcast data
336
+ end
453
337
  true
454
338
  end
455
339
 
456
- # WebSockets: fires an event on all of this controller's active websocket connections.
457
- #
458
- # Class method.
459
- #
460
- # Use this to brodcast an event to all connections.
461
- #
462
- # accepts:
463
- # method_name:: a Symbol with the method's name that should respond to the broadcast.
464
- # *args:: any arguments that should be passed to the method (IF REDIS IS USED, LIMITATIONS APPLY).
465
- #
466
- # this method accepts and optional block (NON-REDIS ONLY) to be used as a callback for each sibling's event.
467
- #
468
- # the method will be called asynchrnously for each sibling instance of this Controller class.
469
- def broadcast method_name, *args, &block
470
- return false unless public_instance_methods.include?(method_name)
471
- __inner_redis_broadcast(nil, nil, method_name, args, &block) || __inner_process_broadcast(nil, nil, method_name.to_sym, args, &block)
472
- end
473
-
474
- # WebSockets: fires an event on a specific websocket connection using it's UUID.
475
- #
476
- # Use this to unidcast an event to specific websocket connection using it's UUID.
477
- #
478
- # accepts:
479
- # target_uuid:: the target's unique UUID.
480
- # method_name:: a Symbol with the method's name that should respond to the broadcast.
481
- # *args:: any arguments that should be passed to the method (IF REDIS IS USED, LIMITATIONS APPLY).
482
- def unicast target_uuid, method_name, *args
483
- return false unless public_instance_methods.include?(method_name.to_sym)
484
- __inner_redis_broadcast(nil, target_uuid, method_name, args) || __inner_process_broadcast(nil, target_uuid, method_name.to_sym, args)
485
- end
486
-
487
- # WebSockets.
488
- #
489
- # Class method.
490
- #
491
- # Use this to collect data from all websockets for the calling class (websockets that have been created using the same Controller class).
492
- #
493
- # This method will call the requested method on all instance and return an Array of the returned values (including nil values).
494
- #
495
- # This method will block the excecution unless a block is passed to the method - in which case
496
- # the block will used as a callback and recieve the Array as a parameter.
497
- #
498
- # i.e.
499
- #
500
- # this will block: `collect :_get_id`
501
- #
502
- # this will not block: `collect(:_get_id) {|a| puts "got #{a.length} responses."; a.each { |id| puts "#{id}"} }
503
- #
504
- # accepts:
505
- # method_name:: a Symbol with the method's name that should respond to the broadcast.
506
- # *args:: any arguments that should be passed to the method.
507
- # &block:: an optional block to be used as a callback.
508
- #
509
- # the method will be called asynchrnously for each instance of this Controller class.
510
- def collect method_name, *args, &block
511
- return Plezi.push_event(self.method(:collect), *args, &block) if block
512
-
513
- r = []
514
- ObjectSpace.each_object(self) { |controller| r << controller.method(method_name).call(*args) if controller.accepts_broadcast? }
515
- return r
340
+ def __inner_redis_broadcast data
341
+ conn = Plezi.redis_connection
342
+ data[:server] = Plezi::Settings::UUID
343
+ return conn.publish( Plezi::Settings.redis_channel_name, data.to_yaml ) if conn
344
+ false
516
345
  end
517
346
  end
518
347
 
519
- module_function
520
-
521
-
522
348
  end
523
349
  end