plezi 0.9.2 → 0.10.1

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