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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +30 -0
- data/README.md +44 -31
- data/bin/plezi +3 -3
- data/lib/plezi.rb +21 -43
- data/lib/plezi/common/defer.rb +21 -0
- data/lib/plezi/common/dsl.rb +115 -91
- data/lib/plezi/common/redis.rb +44 -0
- data/lib/plezi/common/settings.rb +58 -0
- data/lib/plezi/handlers/controller_core.rb +132 -0
- data/lib/plezi/handlers/controller_magic.rb +85 -259
- data/lib/plezi/handlers/http_router.rb +139 -60
- data/lib/plezi/handlers/route.rb +9 -178
- data/lib/plezi/handlers/stubs.rb +2 -2
- data/lib/plezi/helpers/http_sender.rb +72 -0
- data/lib/plezi/helpers/magic_helpers.rb +12 -0
- data/lib/plezi/{server → helpers}/mime_types.rb +0 -0
- data/lib/plezi/version.rb +1 -1
- data/plezi.gemspec +3 -11
- data/resources/Gemfile +20 -21
- data/resources/controller.rb +2 -2
- data/resources/oauth_config.rb +1 -1
- data/resources/redis_config.rb +2 -0
- data/test/plezi_tests.rb +39 -46
- metadata +24 -33
- data/lib/plezi/common/logging.rb +0 -60
- data/lib/plezi/eventmachine/connection.rb +0 -190
- data/lib/plezi/eventmachine/em.rb +0 -98
- data/lib/plezi/eventmachine/io.rb +0 -272
- data/lib/plezi/eventmachine/protocol.rb +0 -54
- data/lib/plezi/eventmachine/queue.rb +0 -51
- data/lib/plezi/eventmachine/ssl_connection.rb +0 -144
- data/lib/plezi/eventmachine/timers.rb +0 -117
- data/lib/plezi/eventmachine/workers.rb +0 -33
- data/lib/plezi/handlers/http_echo.rb +0 -27
- data/lib/plezi/handlers/http_host.rb +0 -214
- data/lib/plezi/handlers/magic_helpers.rb +0 -32
- data/lib/plezi/server/http.rb +0 -129
- data/lib/plezi/server/http_protocol.rb +0 -319
- data/lib/plezi/server/http_request.rb +0 -146
- data/lib/plezi/server/http_response.rb +0 -319
- data/lib/plezi/server/websocket.rb +0 -251
- data/lib/plezi/server/websocket_client.rb +0 -178
- 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
|
-
|
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
|
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
|
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.
|
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
|
-
#
|
224
|
+
# Accepts:
|
255
225
|
# method_name:: a Symbol with the method's name that should respond to the broadcast.
|
256
|
-
#
|
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
|
-
#
|
228
|
+
# The method will be called asynchrnously for each sibling instance of this Controller class.
|
259
229
|
#
|
260
|
-
def broadcast method_name, *args
|
261
|
-
return false unless self.class.
|
262
|
-
|
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.
|
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
|
-
|
251
|
+
public
|
311
252
|
|
312
|
-
#
|
253
|
+
# WebSockets: fires an event on all of this controller's active websocket connections.
|
313
254
|
#
|
314
|
-
#
|
315
|
-
|
316
|
-
|
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
|
-
#
|
271
|
+
# WebSockets: fires an event on a specific websocket connection using it's UUID.
|
320
272
|
#
|
321
|
-
#
|
322
|
-
|
323
|
-
|
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
|
-
#
|
334
|
-
def
|
335
|
-
|
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
|
-
|
340
|
-
|
341
|
-
|
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
|
-
#
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
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
|
-
|
371
|
-
|
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
|
-
#
|
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
|
-
#
|
447
|
-
def
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
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
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
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
|