plezi 0.10.12 → 0.10.13
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 +18 -0
- data/README.md +15 -19
- data/lib/plezi.rb +1 -0
- data/lib/plezi/common/api.rb +9 -5
- data/lib/plezi/common/redis.rb +1 -1
- data/lib/plezi/common/settings.rb +1 -1
- data/lib/plezi/handlers/controller_core.rb +2 -38
- data/lib/plezi/handlers/controller_magic.rb +1 -119
- data/lib/plezi/handlers/placebo old.rb +161 -0
- data/lib/plezi/handlers/placebo.rb +8 -45
- data/lib/plezi/handlers/ws_object.rb +204 -0
- data/lib/plezi/helpers/http_sender.rb +1 -1
- data/lib/plezi/version.rb +1 -1
- data/plezi.gemspec +3 -3
- data/resources/404.erb +93 -36
- data/resources/404.haml +91 -34
- data/resources/404.html +83 -26
- data/resources/404.slim +83 -26
- data/resources/500.erb +109 -52
- data/resources/500.haml +84 -27
- data/resources/500.html +109 -51
- data/resources/500.slim +83 -26
- data/resources/controller.rb +2 -1
- data/resources/mini_app.rb +8 -5
- data/resources/welcome_page.html +4 -4
- data/test/plezi_tests.rb +82 -38
- metadata +10 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7f37f01622fbcda6cf86ca9575aafb2145b5479f
|
4
|
+
data.tar.gz: cbdf67890ad9995b11d058cbe821c62fad863995
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f940d9e1731e9708228c8519a3be967762c427db4d3644aac6ceb439db8a9e44ac1de7bdfd4d3e998039f70452e1e2ffe02dc4ab444c4445793a017c1c02b432
|
7
|
+
data.tar.gz: 9a336108299a74946dd97b6718f2440f80df3d5ec6dc33a925c35e5f82eb0d15c21571bbdd6a79c7c1b3cbfafd7ee79a89b3b449773e467018667a21948be1c0
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,23 @@
|
|
1
1
|
#Change Log
|
2
2
|
|
3
|
+
Change log v.0.10.13
|
4
|
+
|
5
|
+
**Fix**: The Placebo API was tested and an issue with the new Placebo class broadcast method was fixed.
|
6
|
+
|
7
|
+
**Update**: Websocket code refactoring unified Placebo and Controller's API and bahavior.
|
8
|
+
|
9
|
+
**Update**: Unicasting performance using Redis was improved by creating a different Redis channel for each process, so that the unicast is only sent to the process containing the receiver.
|
10
|
+
|
11
|
+
this should mean that apps using unicasting can scale freely while apps using broadcasting need to address boradcasting considirations (broadcasting causes ALL the websocket connections - in ALL processes - to answer a broadcast, which raises scaling considirations).
|
12
|
+
|
13
|
+
***
|
14
|
+
|
15
|
+
Change log v.0.10.12
|
16
|
+
|
17
|
+
**Placebo API**: The Placebo API was reviews and revamped slightly, to allow you a better experience and better error feedback.
|
18
|
+
|
19
|
+
**Template**: The template's error pages were restyled with Plezi's new color scheme.
|
20
|
+
|
3
21
|
***
|
4
22
|
|
5
23
|
Change log v.0.10.12
|
data/README.md
CHANGED
@@ -4,11 +4,15 @@
|
|
4
4
|
|
5
5
|
Plezi is an easy to use Ruby Websocket Framework, with full RESTful routing support and HTTP streaming support. It's name comes from the word "fun", or "pleasure", since Plezi is a pleasure to work with.
|
6
6
|
|
7
|
-
|
7
|
+
With Plezi, you can easily:
|
8
8
|
|
9
|
-
|
9
|
+
1. Add Websocket services and RESTful HTTP Streaming to your existing Web-App, (Rails/Sinatra or any other Rack based Ruby app).
|
10
10
|
|
11
|
-
|
11
|
+
2. Create an easily scalable backend for your SPA.
|
12
|
+
|
13
|
+
3. Create a full fledged Ruby web application, taking full advantage of RESTful routing, HTTP streaming and scalable Websocket features.
|
14
|
+
|
15
|
+
Plezi leverages [GRHttp server](https://github.com/boazsegev/GRHttp)'s new architecture. GRHttp is a pure Ruby HTTP and Websocket Generic Server built using [GReactor](https://github.com/boazsegev/GReactor) - a multi-threaded pure ruby alternative to EventMachine with basic process forking support (enjoy forking, if your code is scaling ready).
|
12
16
|
|
13
17
|
## Installation
|
14
18
|
|
@@ -43,21 +47,11 @@ the default first port for the app is 3000. you can set the first port to listen
|
|
43
47
|
|
44
48
|
you now have a smart framework app that will happily assimilate any gem you feed it. it responds extra well to Haml, Sass and Coffee-Script, which you can enable in it's Gemfile.
|
45
49
|
|
46
|
-
## Barebones Web Service
|
47
|
-
|
48
|
-
This example is basic, useless, but required for every doc out there...
|
49
|
-
|
50
|
-
"Hello World!" in 3 lines - try it in irb (exit irb to start server):
|
51
|
-
|
52
|
-
require 'plezi'
|
53
|
-
listen
|
54
|
-
route(/.?/) { |req, res| res << "Hello World!" }
|
55
|
-
|
56
|
-
After you exit irb, the Plezi server will start up. Go to http://localhost:3000/ and see it run :)
|
57
|
-
|
58
50
|
## Plezi Controller classes
|
59
51
|
|
60
|
-
One of the best things about the Plezi is it's ability to take in any class as a controller class and route to the classes methods with special support for RESTful methods (`index`, `show`, `new`, `save`, `update`, `delete`, `before` and `after`) and for WebSockets (`pre_connect`, `on_open`, `on_message(data)`, `on_close`, `broadcast`, `unicast`, `on_broadcast(data)`)
|
52
|
+
One of the best things about the Plezi is it's ability to take in any class as a controller class and route to the classes methods with special support for RESTful methods (`index`, `show`, `new`, `save`, `update`, `delete`, `before` and `after`) and for WebSockets (`pre_connect`, `on_open`, `on_message(data)`, `on_close`, `broadcast`, `unicast`, `multicast`, `on_broadcast(data)`).
|
53
|
+
|
54
|
+
Here is a Hello World using a Controller class (run in `irb`):
|
61
55
|
|
62
56
|
require 'plezi'
|
63
57
|
|
@@ -70,11 +64,13 @@ One of the best things about the Plezi is it's ability to take in any class as a
|
|
70
64
|
listen
|
71
65
|
route '*' , Controller
|
72
66
|
|
73
|
-
|
67
|
+
exit # Plezi will autostart once you exit irb.
|
68
|
+
|
69
|
+
Except while using WebSockets, returning a String will automatically add the string to the response before sending the response - which makes for cleaner code. It's also possible to use the `response` object to set the response or stream HTTP (return true instead of a stream when you're done).
|
74
70
|
|
75
|
-
|
71
|
+
It's also possible to define a number of controllers for a similar route. The controllers will answer in the order in which the routes are defined (this allows to group code by logic instead of url).
|
76
72
|
|
77
|
-
\* please read the demo code for Plezi::StubRESTCtrl and Plezi::StubWSCtrl to learn more. Also, read more about the [GRHttp
|
73
|
+
\* please read the demo code for Plezi::StubRESTCtrl and Plezi::StubWSCtrl to learn more. Also, read more about the [GRHttp Websocket and HTTP server](https://github.com/boazsegev/GRHttp) at the core of Plezi to get more information about the amazing [HTTPRequest](http://www.rubydoc.info/github/boazsegev/GRHttp/master/GRHttp/HTTPRequest) and [HTTPResponse](http://www.rubydoc.info/github/boazsegev/GRHttp/master/GRHttp/HTTPResponse) objects.
|
78
74
|
|
79
75
|
## Native Websocket and Redis support
|
80
76
|
|
data/lib/plezi.rb
CHANGED
@@ -45,6 +45,7 @@ require 'plezi/helpers/mime_types.rb'
|
|
45
45
|
### HTTP and WebSocket Handlers
|
46
46
|
require 'plezi/handlers/http_router.rb'
|
47
47
|
require 'plezi/handlers/route.rb'
|
48
|
+
require 'plezi/handlers/ws_object.rb'
|
48
49
|
require 'plezi/handlers/controller_magic.rb'
|
49
50
|
require 'plezi/handlers/controller_core.rb'
|
50
51
|
require 'plezi/handlers/placebo.rb'
|
data/lib/plezi/common/api.rb
CHANGED
@@ -10,9 +10,9 @@ module Plezi
|
|
10
10
|
# port:: port number. defaults to 3000 or the port specified when the script was called.
|
11
11
|
# host:: the host name. defaults to any host not explicitly defined (a catch-all). NOTICE: in order to allow for hostname aliases, this is host emulation and the listening socket will bind to all the addresses available. To limit the actual binding use the `:bind` parameter as set by the GReactor's API - in which case host aliases might not work.
|
12
12
|
# alias:: a String or an Array of Strings which represent alternative host names (i.e. `alias: ["admin.google.com", "admin.gmail.com"]`).
|
13
|
-
#
|
13
|
+
# public:: the public root folder. if this is defined, static files will be served from this folder and all it's sub-folders. Plezi does NOT support file indexing.
|
14
14
|
# assets:: the assets root folder. defaults to nil (no assets support). if the path is defined, assets will be served from `/assets/...` (or the public_asset path defined) before any static files. assets will not be served if the file in the /public/assets folder if up to date (a rendering attempt will be made for systems that allow file writing).
|
15
|
-
# assets_public:: the assets public uri location (uri format, NOT a file path). defaults to `/assets`.
|
15
|
+
# assets_public:: the assets public uri location (uri format, NOT a file path). defaults to `/assets`. `save_assets` will set if assets should be saved to the assets public folder as static files (defaults to false).
|
16
16
|
# assets_callback:: a method that accepts two parameters: (request, response) and renders any custom assets. the method should return `false` unless it had set the response.
|
17
17
|
# save_assets:: saves the rendered assets to the filesystem, under the public folder. defaults to false.
|
18
18
|
# templates:: the templates root folder. defaults to nil (no template support). templates can be rendered by a Controller class, using the `render` method.
|
@@ -20,14 +20,16 @@ module Plezi
|
|
20
20
|
# ssl_key:: the public key for the SSL service.
|
21
21
|
# ssl_cert:: the certificate for the SSL service.
|
22
22
|
#
|
23
|
-
#
|
23
|
+
# Assets:
|
24
24
|
#
|
25
|
-
#
|
25
|
+
# Assets support will render `.sass`, `.scss` and `.coffee` and save them as local files (`.css`, `.css`, and `.js` respectively)
|
26
26
|
# before sending them as static files.
|
27
27
|
#
|
28
|
+
# Should you need to render a different type of asset, you can define an assets_callback (or submit a pull request with a patch).
|
29
|
+
#
|
28
30
|
# templates:
|
29
31
|
#
|
30
|
-
# ERB, Slim and Haml are natively supported.
|
32
|
+
# Plezi's controller.render ERB, Slim and Haml are natively supported.
|
31
33
|
#
|
32
34
|
# @returns [Plezi::Router]
|
33
35
|
#
|
@@ -36,6 +38,8 @@ module Plezi
|
|
36
38
|
parameters[:index_file] ||= 'index.html'
|
37
39
|
parameters[:assets_public] ||= '/assets'
|
38
40
|
parameters[:assets_public].chomp! '/'
|
41
|
+
parameters[:public] ||= parameters[:root] # backwards compatability
|
42
|
+
puts "Warning: 'root' option is being depracated. use 'public' instead." if parameters[:root]
|
39
43
|
|
40
44
|
# check if the port is used twice.
|
41
45
|
@routers_locker.synchronize do
|
data/lib/plezi/common/redis.rb
CHANGED
@@ -21,7 +21,7 @@ module Plezi
|
|
21
21
|
raise "Redis connction failed for: #{ENV['PL_REDIS_URL']}" unless @redis
|
22
22
|
@redis_sub_thread = Thread.new do
|
23
23
|
begin
|
24
|
-
Redis.new(host: @redis_uri.host, port: @redis_uri.port, password: @redis_uri.password).subscribe(Plezi::Settings.redis_channel_name) do |on|
|
24
|
+
Redis.new(host: @redis_uri.host, port: @redis_uri.port, password: @redis_uri.password).subscribe(Plezi::Settings.redis_channel_name, Plezi::Settings.uuid) do |on|
|
25
25
|
on.message do |channel, msg|
|
26
26
|
begin
|
27
27
|
data = YAML.load(msg)
|
@@ -4,6 +4,7 @@ module Plezi
|
|
4
4
|
# the methods defined in this module will be injected into the Controller's Core class (inherited from the controller).
|
5
5
|
module ControllerCore
|
6
6
|
def self.included base
|
7
|
+
base.send :include, Plezi::Base::WSObject
|
7
8
|
base.send :include, InstanceMethods
|
8
9
|
base.extend ClassMethods
|
9
10
|
end
|
@@ -65,7 +66,7 @@ module Plezi
|
|
65
66
|
return false
|
66
67
|
end
|
67
68
|
return false if data[:type] && data[:type] != :all && !self.is_a?(data[:type])
|
68
|
-
return false if data[:target] && data[:target] != ws.uuid
|
69
|
+
# return false if data[:target] && data[:target] != ws.uuid + Plezi::Settings.uuid # already reviewed by the GRHttp
|
69
70
|
return false unless self.class.has_method?(data[:method])
|
70
71
|
self.method(data[:method]).call *data[:data]
|
71
72
|
end
|
@@ -90,43 +91,6 @@ module Plezi
|
|
90
91
|
end
|
91
92
|
|
92
93
|
module ClassMethods
|
93
|
-
public
|
94
|
-
|
95
|
-
def reset_routing_cache
|
96
|
-
@methods_list = nil
|
97
|
-
@exposed_methods_list = nil
|
98
|
-
@super_methods_list = nil
|
99
|
-
has_method? nil
|
100
|
-
has_exposed_method? nil
|
101
|
-
has_super_method? nil
|
102
|
-
end
|
103
|
-
def has_method? method_name
|
104
|
-
@methods_list ||= self.instance_methods.to_set
|
105
|
-
@methods_list.include? method_name
|
106
|
-
end
|
107
|
-
def has_exposed_method? method_name
|
108
|
-
@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
|
109
|
-
@exposed_methods_list.include? method_name
|
110
|
-
end
|
111
|
-
def has_super_method? method_name
|
112
|
-
@super_methods_list ||= self.superclass.instance_methods.to_set
|
113
|
-
@super_methods_list.include? method_name
|
114
|
-
end
|
115
|
-
|
116
|
-
protected
|
117
|
-
|
118
|
-
# a callback that resets the class router whenever a method (a potential route) is added
|
119
|
-
def method_added(id)
|
120
|
-
reset_routing_cache
|
121
|
-
end
|
122
|
-
# a callback that resets the class router whenever a method (a potential route) is removed
|
123
|
-
def method_removed(id)
|
124
|
-
reset_routing_cache
|
125
|
-
end
|
126
|
-
# a callback that resets the class router whenever a method (a potential route) is undefined (using #undef_method).
|
127
|
-
def method_undefined(id)
|
128
|
-
reset_routing_cache
|
129
|
-
end
|
130
94
|
end
|
131
95
|
end
|
132
96
|
end
|
@@ -204,106 +204,11 @@ module Plezi
|
|
204
204
|
end
|
205
205
|
false
|
206
206
|
end
|
207
|
-
|
208
|
-
## WebSockets Magic
|
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
|
-
|
220
|
-
# WebSockets.
|
221
|
-
#
|
222
|
-
# Use this to brodcast an event to all 'sibling' websockets (websockets that have been created using the same Controller class).
|
223
|
-
#
|
224
|
-
# Accepts:
|
225
|
-
# method_name:: a Symbol with the method's name that should respond to the broadcast.
|
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.
|
227
|
-
#
|
228
|
-
# The method will be called asynchrnously for each sibling instance of this Controller class.
|
229
|
-
#
|
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)
|
233
|
-
end
|
234
|
-
|
235
|
-
# {include:ControllerMagic::ClassMethods#unicast}
|
236
|
-
def unicast target_uuid, method_name, *args
|
237
|
-
self.class._inner_broadcast method: method_name, data: args, target: target_uuid
|
238
|
-
end
|
239
|
-
|
240
|
-
# Use this to multicast an event to ALL websocket connections on EVERY controller, including Placebo controllers.
|
241
|
-
#
|
242
|
-
# Accepts:
|
243
|
-
# method_name:: a Symbol with the method's name that should respond to the broadcast.
|
244
|
-
# 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.
|
245
|
-
#
|
246
|
-
# The method will be called asynchrnously for ALL websocket connections.
|
247
|
-
#
|
248
|
-
def multicast method_name, *args
|
249
|
-
self.class._inner_broadcast({ method: method_name, data: args, type: :all}, @response.io)
|
250
|
-
end
|
251
|
-
|
252
|
-
|
253
|
-
# # will (probably NOT), in the future, require authentication or, alternatively, return an Array [user_name, password]
|
254
|
-
# #
|
255
|
-
# #
|
256
|
-
# def request_http_auth realm = false, auth = 'Digest'
|
257
|
-
# return request.service.handler.hosts[request[:host] || :default].send_by_code request, 401, "WWW-Authenticate" => "#{auth}#{realm ? "realm=\"#{realm}\"" : ''}" unless request['authorization']
|
258
|
-
# request['authorization']
|
259
|
-
# end
|
260
207
|
end
|
261
208
|
|
262
209
|
module ClassMethods
|
263
210
|
public
|
264
211
|
|
265
|
-
# WebSockets: fires an event on all of this controller's active websocket connections.
|
266
|
-
#
|
267
|
-
# Class method.
|
268
|
-
#
|
269
|
-
# Use this to brodcast an event to all connections.
|
270
|
-
#
|
271
|
-
# accepts:
|
272
|
-
# method_name:: a Symbol with the method's name that should respond to the broadcast.
|
273
|
-
# *args:: any arguments that should be passed to the method (IF REDIS IS USED, LIMITATIONS APPLY).
|
274
|
-
#
|
275
|
-
# this method accepts and optional block (NON-REDIS ONLY) to be used as a callback for each sibling's event.
|
276
|
-
#
|
277
|
-
# the method will be called asynchrnously for each sibling instance of this Controller class.
|
278
|
-
def broadcast method_name, *args
|
279
|
-
return false unless has_method? method_name
|
280
|
-
_inner_broadcast method: method_name, data: args, type: self
|
281
|
-
end
|
282
|
-
|
283
|
-
# WebSockets: fires an event on a specific websocket connection using it's UUID.
|
284
|
-
#
|
285
|
-
# Use this to unidcast an event to specific websocket connection using it's UUID.
|
286
|
-
#
|
287
|
-
# accepts:
|
288
|
-
# target_uuid:: the target's unique UUID.
|
289
|
-
# method_name:: a Symbol with the method's name that should respond to the broadcast.
|
290
|
-
# *args:: any arguments that should be passed to the method (IF REDIS IS USED, LIMITATIONS APPLY).
|
291
|
-
def unicast target_uuid, method_name, *args
|
292
|
-
_inner_broadcast method: method_name, data: args, target: target_uuid
|
293
|
-
end
|
294
|
-
|
295
|
-
# Use this to multicast an event to ALL websocket connections on EVERY controller, including Placebo controllers.
|
296
|
-
#
|
297
|
-
# Accepts:
|
298
|
-
# method_name:: a Symbol with the method's name that should respond to the broadcast.
|
299
|
-
# 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.
|
300
|
-
#
|
301
|
-
# The method will be called asynchrnously for ALL websocket connections.
|
302
|
-
#
|
303
|
-
def multicast method_name, *args
|
304
|
-
_inner_broadcast({ method: method_name, data: args, type: :all}, @response.io)
|
305
|
-
end
|
306
|
-
|
307
212
|
# This class method behaves the same way as the instance method #url_for. See the instance method's documentation for more details.
|
308
213
|
def url_for dest
|
309
214
|
get_pl_route.url_for dest
|
@@ -311,7 +216,7 @@ module Plezi
|
|
311
216
|
|
312
217
|
# resets the routing cache
|
313
218
|
def reset_routing_cache
|
314
|
-
@inheritance.each {|sub| sub.reset_routing_cache}
|
219
|
+
@inheritance.each {|sub| sub.reset_routing_cache} if @inheritance
|
315
220
|
end
|
316
221
|
|
317
222
|
protected
|
@@ -346,29 +251,6 @@ module Plezi
|
|
346
251
|
def inherited sub
|
347
252
|
(@inheritance ||= [].to_set) << sub
|
348
253
|
end
|
349
|
-
|
350
|
-
public
|
351
|
-
|
352
|
-
|
353
|
-
# WebSockets
|
354
|
-
|
355
|
-
# sends the broadcast
|
356
|
-
def _inner_broadcast data, ignore_io = nil
|
357
|
-
if data[:target]
|
358
|
-
__inner_redis_broadcast data unless GRHttp::Base::WSHandler.unicast data[:target], data
|
359
|
-
else
|
360
|
-
GRHttp::Base::WSHandler.broadcast data, ignore_io
|
361
|
-
__inner_redis_broadcast data
|
362
|
-
end
|
363
|
-
true
|
364
|
-
end
|
365
|
-
|
366
|
-
def __inner_redis_broadcast data
|
367
|
-
conn = Plezi.redis_connection
|
368
|
-
data[:server] = Plezi::Settings.uuid
|
369
|
-
return conn.publish( Plezi::Settings.redis_channel_name, data.to_yaml ) if conn
|
370
|
-
false
|
371
|
-
end
|
372
254
|
end
|
373
255
|
|
374
256
|
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
module Plezi
|
2
|
+
|
3
|
+
# This API wil allows you to listen to Websocket Broadcasts sent to any object and to accept unicasts
|
4
|
+
# even when NOT connected to a websocket.
|
5
|
+
#
|
6
|
+
# Simpley create a class to handle any events and call `Plezi::Placebo.new ClassName` :
|
7
|
+
#
|
8
|
+
# class MyListener
|
9
|
+
# def _my_method_name *args
|
10
|
+
# #logic
|
11
|
+
# end
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# Plezi::Placebo.new MyListener
|
15
|
+
#
|
16
|
+
# A new instance will be created and that instance will answer any broadcasts, for ALL possible
|
17
|
+
# Plezi controllers, as long as it had a method defined that is capable to handle the broadcast.
|
18
|
+
#
|
19
|
+
# The new instance will also accept unicasts sent to it's unique UUID.
|
20
|
+
#
|
21
|
+
# Returns an instance that is a member of the class passed, after that class was inherited by Plezi and
|
22
|
+
# more methods were injected into it's subclass.
|
23
|
+
module Placebo
|
24
|
+
|
25
|
+
# the base module exposes some of the core functionality, but shouldn't be relied upon as far as it's API goes.
|
26
|
+
module Base
|
27
|
+
#the methods here will be injected to the Placebo controller.
|
28
|
+
module Core
|
29
|
+
def self.included base
|
30
|
+
base.send :include, InstanceMethods
|
31
|
+
base.extend ClassMethods
|
32
|
+
base.superclass.instance_eval {extend SuperClassMethods}
|
33
|
+
end
|
34
|
+
|
35
|
+
#the methods here will be injected to the Placebo controller as Instance methods.
|
36
|
+
module InstanceMethods
|
37
|
+
public
|
38
|
+
attr_accessor :io
|
39
|
+
def initialize io
|
40
|
+
@io = io
|
41
|
+
@io[:websocket_handler] = self
|
42
|
+
super()
|
43
|
+
end
|
44
|
+
# notice of disconnect
|
45
|
+
def on_close
|
46
|
+
return super() if defined? super
|
47
|
+
GR.warn "Placebo #{self.class.superclass.name} disconnected. Ignore if this message appears during shutdown."
|
48
|
+
end
|
49
|
+
|
50
|
+
# handles broadcasts / unicasts
|
51
|
+
def on_broadcast ws
|
52
|
+
data = ws.data
|
53
|
+
unless (data[:type] || data[:target]) && data[:method] && data[:data]
|
54
|
+
GReactor.warn "Broadcast message unknown... falling back on base broadcasting"
|
55
|
+
return super(data) if defined? super
|
56
|
+
return false
|
57
|
+
end
|
58
|
+
return false if data[:type] && data[:type] != :all && !self.is_a?(data[:type])
|
59
|
+
return ((data[:type] == :all) ? false : (raise "Placebo Broadcasting recieved but no method can handle it - dump:\r\n #{data.to_s}") ) unless self.class.has_super_method?(data[:method])
|
60
|
+
self.method(data[:method]).call *data[:data]
|
61
|
+
end
|
62
|
+
# Returns the websocket connection's UUID, used for unicasting.
|
63
|
+
def uuid
|
64
|
+
return (@uuid ||= @response.uuid + Plezi::Settings.uuid) if @response.is_a?(GRHttp::WSEvent)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Performs a websocket unicast to the specified target.
|
68
|
+
def unicast target_uuid, method_name, *args
|
69
|
+
self.class.unicast target_uuid, method_name, *args
|
70
|
+
end
|
71
|
+
# broadcast to a specific controller
|
72
|
+
def broadcast controller_class, method_name, *args
|
73
|
+
GRHttp::Base::WSHandler.broadcast({data: args, type: controller_class, method: method_name}, self)
|
74
|
+
self.class.__send_to_redis data: args, type: controller_class, method: method_name
|
75
|
+
end
|
76
|
+
# multicast to all handlers.
|
77
|
+
def multicast method_name, *args
|
78
|
+
self.class.multicast method_name, *args
|
79
|
+
end
|
80
|
+
end
|
81
|
+
#the methods here will be injected to the Placebo controller as class methods.
|
82
|
+
module ClassMethods
|
83
|
+
public
|
84
|
+
def has_super_method? method_name
|
85
|
+
@super_methods_list ||= self.superclass.instance_methods.to_set
|
86
|
+
@super_methods_list.include? method_name
|
87
|
+
end
|
88
|
+
end
|
89
|
+
module SuperClassMethods
|
90
|
+
# multicast to all handlers.
|
91
|
+
def multicast method_name, *args
|
92
|
+
GRHttp::Base::WSHandler.broadcast({method: method_name, data: args, type: :all}, self)
|
93
|
+
__send_to_redis method: method_name, data: args, type: :all
|
94
|
+
end
|
95
|
+
# Broadcast to all instances (usually one instance) of THIS placebo controller.
|
96
|
+
#
|
97
|
+
# This should be used by the real websocket connections to forward messages to the placebo controller classes.
|
98
|
+
def broadcast method_name, *args
|
99
|
+
@methods_list ||= self.public_instance_methods.to_set
|
100
|
+
raise "No mothod defined to accept this broadcast." unless @methods_list.include? method_name
|
101
|
+
GRHttp::Base::WSHandler.broadcast data: args, type: self, method: method_name
|
102
|
+
__send_to_redis data: args, type: self, method: method_name
|
103
|
+
end
|
104
|
+
# Performs a websocket unicast to the specified target.
|
105
|
+
def unicast target_uuid, method_name, *args
|
106
|
+
GRHttp::Base::WSHandler.unicast target_uuid, data: args, target: target_uuid, method: method_name
|
107
|
+
__send_to_redis data: args, target: target_uuid, method: method_name
|
108
|
+
end
|
109
|
+
protected
|
110
|
+
def __send_to_redis data
|
111
|
+
raise "Wrong method name for websocket broadcasting - expecting type Symbol" unless data[:method].is_a?(Symbol) || data[:method].is_a?(Symbol)
|
112
|
+
conn = Plezi.redis_connection
|
113
|
+
data[:server] = Plezi::Settings.uuid
|
114
|
+
return conn.publish( Plezi::Settings.redis_channel_name, data.to_yaml ) if conn
|
115
|
+
false
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
class PlaceboIO < GReactor::BasicIO
|
120
|
+
def clear?
|
121
|
+
io.closed?
|
122
|
+
end
|
123
|
+
def call
|
124
|
+
self.read
|
125
|
+
GR.warn "Placebo IO recieved IO signal - this is unexpected..."
|
126
|
+
end
|
127
|
+
def on_disconnect
|
128
|
+
@params[:out].close rescue nil
|
129
|
+
@cache[:websocket_handler].on_close if @cache[:websocket_handler]
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
module_function
|
134
|
+
def new placebo_class
|
135
|
+
new_class_name = "PlaceboPlezi__#{placebo_class.name.gsub /[\:\-\#\<\>\{\}\(\)\s]/, '_'}"
|
136
|
+
new_class = nil
|
137
|
+
new_class = Module.const_get new_class_name if Module.const_defined? new_class_name
|
138
|
+
unless new_class
|
139
|
+
new_class = Class.new(placebo_class) do
|
140
|
+
include Placebo::Base::Core
|
141
|
+
end
|
142
|
+
Object.const_set(new_class_name, new_class)
|
143
|
+
end
|
144
|
+
i, o = IO.pipe
|
145
|
+
io = Placebo::Base::PlaceboIO.new i, out: o
|
146
|
+
io = GReactor.add_raw_io i, io
|
147
|
+
new_class.new(io)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
|
153
|
+
# class A
|
154
|
+
# def _hi
|
155
|
+
# 'hi'
|
156
|
+
# end
|
157
|
+
# end
|
158
|
+
# Plezi::Placebo.new A
|
159
|
+
# a = nil
|
160
|
+
# GReactor.each {|h| a= h}
|
161
|
+
# a[:websocket_handler].on_broadcast GRHttp::WSEvent.new(nil, type: true, data: [], method: :_hi)
|