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